Skip to content
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
53 changes: 31 additions & 22 deletions src/libraries/Common/src/Interop/Unix/Interop.IOErrors.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,19 @@

internal static partial class Interop
{
private static void ThrowExceptionForIoErrno(ErrorInfo errorInfo, string? path, bool isDirectory)
private static void ThrowExceptionForIoErrno(ErrorInfo errorInfo, string? path, bool isDirError)
{
Debug.Assert(errorInfo.Error != Error.SUCCESS);
Debug.Assert(errorInfo.Error != Error.EINTR, "EINTR errors should be handled by the native shim and never bubble up to managed code");

throw Interop.GetExceptionForIoErrno(errorInfo, path, isDirectory);
throw Interop.GetExceptionForIoErrno(errorInfo, path, isDirError);
}

internal static void CheckIo(Error error, string? path = null, bool isDirectory = false)
internal static void CheckIo(Error error, string? path = null, bool isDirError = false)
{
if (error != Interop.Error.SUCCESS)
{
ThrowExceptionForIoErrno(error.Info(), path, isDirectory);
ThrowExceptionForIoErrno(error.Info(), path, isDirError);
}
}

Expand All @@ -31,15 +31,15 @@ internal static void CheckIo(Error error, string? path = null, bool isDirectory
/// </summary>
/// <param name="result">The result of the system call.</param>
/// <param name="path">The path with which this error is associated. This may be null.</param>
/// <param name="isDirectory">true if the <paramref name="path"/> is known to be a directory; otherwise, false.</param>
/// <param name="isDirError">true if error is caused by a directory issue.</param>
/// <returns>
/// On success, returns the non-negative result long that was validated.
/// </returns>
internal static long CheckIo(long result, string? path = null, bool isDirectory = false)
internal static long CheckIo(long result, string? path = null, bool isDirError = false)
{
if (result < 0)
{
ThrowExceptionForIoErrno(Sys.GetLastErrorInfo(), path, isDirectory);
ThrowExceptionForIoErrno(Sys.GetLastErrorInfo(), path, isDirError);
}

return result;
Expand All @@ -53,9 +53,9 @@ internal static long CheckIo(long result, string? path = null, bool isDirectory
/// <returns>
/// On success, returns the non-negative result int that was validated.
/// </returns>
internal static int CheckIo(int result, string? path = null, bool isDirectory = false)
internal static int CheckIo(int result, string? path = null, bool isDirError = false)
{
CheckIo((long)result, path, isDirectory);
CheckIo((long)result, path, isDirError);

return result;
}
Expand All @@ -68,9 +68,9 @@ internal static int CheckIo(int result, string? path = null, bool isDirectory =
/// <returns>
/// On success, returns the non-negative result IntPtr that was validated.
/// </returns>
internal static IntPtr CheckIo(IntPtr result, string? path = null, bool isDirectory = false)
internal static IntPtr CheckIo(IntPtr result, string? path = null, bool isDirError = false)
{
CheckIo((long)result, path, isDirectory);
CheckIo((long)result, path, isDirError);

return result;
}
Expand All @@ -83,12 +83,12 @@ internal static IntPtr CheckIo(IntPtr result, string? path = null, bool isDirect
/// <returns>
/// On success, returns the valid SafeFileHandle that was validated.
/// </returns>
internal static TSafeHandle CheckIo<TSafeHandle>(TSafeHandle handle, string? path = null, bool isDirectory = false)
internal static TSafeHandle CheckIo<TSafeHandle>(TSafeHandle handle, string? path = null, bool isDirError = false)
where TSafeHandle : SafeHandle
{
if (handle.IsInvalid)
{
Exception e = Interop.GetExceptionForIoErrno(Sys.GetLastErrorInfo(), path, isDirectory);
Exception e = Interop.GetExceptionForIoErrno(Sys.GetLastErrorInfo(), path, isDirError);
handle.Dispose();
throw e;
}
Expand All @@ -101,27 +101,29 @@ internal static TSafeHandle CheckIo<TSafeHandle>(TSafeHandle handle, string? pat
/// </summary>
/// <param name="errorInfo">The error info</param>
/// <param name="path">The path with which this error is associated. This may be null.</param>
/// <param name="isDirectory">true if the <paramref name="path"/> is known to be a directory; otherwise, false.</param>
/// <param name="isDirError">true if error is caused by a directory issue.</param>
/// <returns></returns>
internal static Exception GetExceptionForIoErrno(ErrorInfo errorInfo, string? path = null, bool isDirectory = false)
internal static Exception GetExceptionForIoErrno(ErrorInfo errorInfo, string? path = null, bool isDirError = false)
{
// Translate the errno into a known set of exception types. For cases where multiple errnos map
// to the same exception type, include an inner exception with the details.
switch (errorInfo.Error)
{
case Error.ENOENT:
if (isDirectory)
{
return !string.IsNullOrEmpty(path) ?
new DirectoryNotFoundException(SR.Format(SR.IO_PathNotFound_Path, path)) :
new DirectoryNotFoundException(SR.IO_PathNotFound_NoPathName);
}
else
// For Windows compatibility, throw DirectoryNotFoundException instead of FileNotFoundException
// when the parent folder does not exist.
if (!isDirError && (path is null || ParentDirectoryExists(path)))
{
return !string.IsNullOrEmpty(path) ?
new FileNotFoundException(SR.Format(SR.IO_FileNotFound_FileName, path), path) :
new FileNotFoundException(SR.IO_FileNotFound);
}
goto case Error.ENOTDIR;

case Error.ENOTDIR:
return !string.IsNullOrEmpty(path) ?
new DirectoryNotFoundException(SR.Format(SR.IO_PathNotFound_Path, path)) :
new DirectoryNotFoundException(SR.IO_PathNotFound_NoPathName);

case Error.EACCES:
case Error.EBADF:
Expand Down Expand Up @@ -156,6 +158,13 @@ internal static Exception GetExceptionForIoErrno(ErrorInfo errorInfo, string? pa

default:
return GetIOException(errorInfo, path);

static bool ParentDirectoryExists(string fullPath)
{
string? parentPath = Path.GetDirectoryName(Path.TrimEndingDirectorySeparator(fullPath));

return Directory.Exists(parentPath);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ private void CheckStatfsResultAndThrowIfNecessary(int result)
}
else
{
throw Interop.GetExceptionForIoErrno(errorInfo, isDirectory: true);
throw Interop.GetExceptionForIoErrno(errorInfo);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -787,8 +787,7 @@ private bool TryReadEvent(out NotifyEvent notifyEvent)
{
fixed (byte* buf = &_buffer[0])
{
_bufferAvailable = Interop.CheckIo(Interop.Sys.Read(_inotifyHandle, buf, this._buffer.Length),
isDirectory: true);
_bufferAvailable = Interop.CheckIo(Interop.Sys.Read(_inotifyHandle, buf, this._buffer.Length));
Debug.Assert(_bufferAvailable <= this._buffer.Length);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ internal unsafe RunningInstance(
_fullDirectory = Interop.Sys.RealPath(_fullDirectory);
if (_fullDirectory is null)
{
throw Interop.GetExceptionForIoErrno(Interop.Sys.GetLastErrorInfo(), _fullDirectory, isDirectory: true);
throw Interop.GetExceptionForIoErrno(Interop.Sys.GetLastErrorInfo(), _fullDirectory, isDirError: true);
}

// Also ensure it has a trailing slash.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,43 +105,17 @@ private static SafeFileHandle Open(string path, Interop.Sys.OpenFlags flags, int
throw ex;
}

// 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(System.IO.Path.GetDirectoryName(System.IO.Path.TrimEndingDirectorySeparator(path!))!));

if (error.Error == Interop.Error.EISDIR)
{
error = Interop.Error.EACCES.Info();
}

Interop.CheckIo(
error.Error,
path,
isDirectory);
Interop.CheckIo(error.Error, path);
}

return handle;
}

private static bool DirectoryExists(string fullPath)
{
Interop.Sys.FileStatus fileinfo;

if (Interop.Sys.Stat(fullPath, out fileinfo) < 0)
{
return false;
}

return ((fileinfo.Mode & Interop.Sys.FileTypes.S_IFMT) == Interop.Sys.FileTypes.S_IFDIR);
}

// Each thread will have its own copy. This prevents race conditions if the handle had the last error.
[ThreadStatic]
internal static Interop.ErrorInfo? t_lastCloseErrorInfo;
Expand Down Expand Up @@ -336,7 +310,7 @@ private bool Init(string path, FileMode mode, FileAccess access, FileShare share

if ((status.Mode & Interop.Sys.FileTypes.S_IFMT) == Interop.Sys.FileTypes.S_IFDIR)
{
throw Interop.GetExceptionForIoErrno(Interop.Error.EACCES.Info(), path, isDirectory: true);
throw Interop.GetExceptionForIoErrno(Interop.Error.EACCES.Info(), path);
}

if ((status.Mode & Interop.Sys.FileTypes.S_IFMT) == Interop.Sys.FileTypes.S_IFREG)
Expand Down Expand Up @@ -367,7 +341,7 @@ private bool Init(string path, FileMode mode, FileAccess access, FileShare share
Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo();
if (errorInfo.Error == Interop.Error.EWOULDBLOCK)
{
throw Interop.GetExceptionForIoErrno(errorInfo, path, isDirectory: false);
throw Interop.GetExceptionForIoErrno(errorInfo, path);
}
}

Expand Down Expand Up @@ -434,7 +408,7 @@ private bool Init(string path, FileMode mode, FileAccess access, FileShare share
// We know the file descriptor is valid and we know the size argument to FTruncate is correct,
// so if EBADF or EINVAL is returned, it means we're dealing with a special file that can't be
// truncated. Ignore the error in such cases; in all others, throw.
throw Interop.GetExceptionForIoErrno(errorInfo, path, isDirectory: false);
throw Interop.GetExceptionForIoErrno(errorInfo, path);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public static partial class Environment
private static string CurrentDirectoryCore
{
get => Interop.Sys.GetCwd();
set => Interop.CheckIo(Interop.Sys.ChDir(value), value, isDirectory: true);
set => Interop.CheckIo(Interop.Sys.ChDir(value), value, isDirError: true);
}

private static string ExpandEnvironmentVariablesCore(string name)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ private IntPtr CreateDirectoryHandle(string path, bool ignoreNotFound = false)
{
return IntPtr.Zero;
}
throw Interop.GetExceptionForIoErrno(info, path, isDirectory: true);
throw Interop.GetExceptionForIoErrno(info, path, isDirError: true);
}
return handle;
}
Expand Down Expand Up @@ -198,7 +198,7 @@ private unsafe void FindNextEntry(byte* entryBufferPtr, int bufferLength)
}
else
{
throw Interop.GetExceptionForIoErrno(new Interop.ErrorInfo(result), _currentPath, isDirectory: true);
throw Interop.GetExceptionForIoErrno(new Interop.ErrorInfo(result), _currentPath, isDirError: true);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,15 @@ internal partial struct FileStatus
{
private const int NanosecondsPerTick = 100;

private const int InitializedExistsBrokenLink = -4; // target is link with no target.
private const int InitializedExistsDir = -3; // target is directory.
private const int InitializedExistsFile = -2; // target is file.
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 successfully. Value is InitializedNotExists, InitializedExistsFile, InitializedExistsDir or InitializedExistsBrokenLink.
// < 0 : initialized succesfully. Value is one of the Initialized* consts.
// 0 : uninitialized.
// > 0 : initialized with error. Value is raw errno.
private int _state;
Expand Down Expand Up @@ -240,7 +241,7 @@ private void SetAttributes(SafeFileHandle? handle, string? path, FileAttributes
EnsureCachesInitialized(handle, path);

if (!EntryExists)
FileSystemInfo.ThrowNotFound(path);
ThrowNotFound(path);

if (Interop.Sys.CanSetHiddenFlag)
{
Expand Down Expand Up @@ -385,7 +386,7 @@ private unsafe void SetAccessOrWriteTimeCore(SafeFileHandle? handle, string? pat
EnsureCachesInitialized(handle, path);

if (!EntryExists)
FileSystemInfo.ThrowNotFound(path);
ThrowNotFound(path);

// we use utimes()/utimensat() to set the accessTime and writeTime
Interop.Sys.TimeSpec* buf = stackalloc Interop.Sys.TimeSpec[2];
Expand Down Expand Up @@ -480,15 +481,6 @@ private void SetUnixFileMode(SafeFileHandle? handle, string? path, UnixFileMode
throw new ArgumentException(SR.Arg_InvalidUnixFileMode, nameof(UnixFileMode));
}

// Use ThrowNotFound to throw the appropriate exception when the file doesn't exist.
if (handle is null && path is not null)
{
EnsureCachesInitialized(path);

if (!EntryExists || IsBrokenLink)
FileSystemInfo.ThrowNotFound(path);
}

// 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)
Expand Down Expand Up @@ -518,15 +510,18 @@ internal void RefreshCaches(SafeFileHandle? handle, ReadOnlySpan<char> path)
{
Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo();

if (errorInfo.Error == Interop.Error.ENOENT || // A component of the path does not exist, or path is an empty string
errorInfo.Error == Interop.Error.ENOTDIR) // A component of the path prefix of path is not a directory
switch (errorInfo.Error)
{
_state = InitializedNotExists;
}
else
{
Debug.Assert(errorInfo.RawErrno > 0); // Expect a positive integer
_state = errorInfo.RawErrno; // Initialized with 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;
Expand Down Expand Up @@ -591,5 +586,11 @@ private static long UnixTimeSecondsToNanoseconds(DateTimeOffset time, long secon
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);
}
}
}
Loading