Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.

Update Unix SafeHandle to throw NotFound correctly (#11757) #11807

Merged
merged 1 commit into from
May 23, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;

namespace Microsoft.Win32.SafeHandles
Expand Down Expand Up @@ -38,18 +39,30 @@ public SafeFileHandle(IntPtr preexistingHandle, bool ownsHandle) : this(ownsHand
internal static SafeFileHandle Open(string path, Interop.Sys.OpenFlags flags, int mode)
{
Debug.Assert(path != null);
SafeFileHandle handle = Interop.Sys.Open(path, flags, mode);

// If we fail to open the file due to a path not existing, we need to know whether to blame
// the file itself or its directory. If we're creating the file, then we blame the directory,
// otherwise we blame the file.
bool enoentDueToDirectory = (flags & Interop.Sys.OpenFlags.O_CREAT) != 0;

// Open the file.
SafeFileHandle handle = Interop.CheckIo(
Interop.Sys.Open(path, flags, mode),
path,
isDirectory: enoentDueToDirectory,
errorRewriter: e => (e.Error == Interop.Error.EISDIR) ? Interop.Error.EACCES.Info() : e);
if (handle.IsInvalid)
{
handle.Dispose();
Interop.ErrorInfo error = Interop.Sys.GetLastErrorInfo();

// If we fail to open the file due to a path not existing, we need to know whether to blame
// the file itself or its directory. If we're creating the file, then we blame the directory,
// otherwise we blame the file.
//
// When opening, we need to align with Windows, which considers a missing path to be
// FileNotFound only if the containing directory exists.

bool isDirectory = (error.Error == Interop.Error.ENOENT) &&
((flags & Interop.Sys.OpenFlags.O_CREAT) != 0
|| !DirectoryExists(Path.GetDirectoryName(PathInternal.TrimEndingDirectorySeparator(path))));

Interop.CheckIo(
error.Error,
path,
isDirectory,
errorRewriter: e => (e.Error == Interop.Error.EISDIR) ? Interop.Error.EACCES.Info() : e);
}

// Make sure it's not a directory; we do this after opening it once we have a file descriptor
// to avoid race conditions.
Expand All @@ -68,6 +81,31 @@ internal static SafeFileHandle Open(string path, Interop.Sys.OpenFlags flags, in
return handle;
}

private static bool DirectoryExists(string fullPath)
{
int fileType = Interop.Sys.FileTypes.S_IFDIR;

Interop.Sys.FileStatus fileinfo;
Interop.ErrorInfo errorInfo = default(Interop.ErrorInfo);

// First use stat, as we want to follow symlinks. If that fails, it could be because the symlink
// is broken, we don't have permissions, etc., in which case fall back to using LStat to evaluate
// based on the symlink itself.
if (Interop.Sys.Stat(fullPath, out fileinfo) < 0 &&
Interop.Sys.LStat(fullPath, out fileinfo) < 0)
{
errorInfo = Interop.Sys.GetLastErrorInfo();
return false;
}

// Something exists at this path. If the caller is asking for a directory, return true if it's
// a directory and false for everything else. If the caller is asking for a file, return false for
// a directory and true for everything else.
return
(fileType == Interop.Sys.FileTypes.S_IFDIR) ==
((fileinfo.Mode & Interop.Sys.FileTypes.S_IFMT) == Interop.Sys.FileTypes.S_IFDIR);
}

/// <summary>Opens a SafeFileHandle for a file descriptor created by a provided delegate.</summary>
/// <param name="fdFunc">
/// The function that creates the file descriptor. Returns the file descriptor on success, or an invalid
Expand Down
5 changes: 5 additions & 0 deletions src/mscorlib/shared/System/IO/PathInternal.Unix.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,5 +100,10 @@ internal static bool IsPartiallyQualified(string path)
// As long as the path is rooted in Unix it doesn't use the current directory and therefore is fully qualified.
return !Path.IsPathRooted(path);
}

internal static string TrimEndingDirectorySeparator(string path) =>
path.Length > 1 && IsDirectorySeparator(path[path.Length - 1]) ? // exclude root "/"
path.Substring(0, path.Length - 1) :
path;
}
}
5 changes: 5 additions & 0 deletions src/mscorlib/shared/System/IO/PathInternal.Windows.cs
Original file line number Diff line number Diff line change
Expand Up @@ -438,5 +438,10 @@ internal static bool IsDirectoryOrVolumeSeparator(char ch)
{
return IsDirectorySeparator(ch) || VolumeSeparatorChar == ch;
}

internal static string TrimEndingDirectorySeparator(string path) =>
EndsInDirectorySeparator(path) ?
path.Substring(0, path.Length - 1) :
path;
}
}