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
4 changes: 4 additions & 0 deletions Src/IronPython.Modules/nt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2326,6 +2326,10 @@ private static string GetWin32ErrorMessage(int errorCode) {

[SupportedOSPlatform("windows")]
internal static Exception GetWin32Error(int winerror, string? filename = null, string? filename2 = null) {
// Unwrap FACILITY_WIN32 HRESULT errors
if ((winerror & 0xFFFF0000) == 0x80070000) {
winerror &= 0x0000FFFF;
}
var msg = GetWin32ErrorMessage(winerror);
return PythonOps.OSError(0, msg, filename, winerror, filename2);
}
Expand Down
12 changes: 7 additions & 5 deletions Src/IronPython/Modules/_fileio.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@
using IronPython.Runtime.Exceptions;
using IronPython.Runtime.Operations;
using IronPython.Runtime.Types;
using PythonErrno = IronPython.Runtime.Exceptions.PythonExceptions._OSError.Errno;

using Microsoft.Scripting;
using Microsoft.Scripting.Runtime;
using Mono.Unix.Native;

#nullable enable

Expand Down Expand Up @@ -179,7 +181,7 @@ public FileIO(CodeContext/*!*/ context, [NotNone] string name, [NotNone] string
// In such case:
// _streams = new(new UnixStream(fd, ownsHandle: true))
// _context.FileManager.Add(fd, _streams);
throw PythonOps.OSError(PythonFileManager.EBADF, "Bad file descriptor");
throw PythonOps.OSError(PythonErrno.EBADF, "Bad file descriptor");
}
} else {
throw PythonOps.TypeError("expected integer from opener");
Expand Down Expand Up @@ -450,7 +452,7 @@ public override BigInteger seek(CodeContext/*!*/ context, BigInteger offset, [Op

var origin = (SeekOrigin)GetInt(whence);
if (origin < SeekOrigin.Begin || origin > SeekOrigin.End)
throw PythonOps.OSError(PythonFileManager.EINVAL, "Invalid argument");
throw PythonOps.OSError(PythonErrno.EINVAL, "Invalid argument");

long ofs = checked((long)offset);

Expand Down Expand Up @@ -584,13 +586,13 @@ private static void AddFilename(CodeContext context, string name, Exception ioe)


private static Stream OpenFile(CodeContext/*!*/ context, PlatformAdaptationLayer pal, string name, FileMode fileMode, FileAccess fileAccess, FileShare fileShare) {
if (string.IsNullOrWhiteSpace(name)) throw PythonOps.OSError(PythonFileManager.ENOENT, "No such file or directory", filename: name);
if (string.IsNullOrWhiteSpace(name)) throw PythonOps.OSError(PythonErrno.ENOENT, "No such file or directory", filename: name);
try {
return pal.OpenFileStream(name, fileMode, fileAccess, fileShare, 1); // Use a 1 byte buffer size to disable buffering (if the FileStream implementation supports it).
} catch (UnauthorizedAccessException) {
throw PythonOps.OSError(PythonFileManager.EACCES, "Permission denied", name);
throw PythonOps.OSError(PythonErrno.EACCES, "Permission denied", name);
} catch (FileNotFoundException) {
throw PythonOps.OSError(PythonFileManager.ENOENT, "No such file or directory", name);
throw PythonOps.OSError(PythonErrno.ENOENT, "No such file or directory", name);
} catch (IOException e) {
AddFilename(context, name, e);
throw;
Expand Down
134 changes: 107 additions & 27 deletions Src/IronPython/Runtime/Exceptions/PythonExceptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
using IronPython.Runtime.Operations;
using IronPython.Runtime.Types;


#if !FEATURE_REMOTING
using MarshalByRefObject = System.Object;
#endif
Expand All @@ -36,7 +37,7 @@ namespace IronPython.Runtime.Exceptions {
/// Because the oddity of the built-in exception types all sharing the same physical layout
/// (see also PythonExceptions.BaseException) some classes are defined as classes w/ their
/// proper name and some classes are defined as PythonType fields. When a class is defined
/// for convenience their's also an _TypeName version which is the PythonType.
/// for convenience there's also an _TypeName version which is the PythonType.
/// </summary>
public static partial class PythonExceptions {
private static readonly object _pythonExceptionKey = typeof(BaseException);
Expand Down Expand Up @@ -124,9 +125,9 @@ public partial class _OSError {
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
if (args.Length >= 4 && args[3] is int winerror) {
errno = WinErrorToErrno(winerror);
}
}
}
cls = ErrnoToPythonType(ErrnoToErrorEnum(errno));
cls = ErrorEnumToPythonType(ErrnoToErrorEnum(errno));
}
return Activator.CreateInstance(cls.UnderlyingSystemType, cls);
}
Expand Down Expand Up @@ -179,6 +180,13 @@ public override void __init__(params object[] args) {
base.__init__(args);
}

// This enum is used solely for the purpose of mapping errno values to Python exception types.
// The values are based on errno codes but do not exactly match them;
// they are selected such that it is possible to algorithmically map them from true platform-dependent errno values.
// The subset of codes is chosen that is sufficient for mapping all relevant Python exceptions.
// Because it is an enum, it can be used in switch statements and expressions, simplifying the code
// over using actual errno values (which are not always compile-time constants) while keeping it readable.
// In a way it is subset-equivalent to Mono.Unix.Native.Errno, but it is not dependent on Mono.Posix assembly.
private enum Error {
UNSPECIFIED = -1,
EPERM = 1,
Expand Down Expand Up @@ -211,15 +219,17 @@ private enum Error {
WSAECONNREFUSED = 10061,
}

// Not all input errno values are mapped to existing constants of Error.
// This is suffcient since all values that are not listed as Error constants are mapped to OSError.
private static Error ErrnoToErrorEnum(int errno) {
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) {
if (errno == 11) return Error.UNSPECIFIED; // EAGAIN on Linux/Windows but EDEADLK on OSX, which is not being remapped
if (errno >= 35) errno += 10000; // add WSABASEERR to map to Windows error range
if (errno >= 35) errno += WSABASEERR; // add WSABASEERR to map to Windows error range
}
return (Error)errno;
}

private static PythonType ErrnoToPythonType(Error errno) {
private static PythonType ErrorEnumToPythonType(Error errno) {
var res = errno switch {
Error.EPERM => PermissionError,
Error.ENOENT => FileNotFoundError,
Expand Down Expand Up @@ -263,6 +273,42 @@ private static PythonType ErrnoToPythonType(Error errno) {
return res ?? OSError;
}

/// <summary>
/// Provides a subset of platform-independent errno codes to be used in this assembly.
/// </summary>
/// <remarks>
/// Values of the Errno codes defined here are identical with values defined in PythonErrno in IronPython.Modules.dll.
/// </remarks>
internal static class Errno {

#region Generated Common Errno Codes

// *** BEGIN GENERATED CODE ***
// generated by function: generate_common_errno_codes from: generate_os_codes.py

internal const int ENOENT = 2;
internal const int E2BIG = 7;
internal const int ENOEXEC = 8;
internal const int EBADF = 9;
internal const int ECHILD = 10;
internal static int EAGAIN => RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? 11 : RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 35 : 11;
internal const int ENOMEM = 12;
internal const int EACCES = 13;
internal const int EEXIST = 17;
internal const int EXDEV = 18;
internal const int ENOTDIR = 20;
internal const int EMFILE = 24;
internal const int ENOSPC = 28;
internal const int EPIPE = 32;
internal static int ENOTEMPTY => RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? 41 : RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 66 : 39;
internal static int EILSEQ => RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? 42 : RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 92 : 84;
internal const int EINVAL = 22;

// *** END GENERATED CODE ***

#endregion
}

/*
* errors were generated using this script run against CPython:
f = open(r'C:\Program Files\Microsoft SDKs\Windows\v6.0A\Include\WinError.h', 'r')
Expand Down Expand Up @@ -373,6 +419,7 @@ private static PythonType ErrnoToPythonType(Error errno) {
internal const int ERROR_NESTING_NOT_ALLOWED = 215;
internal const int ERROR_NO_DATA = 232;
internal const int ERROR_DIRECTORY = 267;
internal const int ERROR_NO_UNICODE_TRANSLATION = 1113;
internal const int ERROR_NOT_ENOUGH_QUOTA = 1816;

// These map to POSIX errno 22 and are added by hand as needed.
Expand All @@ -381,14 +428,25 @@ private static PythonType ErrnoToPythonType(Error errno) {
internal const int ERROR_FILE_INVALID = 1006;
internal const int ERROR_MAPPED_ALIGNMENT = 1132;

// Some Winsock error codes are errno values.
internal const int WSABASEERR = 10000;
internal const int WSAEINTR = WSABASEERR + 4;
internal const int WSAEBADF = WSABASEERR + 9;
internal const int WSAEACCES = WSABASEERR + 13;
internal const int WSAEFAULT = WSABASEERR + 14;
internal const int WSAEINVAL = WSABASEERR + 22;
internal const int WSAEMFILE = WSABASEERR + 24;

// See also errmap.h in CPython
internal static int WinErrorToErrno(int winerror) {
// Unwrap FACILITY_WIN32 HRESULT errors
if ((winerror & 0xFFFF0000) == 0x80070000) {
winerror &= 0x0000FFFF;
}

int errno = winerror;
if (winerror < 10000) {
if (winerror < WSABASEERR) {
switch (winerror) {
case ERROR_BROKEN_PIPE:
case ERROR_NO_DATA:
errno = 32;
break;
case ERROR_FILE_NOT_FOUND:
case ERROR_PATH_NOT_FOUND:
case ERROR_INVALID_DRIVE:
Expand All @@ -397,10 +455,10 @@ internal static int WinErrorToErrno(int winerror) {
case ERROR_BAD_NET_NAME:
case ERROR_BAD_PATHNAME:
case ERROR_FILENAME_EXCED_RANGE:
errno = 2;
errno = Errno.ENOENT;
break;
case ERROR_BAD_ENVIRONMENT:
errno = 7;
errno = Errno.E2BIG;
break;
case ERROR_BAD_FORMAT:
case ERROR_INVALID_STARTING_CODESEG:
Expand All @@ -418,27 +476,27 @@ internal static int WinErrorToErrno(int winerror) {
case ERROR_RING2SEG_MUST_BE_MOVABLE:
case ERROR_RELOC_CHAIN_XEEDS_SEGLIM:
case ERROR_INFLOOP_IN_RELOC_CHAIN:
errno = 8;
errno = Errno.ENOEXEC;
break;
case ERROR_INVALID_HANDLE:
case ERROR_INVALID_TARGET_HANDLE:
case ERROR_DIRECT_ACCESS_HANDLE:
errno = 9;
errno = Errno.EBADF;
break;
case ERROR_WAIT_NO_CHILDREN:
case ERROR_CHILD_NOT_COMPLETE:
errno = 10;
errno = Errno.ECHILD;
break;
case ERROR_NO_PROC_SLOTS:
case ERROR_MAX_THRDS_REACHED:
case ERROR_NESTING_NOT_ALLOWED:
errno = 11;
errno = Errno.EAGAIN;
break;
case ERROR_ARENA_TRASHED:
case ERROR_NOT_ENOUGH_MEMORY:
case ERROR_INVALID_BLOCK:
case ERROR_NOT_ENOUGH_QUOTA:
errno = 12;
errno = Errno.ENOMEM;
break;
case ERROR_ACCESS_DENIED:
case ERROR_CURRENT_DIRECTORY:
Expand Down Expand Up @@ -466,29 +524,51 @@ internal static int WinErrorToErrno(int winerror) {
case ERROR_SEEK_ON_DEVICE:
case ERROR_NOT_LOCKED:
case ERROR_LOCK_FAILED:
errno = 13;
case 35: // undefined
errno = Errno.EACCES;
break;
case ERROR_FILE_EXISTS:
case ERROR_ALREADY_EXISTS:
errno = 17;
errno = Errno.EEXIST;
break;
case ERROR_NOT_SAME_DEVICE:
errno = 18;
errno = Errno.EXDEV;
break;
case ERROR_DIRECTORY:
errno = 20;
break;
case ERROR_DIR_NOT_EMPTY:
errno = 41;
errno = Errno.ENOTDIR;
break;
case ERROR_TOO_MANY_OPEN_FILES:
errno = 24;
errno = Errno.EMFILE;
break;
case ERROR_DISK_FULL:
errno = 28;
errno = Errno.ENOSPC;
break;
case ERROR_BROKEN_PIPE:
case ERROR_NO_DATA:
errno = Errno.EPIPE;
break;
case ERROR_DIR_NOT_EMPTY: // ENOTEMPTY
errno = Errno.ENOTEMPTY;
break;
case ERROR_NO_UNICODE_TRANSLATION: // EILSEQ
errno = Errno.EILSEQ;
break;
default:
errno = Errno.EINVAL;
break;
}
} else if (winerror < 12000) { // Winsock error codes are 10000-11999
switch (winerror) {
case WSAEINTR:
case WSAEBADF:
case WSAEACCES:
case WSAEFAULT:
case WSAEINVAL:
case WSAEMFILE:
errno = winerror - WSABASEERR;
break;
default:
errno = 22;
errno = winerror;
break;
}
}
Expand Down
11 changes: 6 additions & 5 deletions Src/IronPython/Runtime/PosixFileStream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#endif

using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
Expand All @@ -15,8 +16,8 @@
using Mono.Unix.Native;

using IronPython.Runtime.Operations;
using System.Diagnostics;
using IronPython.Runtime.Exceptions;
using PythonErrno = IronPython.Runtime.Exceptions.PythonExceptions._OSError.Errno;

#nullable enable

Expand All @@ -39,7 +40,7 @@ public PosixFileStream(int fileDescriptor) {
throw new PlatformNotSupportedException("This stream only works on POSIX systems");

if (fileDescriptor < 0)
throw PythonOps.OSError(PythonFileManager.EBADF, "Bad file descriptor");
throw PythonOps.OSError(PythonErrno.EBADF, "Bad file descriptor");

_fd = fileDescriptor;

Expand Down Expand Up @@ -97,7 +98,7 @@ public override long Seek(long offset, SeekOrigin origin) {
SeekOrigin.Begin => SeekFlags.SEEK_SET,
SeekOrigin.Current => SeekFlags.SEEK_CUR,
SeekOrigin.End => SeekFlags.SEEK_END,
_ => throw PythonOps.OSError(PythonFileManager.EINVAL, "Invalid argument")
_ => throw PythonOps.OSError(PythonErrno.EINVAL, "Invalid argument")
};

long result = Syscall.lseek(_fd, offset, whence);
Expand Down Expand Up @@ -126,7 +127,7 @@ public override void SetLength(long value) {
public int Read(Span<byte> buffer) {
ThrowIfDisposed();
if (!CanRead)
throw PythonOps.OSError(PythonFileManager.EBADF, "Bad file descriptor");
throw PythonOps.OSError(PythonErrno.EBADF, "Bad file descriptor");


if (buffer.Length == 0)
Expand Down Expand Up @@ -169,7 +170,7 @@ public override int ReadByte() {
public void Write(ReadOnlySpan<byte> buffer) {
ThrowIfDisposed();
if (!CanWrite)
throw PythonOps.OSError(PythonFileManager.EBADF, "Bad file descriptor");
throw PythonOps.OSError(PythonErrno.EBADF, "Bad file descriptor");

if (buffer.Length == 0)
return;
Expand Down
Loading