File: src\libraries\Common\src\Interop\Unix\Interop.IOErrors.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;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
 
internal static partial class Interop
{
    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, isDirError);
    }
 
    internal static void CheckIo(Error error, string? path = null, bool isDirError = false)
    {
        if (error != Interop.Error.SUCCESS)
        {
            ThrowExceptionForIoErrno(error.Info(), path, isDirError);
        }
    }
 
    /// <summary>
    /// Validates the result of system call that returns greater than or equal to 0 on success
    /// and less than 0 on failure, with errno set to the error code.
    /// If the system call failed for any reason, an exception is thrown. Otherwise, the system call succeeded.
    /// </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="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 isDirError = false)
    {
        if (result < 0)
        {
            ThrowExceptionForIoErrno(Sys.GetLastErrorInfo(), path, isDirError);
        }
 
        return result;
    }
 
    /// <summary>
    /// Throws an IOException using the last error info (errno).
    /// </summary>
    internal static void ThrowIOExceptionForLastError()
    {
        ThrowExceptionForIoErrno(Sys.GetLastErrorInfo(), path: null, isDirError: false);
    }
 
    /// <summary>
    /// Validates the result of system call that returns greater than or equal to 0 on success
    /// and less than 0 on failure, with errno set to the error code.
    /// If the system call failed for any reason, an exception is thrown. Otherwise, the system call succeeded.
    /// </summary>
    /// <returns>
    /// On success, returns the non-negative result int that was validated.
    /// </returns>
    internal static int CheckIo(int result, string? path = null, bool isDirError = false)
    {
        CheckIo((long)result, path, isDirError);
 
        return result;
    }
 
    /// <summary>
    /// Validates the result of system call that returns greater than or equal to 0 on success
    /// and less than 0 on failure, with errno set to the error code.
    /// If the system call failed for any reason, an exception is thrown. Otherwise, the system call succeeded.
    /// </summary>
    /// <returns>
    /// On success, returns the non-negative result IntPtr that was validated.
    /// </returns>
    internal static IntPtr CheckIo(IntPtr result, string? path = null, bool isDirError = false)
    {
        CheckIo((long)result, path, isDirError);
 
        return result;
    }
 
    /// <summary>
    /// Validates the result of system call that returns greater than or equal to 0 on success
    /// and less than 0 on failure, with errno set to the error code.
    /// If the system call failed for any reason, an exception is thrown. Otherwise, the system call succeeded.
    /// </summary>
    /// <returns>
    /// On success, returns the valid SafeFileHandle that was validated.
    /// </returns>
    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, isDirError);
            handle.Dispose();
            throw e;
        }
 
        return handle;
    }
 
    /// <summary>
    /// Gets an Exception to represent the supplied error info.
    /// </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="isDirError">true if error is caused by a directory issue.</param>
    /// <returns></returns>
    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:
                // 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:
            case Error.EPERM:
                Exception inner = GetIOException(errorInfo);
                return !string.IsNullOrEmpty(path) ?
                    new UnauthorizedAccessException(SR.Format(SR.UnauthorizedAccess_IODenied_Path, path), inner) :
                    new UnauthorizedAccessException(SR.UnauthorizedAccess_IODenied_NoPathName, inner);
 
            case Error.ENAMETOOLONG:
                return !string.IsNullOrEmpty(path) ?
                    new PathTooLongException(SR.Format(SR.IO_PathTooLong_Path, path)) :
                    new PathTooLongException(SR.IO_PathTooLong);
 
            case Error.EWOULDBLOCK:
                return !string.IsNullOrEmpty(path) ?
                    new IOException(SR.Format(SR.IO_SharingViolation_File, path), errorInfo.RawErrno) :
                    new IOException(SR.IO_SharingViolation_NoFileName, errorInfo.RawErrno);
 
            case Error.ECANCELED:
                return new OperationCanceledException();
 
            case Error.EFBIG:
                return new ArgumentOutOfRangeException("value", SR.ArgumentOutOfRange_FileLengthTooBig);
 
            case Error.EEXIST:
                if (!string.IsNullOrEmpty(path))
                {
                    return new IOException(SR.Format(SR.IO_FileExists_Name, path), errorInfo.RawErrno);
                }
                goto default;
 
            default:
                return GetIOException(errorInfo, path);
 
            static bool ParentDirectoryExists(string fullPath)
            {
                string? parentPath = Path.GetDirectoryName(Path.TrimEndingDirectorySeparator(fullPath));
 
                return Directory.Exists(parentPath);
            }
        }
    }
 
    internal static Exception GetIOException(Interop.ErrorInfo errorInfo, string? path = null)
    {
        string msg = errorInfo.GetErrorMessage();
        return new IOException(
            string.IsNullOrEmpty(path) ? msg : $"{msg} : '{path}'", errorInfo.RawErrno);
    }
}