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

Commit

Permalink
Update Unix SafeHandle to throw NotFound correctly
Browse files Browse the repository at this point in the history
Need to match Windows semantics for missing files. This means throwing
FileNotFound only if the last segment of the path can't be found.
  • Loading branch information
JeremyKuhne committed May 19, 2017
1 parent dde2324 commit 142eb27
Showing 1 changed file with 43 additions and 10 deletions.
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,25 @@ 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;
if (handle.IsInvalid)
{
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(path.TrimEnd(Path.DirectorySeparatorChar))));

// 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);
throw Interop.GetExceptionForIoErrno(error, path, isDirectory);
}

// 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 +76,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

0 comments on commit 142eb27

Please sign in to comment.