File: src\libraries\System.Private.CoreLib\src\System\IO\FileStatus.Unix.cs
Web Access
Project: src\src\coreclr\System.Private.CoreLib\System.Private.CoreLib.csproj (System.Private.CoreLib)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Diagnostics;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
 
namespace System.IO
{
    internal partial struct FileStatus
    {
        private const int NanosecondsPerTick = 100;
 
        private const int InitializedExistsBrokenLink = -5;  // target is link with no target.
        private const int InitializedExistsDir = -4;  // target is directory.
        private const int InitializedExistsFile = -3; // target is file.
        private const int InitializedNotExistsNotADir = -2;  // entry parent path is not a dir.
        private const int InitializedNotExists = -1;  // entry does not exist.
        private const int Uninitialized = 0;          // uninitialized, '0' to make default(FileStatus) uninitialized.
 
        // Tracks the initialization state.
        // < 0 : initialized succesfully. Value is one of the Initialized* consts.
        //   0 : uninitialized.
        // > 0 : initialized with error. Value is raw errno.
        private int _state;
 
        // The last cached lstat information about the file.
        // Must only be used after calling EnsureCachesInitialized and checking EntryExists is true.
        private Interop.Sys.FileStatus _fileCache;
 
        private bool EntryExists => _state <= InitializedExistsFile;
 
        private bool IsDir => _state == InitializedExistsDir;
 
        private bool IsBrokenLink => _state == InitializedExistsBrokenLink;
 
        // Check if the main path (without following symlinks) has the hidden attribute set.
        private bool HasHiddenFlag
        {
            get
            {
                Debug.Assert(_state != Uninitialized); // Use this after EnsureCachesInitialized has been called.
 
                return EntryExists && (_fileCache.UserFlags & (uint)Interop.Sys.UserFlags.UF_HIDDEN) == (uint)Interop.Sys.UserFlags.UF_HIDDEN;
            }
        }
 
        // Checks if the main path (without following symlinks) has the read-only attribute set.
        private bool HasReadOnlyFlag
        {
            get
            {
                Debug.Assert(_state != Uninitialized); // Use this after EnsureCachesInitialized has been called.
 
                if (!EntryExists || IsBrokenLink)
                {
                    return false;
                }
 
#if TARGET_BROWSER || TARGET_WASI
                var mode = ((UnixFileMode)_fileCache.Mode & FileSystem.ValidUnixFileModes);
                bool isUserReadOnly = (mode & UnixFileMode.UserRead) != 0 && // has read permission
                                      (mode & UnixFileMode.UserWrite) == 0;  // but not write permission
                return isUserReadOnly;
#else
                if (_isReadOnlyCache == 0)
                {
                    return false;
                }
                else if (_isReadOnlyCache == 1)
                {
                    return true;
                }
                else
                {
                    bool isReadOnly = IsModeReadOnlyCore();
                    _isReadOnlyCache = isReadOnly ? 1 : 0;
                    return isReadOnly;
                }
#endif
            }
        }
 
#if !TARGET_BROWSER && !TARGET_WASI
        // HasReadOnlyFlag cache.
        // Must only be used after calling EnsureCachesInitialized.
        private int _isReadOnlyCache;
 
        private bool IsModeReadOnlyCore()
        {
            var mode = ((UnixFileMode)_fileCache.Mode & FileSystem.ValidUnixFileModes);
 
            bool isUserReadOnly = (mode & UnixFileMode.UserRead) != 0 &&    // has read permission
                                  (mode & UnixFileMode.UserWrite) == 0;     // but not write permission
            bool isGroupReadOnly = (mode & UnixFileMode.GroupRead) != 0 &&  // has read permission
                                   (mode & UnixFileMode.GroupWrite) == 0;   // but not write permission
            bool isOtherReadOnly = (mode & UnixFileMode.OtherRead) != 0 &&  // has read permission
                                   (mode & UnixFileMode.OtherWrite) == 0;   // but not write permission
 
            // If they are all the same, no need to check user/group.
            if ((isUserReadOnly == isGroupReadOnly) && (isGroupReadOnly == isOtherReadOnly))
            {
                return isUserReadOnly;
            }
 
            if (_fileCache.Uid == Interop.Sys.GetEUid())
            {
                // User owns the file.
                return isUserReadOnly;
            }
 
            // System files often have the same permissions for group and other (umask 022).
            if (isGroupReadOnly == isUserReadOnly)
            {
                return isGroupReadOnly;
            }
 
            if (Interop.Sys.IsMemberOfGroup(_fileCache.Gid))
            {
                // User belongs to group that owns the file.
                return isGroupReadOnly;
            }
            else
            {
                // Other permissions.
                return isOtherReadOnly;
            }
        }
#endif
 
        // Checks if the main path is a symbolic link
        private bool HasSymbolicLinkFlag
        {
            get
            {
                Debug.Assert(_state != Uninitialized); // Use this after EnsureCachesInitialized has been called.
 
                return EntryExists && (_fileCache.Mode & Interop.Sys.FileTypes.S_IFMT) == Interop.Sys.FileTypes.S_IFLNK;
            }
        }
 
        // Sets the cache initialization flags to 0, which means the caches are now uninitialized
        internal void InvalidateCaches()
        {
            _state = Uninitialized;
        }
 
        internal bool IsReadOnly(ReadOnlySpan<char> path, bool continueOnError = false)
        {
            EnsureCachesInitialized(path, continueOnError);
            return HasReadOnlyFlag;
        }
 
        internal bool IsFileSystemEntryHidden(ReadOnlySpan<char> path, ReadOnlySpan<char> fileName)
        {
            // Because this is called for FileSystemEntry we can assume the entry exists and
            // avoid initialization in some cases.
            if (IsNameHidden(fileName))
            {
                return true;
            }
            if (!Interop.Sys.SupportsHiddenFlag)
            {
                return false;
            }
 
            EnsureCachesInitialized(path, continueOnError: true);
            return HasHiddenFlag;
        }
 
        internal static bool IsNameHidden(ReadOnlySpan<char> fileName) => fileName.Length > 0 && fileName[0] == '.';
 
        // Returns true if the path points to a directory, or if the path is a symbolic link
        // that points to a directory
        internal bool IsDirectory(ReadOnlySpan<char> path, bool continueOnError = false)
        {
            EnsureCachesInitialized(path, continueOnError);
            return IsDir;
        }
 
        internal bool IsSymbolicLink(ReadOnlySpan<char> path, bool continueOnError = false)
        {
            EnsureCachesInitialized(path, continueOnError);
            return HasSymbolicLinkFlag;
        }
 
        internal FileAttributes GetAttributes(ReadOnlySpan<char> path, ReadOnlySpan<char> fileName, bool continueOnError = false)
            => GetAttributes(handle: null, path, fileName, continueOnError);
 
        internal FileAttributes GetAttributes(SafeFileHandle handle, bool continueOnError = false)
            => GetAttributes(handle, handle.Path, Path.GetFileName(handle.Path), continueOnError);
 
        private FileAttributes GetAttributes(SafeFileHandle? handle, ReadOnlySpan<char> path, ReadOnlySpan<char> fileName, bool continueOnError = false)
        {
            Debug.Assert(handle is not null || path.Length > 0);
            EnsureCachesInitialized(handle, path, continueOnError);
 
            if (!EntryExists)
                return (FileAttributes)(-1);
 
            FileAttributes attributes = default;
 
            if (HasReadOnlyFlag)
                attributes |= FileAttributes.ReadOnly;
 
            if (HasSymbolicLinkFlag)
                attributes |= FileAttributes.ReparsePoint;
 
            if (IsDir) // Refresh caches this
                attributes |= FileAttributes.Directory;
 
            if (IsNameHidden(fileName) || HasHiddenFlag)
                attributes |= FileAttributes.Hidden;
 
            return attributes != default ? attributes : FileAttributes.Normal;
        }
 
        internal void SetAttributes(string path, FileAttributes attributes, bool asDirectory)
            => SetAttributes(handle: null, path, attributes, asDirectory);
 
        internal void SetAttributes(SafeFileHandle handle, FileAttributes attributes, bool asDirectory)
            => SetAttributes(handle, handle.Path, attributes, asDirectory);
 
        private void SetAttributes(SafeFileHandle? handle, string? path, FileAttributes attributes, bool asDirectory)
        {
            // Validate that only flags from the attribute are being provided.  This is an
            // approximation for the validation done by the Win32 function.
            const FileAttributes allValidFlags =
                FileAttributes.Archive | FileAttributes.Compressed | FileAttributes.Device |
                FileAttributes.Directory | FileAttributes.Encrypted | FileAttributes.Hidden |
                FileAttributes.IntegrityStream | FileAttributes.Normal | FileAttributes.NoScrubData |
                FileAttributes.NotContentIndexed | FileAttributes.Offline | FileAttributes.ReadOnly |
                FileAttributes.ReparsePoint | FileAttributes.SparseFile | FileAttributes.System |
                FileAttributes.Temporary;
            if ((attributes & ~allValidFlags) != 0)
            {
                // Using constant string for argument to match historical throw
                throw new ArgumentException(SR.Arg_InvalidFileAttrs, "Attributes");
            }
 
            EnsureCachesInitialized(handle, path);
 
            if (!EntryExists)
                ThrowNotFound(path);
 
            if (Interop.Sys.CanSetHiddenFlag)
            {
                bool hidden = (attributes & FileAttributes.Hidden) != 0;
                if (hidden ^ HasHiddenFlag)
                {
                    uint flags = hidden ? _fileCache.UserFlags | (uint)Interop.Sys.UserFlags.UF_HIDDEN :
                                          _fileCache.UserFlags & ~(uint)Interop.Sys.UserFlags.UF_HIDDEN;
                    int rv = handle is not null ? Interop.Sys.FChflags(handle, flags) :
                                                  Interop.Sys.LChflags(path!, flags);
                    Interop.CheckIo(rv, path, asDirectory);
                }
            }
 
            // The only thing we can reasonably change is whether the file object is readonly by changing permissions.
 
            int oldMode = _fileCache.Mode & (int)FileSystem.ValidUnixFileModes;
            int newMode = oldMode;
            if ((attributes & FileAttributes.ReadOnly) != 0)
            {
                // Take away all write permissions from user/group/everyone
                newMode &= ~(int)(UnixFileMode.UserWrite | UnixFileMode.GroupWrite | UnixFileMode.OtherWrite);
            }
            else if ((newMode & (int)UnixFileMode.UserRead) != 0)
            {
                // Give write permission to the owner if the owner has read permission
                newMode |= (int)UnixFileMode.UserWrite;
            }
 
            // Change the permissions on the file
            if (newMode != oldMode)
            {
                int rv = handle is not null ? Interop.Sys.FChMod(handle, newMode) :
                                              Interop.Sys.ChMod(path!, newMode);
                Interop.CheckIo(rv, path, asDirectory);
            }
 
            InvalidateCaches();
        }
 
        internal bool GetExists(ReadOnlySpan<char> path, bool asDirectory)
        {
            EnsureCachesInitialized(path, continueOnError: true);
            return EntryExists && asDirectory == IsDir;
        }
 
        internal DateTimeOffset GetCreationTime(ReadOnlySpan<char> path, bool continueOnError = false)
            => GetCreationTime(handle: null, path, continueOnError);
 
        internal DateTimeOffset GetCreationTime(SafeFileHandle handle, bool continueOnError = false)
            => GetCreationTime(handle, handle.Path, continueOnError);
 
        private DateTimeOffset GetCreationTime(SafeFileHandle? handle, ReadOnlySpan<char> path, bool continueOnError = false)
        {
            EnsureCachesInitialized(handle, path, continueOnError);
 
            if (!EntryExists)
                return new DateTimeOffset(DateTime.FromFileTimeUtc(0));
 
            if ((_fileCache.Flags & Interop.Sys.FileStatusFlags.HasBirthTime) != 0)
                return UnixTimeToDateTimeOffset(_fileCache.BirthTime, _fileCache.BirthTimeNsec);
 
            // fall back to the oldest time we have in between change and modify time
            if (_fileCache.MTime < _fileCache.CTime ||
                (_fileCache.MTime == _fileCache.CTime && _fileCache.MTimeNsec < _fileCache.CTimeNsec))
                return UnixTimeToDateTimeOffset(_fileCache.MTime, _fileCache.MTimeNsec);
 
            return UnixTimeToDateTimeOffset(_fileCache.CTime, _fileCache.CTimeNsec);
        }
 
        internal DateTimeOffset GetLastAccessTime(ReadOnlySpan<char> path, bool continueOnError = false)
            => GetLastAccessTime(handle: null, path, continueOnError);
 
        internal DateTimeOffset GetLastAccessTime(SafeFileHandle handle, bool continueOnError = false)
            => GetLastAccessTime(handle, handle.Path, continueOnError);
 
        private DateTimeOffset GetLastAccessTime(SafeFileHandle? handle, ReadOnlySpan<char> path, bool continueOnError = false)
        {
            EnsureCachesInitialized(handle, path, continueOnError);
 
            if (!EntryExists)
                return new DateTimeOffset(DateTime.FromFileTimeUtc(0));
 
            return UnixTimeToDateTimeOffset(_fileCache.ATime, _fileCache.ATimeNsec);
        }
 
        internal void SetLastAccessTime(string path, DateTimeOffset time, bool asDirectory)
            => SetLastAccessTime(handle: null, path, time, asDirectory);
 
        internal void SetLastAccessTime(SafeFileHandle handle, DateTimeOffset time, bool asDirectory)
            => SetLastAccessTime(handle, handle.Path, time, asDirectory);
 
        private void SetLastAccessTime(SafeFileHandle? handle, string? path, DateTimeOffset time, bool asDirectory)
            => SetAccessOrWriteTime(handle, path, time, isAccessTime: true, asDirectory);
 
        internal DateTimeOffset GetLastWriteTime(ReadOnlySpan<char> path, bool continueOnError = false)
            => GetLastWriteTime(handle: null, path, continueOnError);
 
        internal DateTimeOffset GetLastWriteTime(SafeFileHandle handle, bool continueOnError = false)
            => GetLastWriteTime(handle, handle.Path, continueOnError);
 
        private DateTimeOffset GetLastWriteTime(SafeFileHandle? handle, ReadOnlySpan<char> path, bool continueOnError = false)
        {
            EnsureCachesInitialized(handle, path, continueOnError);
 
            if (!EntryExists)
                return new DateTimeOffset(DateTime.FromFileTimeUtc(0));
 
            return UnixTimeToDateTimeOffset(_fileCache.MTime, _fileCache.MTimeNsec);
        }
 
        internal void SetLastWriteTime(string path, DateTimeOffset time, bool asDirectory)
            => SetLastWriteTime(handle: null, path, time, asDirectory);
 
        internal void SetLastWriteTime(SafeFileHandle handle, DateTimeOffset time, bool asDirectory)
            => SetLastWriteTime(handle, handle.Path, time, asDirectory);
 
        internal void SetLastWriteTime(SafeFileHandle? handle, string? path, DateTimeOffset time, bool asDirectory)
            => SetAccessOrWriteTime(handle, path, time, isAccessTime: false, asDirectory);
 
        private static DateTimeOffset UnixTimeToDateTimeOffset(long seconds, long nanoseconds)
        {
            return DateTimeOffset.FromUnixTimeSeconds(seconds).AddTicks(nanoseconds / NanosecondsPerTick);
        }
 
        private unsafe void SetAccessOrWriteTimeCore(SafeFileHandle? handle, string? path, DateTimeOffset time, bool isAccessTime, bool checkCreationTime, bool asDirectory)
        {
            // This api is used to set creation time on non OSX platforms, and as a fallback for OSX platforms.
            // The reason why we use it to set 'creation time' is the below comment:
            // Unix provides APIs to update the last access time (atime) and last modification time (mtime).
            // There is no API to update the CreationTime.
            // Some platforms (e.g. Linux) don't store a creation time. On those platforms, the creation time
            // is synthesized as the oldest of last status change time (ctime) and last modification time (mtime).
            // We update the LastWriteTime (mtime).
            // This triggers a metadata change for FileSystemWatcher NotifyFilters.CreationTime.
            // Updating the mtime, causes the ctime to be set to 'now'. So, on platforms that don't store a
            // CreationTime, GetCreationTime will return the value that was previously set (when that value
            // wasn't in the future).
 
            // force a refresh so that we have an up-to-date times for values not being overwritten
            InvalidateCaches();
            EnsureCachesInitialized(handle, path);
 
            if (!EntryExists)
                ThrowNotFound(path);
 
            // we use utimes()/utimensat() to set the accessTime and writeTime
            Interop.Sys.TimeSpec* buf = stackalloc Interop.Sys.TimeSpec[2];
 
            long seconds = time.ToUnixTimeSeconds();
            long nanoseconds = UnixTimeSecondsToNanoseconds(time, seconds);
 
#if TARGET_BROWSER || TARGET_WASI
            buf[0].TvSec = seconds;
            buf[0].TvNsec = nanoseconds;
            buf[1].TvSec = seconds;
            buf[1].TvNsec = nanoseconds;
#else
            if (isAccessTime)
            {
                buf[0].TvSec = seconds;
                buf[0].TvNsec = nanoseconds;
                buf[1].TvSec = _fileCache.MTime;
                buf[1].TvNsec = _fileCache.MTimeNsec;
            }
            else
            {
                buf[0].TvSec = _fileCache.ATime;
                buf[0].TvNsec = _fileCache.ATimeNsec;
                buf[1].TvSec = seconds;
                buf[1].TvNsec = nanoseconds;
            }
#endif
            int rv = handle is not null
                ? Interop.Sys.FUTimens(handle, buf)
                : Interop.Sys.UTimensat(path!, buf);
            Interop.CheckIo(rv, path, asDirectory);
 
            // On OSX-like platforms, when the modification time is less than the creation time (including
            // when the modification time is already less than but access time is being set), the creation
            // time is set to the modification time due to the api we're currently using; this is not
            // desirable behaviour since it is inconsistent with windows behaviour and is not logical to
            // the programmer (ie. we'd have to document it), so these api calls revert the creation time
            // when it shouldn't be set (since we're setting modification time and access time here).
            // checkCreationTime is only true on OSX-like platforms.
            // allowFallbackToLastWriteTime is ignored on non OSX-like platforms.
            bool updateCreationTime = checkCreationTime && (_fileCache.Flags & Interop.Sys.FileStatusFlags.HasBirthTime) != 0 &&
                                        (buf[1].TvSec < _fileCache.BirthTime || (buf[1].TvSec == _fileCache.BirthTime && buf[1].TvNsec < _fileCache.BirthTimeNsec));
 
            InvalidateCaches();
 
            if (updateCreationTime)
            {
                Interop.Error error = SetCreationTimeCore(handle, path, _fileCache.BirthTime, _fileCache.BirthTimeNsec);
                if (error != Interop.Error.SUCCESS && error != Interop.Error.ENOTSUP)
                {
                    Interop.CheckIo(error, path, asDirectory);
                }
            }
        }
 
        internal long GetLength(ReadOnlySpan<char> path, bool continueOnError = false)
        {
            // For symbolic links, on Windows, Length returns zero and not the target file size.
            // On Unix, it returns the length of the path stored in the link.
 
            EnsureCachesInitialized(path, continueOnError);
            return EntryExists ? _fileCache.Size : 0;
        }
 
        internal UnixFileMode GetUnixFileMode(ReadOnlySpan<char> path, bool continueOnError = false)
            => GetUnixFileMode(handle: null, path, continueOnError);
 
        internal UnixFileMode GetUnixFileMode(SafeFileHandle handle, bool continueOnError = false)
            => GetUnixFileMode(handle, handle.Path, continueOnError);
 
        private UnixFileMode GetUnixFileMode(SafeFileHandle? handle, ReadOnlySpan<char> path, bool continueOnError = false)
        {
            EnsureCachesInitialized(handle, path, continueOnError);
 
            if (!EntryExists || IsBrokenLink)
                return (UnixFileMode)(-1);
 
            return (UnixFileMode)(_fileCache.Mode & (int)FileSystem.ValidUnixFileModes);
        }
 
        internal void SetUnixFileMode(string path, UnixFileMode mode)
            => SetUnixFileMode(handle: null, path, mode);
 
        internal void SetUnixFileMode(SafeFileHandle handle, UnixFileMode mode)
            => SetUnixFileMode(handle, handle.Path, mode);
 
        private void SetUnixFileMode(SafeFileHandle? handle, string? path, UnixFileMode mode)
        {
            if ((mode & ~FileSystem.ValidUnixFileModes) != 0)
            {
                throw new ArgumentException(SR.Arg_InvalidUnixFileMode, nameof(UnixFileMode));
            }
 
            // Linux does not support link permissions.
            // To have consistent cross-platform behavior we operate on the link target.
            int rv = handle is not null ? Interop.Sys.FChMod(handle, (int)mode)
                                        : Interop.Sys.ChMod(path!, (int)mode);
            Interop.CheckIo(rv, path);
 
            InvalidateCaches();
        }
 
        internal void RefreshCaches(ReadOnlySpan<char> path)
            => RefreshCaches(handle: null, path);
 
        // Tries to refresh the lstat cache (_fileCache).
        // This method should not throw. Instead, we store the results, and we will throw when the user attempts to access any of the properties when there was a failure
        internal void RefreshCaches(SafeFileHandle? handle, ReadOnlySpan<char> path)
        {
            Debug.Assert(handle is not null || path.Length > 0);
 
#if !TARGET_BROWSER && !TARGET_WASI
            _isReadOnlyCache = -1;
#endif
            int rv = handle is not null ?
                Interop.Sys.FStat(handle, out _fileCache) :
                Interop.Sys.LStat(Path.TrimEndingDirectorySeparator(path), out _fileCache);
 
            if (rv < 0)
            {
                Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo();
 
                switch (errorInfo.Error)
                {
                    case Interop.Error.ENOENT:
                        _state = InitializedNotExists;
                        break;
                    case Interop.Error.ENOTDIR:
                        _state = InitializedNotExistsNotADir;
                        break;
                    default:
                        Debug.Assert(errorInfo.RawErrno > 0); // Expect a positive integer
                        _state = errorInfo.RawErrno; // Initialized with error.
                        break;
                }
 
                return;
            }
 
            // Check if the main path is a directory, or a link to a directory.
            int fileType = _fileCache.Mode & Interop.Sys.FileTypes.S_IFMT;
            bool isDirectory = fileType == Interop.Sys.FileTypes.S_IFDIR;
 
            if (fileType == Interop.Sys.FileTypes.S_IFLNK)
            {
                if (Interop.Sys.Stat(path, out Interop.Sys.FileStatus target) == 0)
                {
                    isDirectory = (target.Mode & Interop.Sys.FileTypes.S_IFMT) == Interop.Sys.FileTypes.S_IFDIR;
 
                    // Make GetUnixFileMode return target permissions.
                    _fileCache.Mode = Interop.Sys.FileTypes.S_IFLNK | (target.Mode & (int)FileSystem.ValidUnixFileModes);
                }
                else
                {
                    _state = InitializedExistsBrokenLink;
                    return;
                }
            }
 
            _state = isDirectory ? InitializedExistsDir : InitializedExistsFile;
        }
 
        internal void EnsureCachesInitialized(ReadOnlySpan<char> path, bool continueOnError = false)
            => EnsureCachesInitialized(handle: null, path, continueOnError);
 
        // Checks if the file cache is uninitialized and refreshes it's value.
        // If it failed, and continueOnError is set to true, this method will throw.
        internal void EnsureCachesInitialized(SafeFileHandle? handle, ReadOnlySpan<char> path, bool continueOnError = false)
        {
            if (_state == Uninitialized)
            {
                RefreshCaches(handle, path);
            }
 
            if (!continueOnError)
            {
                ThrowOnCacheInitializationError(path);
            }
        }
 
        // Throws if any of the caches has an error number saved in it
        private void ThrowOnCacheInitializationError(ReadOnlySpan<char> path)
        {
            // Lstat should always be initialized by Refresh
            int errno = _state;
            if (errno > 0)
            {
                InvalidateCaches();
                throw Interop.GetExceptionForIoErrno(new Interop.ErrorInfo(errno), new string(path));
            }
        }
 
        private static long UnixTimeSecondsToNanoseconds(DateTimeOffset time, long seconds)
        {
            const long TicksPerMillisecond = 10000;
            const long TicksPerSecond = TicksPerMillisecond * 1000;
            return (time.UtcDateTime.Ticks - DateTimeOffset.UnixEpoch.Ticks - seconds * TicksPerSecond) * NanosecondsPerTick;
        }
 
        private void ThrowNotFound(string? path)
        {
            Interop.Error error = _state == InitializedNotExistsNotADir ? Interop.Error.ENOTDIR : Interop.Error.ENOENT;
            throw Interop.GetExceptionForIoErrno(new Interop.ErrorInfo(error), path);
        }
    }
}