44
55using System ;
66using System . Diagnostics ;
7+ using System . IO ;
78using System . Runtime . InteropServices ;
89
910namespace Microsoft . Win32 . SafeHandles
@@ -38,18 +39,30 @@ public SafeFileHandle(IntPtr preexistingHandle, bool ownsHandle) : this(ownsHand
3839 internal static SafeFileHandle Open ( string path , Interop . Sys . OpenFlags flags , int mode )
3940 {
4041 Debug . Assert ( path != null ) ;
42+ SafeFileHandle handle = Interop . Sys . Open ( path , flags , mode ) ;
4143
42- // If we fail to open the file due to a path not existing, we need to know whether to blame
43- // the file itself or its directory. If we're creating the file, then we blame the directory,
44- // otherwise we blame the file.
45- bool enoentDueToDirectory = ( flags & Interop . Sys . OpenFlags . O_CREAT ) != 0 ;
46-
47- // Open the file.
48- SafeFileHandle handle = Interop . CheckIo (
49- Interop . Sys . Open ( path , flags , mode ) ,
50- path ,
51- isDirectory : enoentDueToDirectory ,
52- errorRewriter : e => ( e . Error == Interop . Error . EISDIR ) ? Interop . Error . EACCES . Info ( ) : e ) ;
44+ if ( handle . IsInvalid )
45+ {
46+ handle . Dispose ( ) ;
47+ Interop . ErrorInfo error = Interop . Sys . GetLastErrorInfo ( ) ;
48+
49+ // If we fail to open the file due to a path not existing, we need to know whether to blame
50+ // the file itself or its directory. If we're creating the file, then we blame the directory,
51+ // otherwise we blame the file.
52+ //
53+ // When opening, we need to align with Windows, which considers a missing path to be
54+ // FileNotFound only if the containing directory exists.
55+
56+ bool isDirectory = ( error . Error == Interop . Error . ENOENT ) &&
57+ ( ( flags & Interop . Sys . OpenFlags . O_CREAT ) != 0
58+ || ! DirectoryExists ( Path . GetDirectoryName ( PathInternal . TrimEndingDirectorySeparator ( path ) ) ) ) ;
59+
60+ Interop . CheckIo (
61+ error . Error ,
62+ path ,
63+ isDirectory ,
64+ errorRewriter : e => ( e . Error == Interop . Error . EISDIR ) ? Interop . Error . EACCES . Info ( ) : e ) ;
65+ }
5366
5467 // Make sure it's not a directory; we do this after opening it once we have a file descriptor
5568 // to avoid race conditions.
@@ -68,6 +81,31 @@ internal static SafeFileHandle Open(string path, Interop.Sys.OpenFlags flags, in
6881 return handle ;
6982 }
7083
84+ private static bool DirectoryExists ( string fullPath )
85+ {
86+ int fileType = Interop . Sys . FileTypes . S_IFDIR ;
87+
88+ Interop . Sys . FileStatus fileinfo ;
89+ Interop . ErrorInfo errorInfo = default ( Interop . ErrorInfo ) ;
90+
91+ // First use stat, as we want to follow symlinks. If that fails, it could be because the symlink
92+ // is broken, we don't have permissions, etc., in which case fall back to using LStat to evaluate
93+ // based on the symlink itself.
94+ if ( Interop . Sys . Stat ( fullPath , out fileinfo ) < 0 &&
95+ Interop . Sys . LStat ( fullPath , out fileinfo ) < 0 )
96+ {
97+ errorInfo = Interop . Sys . GetLastErrorInfo ( ) ;
98+ return false ;
99+ }
100+
101+ // Something exists at this path. If the caller is asking for a directory, return true if it's
102+ // a directory and false for everything else. If the caller is asking for a file, return false for
103+ // a directory and true for everything else.
104+ return
105+ ( fileType == Interop . Sys . FileTypes . S_IFDIR ) ==
106+ ( ( fileinfo . Mode & Interop . Sys . FileTypes . S_IFMT ) == Interop . Sys . FileTypes . S_IFDIR ) ;
107+ }
108+
71109 /// <summary>Opens a SafeFileHandle for a file descriptor created by a provided delegate.</summary>
72110 /// <param name="fdFunc">
73111 /// The function that creates the file descriptor. Returns the file descriptor on success, or an invalid
0 commit comments