diff --git a/Src/IronPython.Modules/nt.cs b/Src/IronPython.Modules/nt.cs
index b77b65a51..9746769d9 100644
--- a/Src/IronPython.Modules/nt.cs
+++ b/Src/IronPython.Modules/nt.cs
@@ -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);
}
diff --git a/Src/IronPython/Modules/_fileio.cs b/Src/IronPython/Modules/_fileio.cs
index 61dbcc600..203bcf2b9 100644
--- a/Src/IronPython/Modules/_fileio.cs
+++ b/Src/IronPython/Modules/_fileio.cs
@@ -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
@@ -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");
@@ -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);
@@ -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;
diff --git a/Src/IronPython/Runtime/Exceptions/PythonExceptions.cs b/Src/IronPython/Runtime/Exceptions/PythonExceptions.cs
index 9c48ded0b..566653c29 100644
--- a/Src/IronPython/Runtime/Exceptions/PythonExceptions.cs
+++ b/Src/IronPython/Runtime/Exceptions/PythonExceptions.cs
@@ -20,6 +20,7 @@
using IronPython.Runtime.Operations;
using IronPython.Runtime.Types;
+
#if !FEATURE_REMOTING
using MarshalByRefObject = System.Object;
#endif
@@ -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.
///
public static partial class PythonExceptions {
private static readonly object _pythonExceptionKey = typeof(BaseException);
@@ -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);
}
@@ -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,
@@ -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,
@@ -263,6 +273,42 @@ private static PythonType ErrnoToPythonType(Error errno) {
return res ?? OSError;
}
+ ///
+ /// Provides a subset of platform-independent errno codes to be used in this assembly.
+ ///
+ ///
+ /// Values of the Errno codes defined here are identical with values defined in PythonErrno in IronPython.Modules.dll.
+ ///
+ 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')
@@ -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.
@@ -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:
@@ -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:
@@ -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:
@@ -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;
}
}
diff --git a/Src/IronPython/Runtime/PosixFileStream.cs b/Src/IronPython/Runtime/PosixFileStream.cs
index 36a380b0a..d4b9e8a8a 100644
--- a/Src/IronPython/Runtime/PosixFileStream.cs
+++ b/Src/IronPython/Runtime/PosixFileStream.cs
@@ -7,6 +7,7 @@
#endif
using System;
+using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
@@ -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
@@ -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;
@@ -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);
@@ -126,7 +127,7 @@ public override void SetLength(long value) {
public int Read(Span buffer) {
ThrowIfDisposed();
if (!CanRead)
- throw PythonOps.OSError(PythonFileManager.EBADF, "Bad file descriptor");
+ throw PythonOps.OSError(PythonErrno.EBADF, "Bad file descriptor");
if (buffer.Length == 0)
@@ -169,7 +170,7 @@ public override int ReadByte() {
public void Write(ReadOnlySpan 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;
diff --git a/Src/IronPython/Runtime/PythonFileManager.cs b/Src/IronPython/Runtime/PythonFileManager.cs
index f1efa378f..20cdfb36c 100644
--- a/Src/IronPython/Runtime/PythonFileManager.cs
+++ b/Src/IronPython/Runtime/PythonFileManager.cs
@@ -16,9 +16,9 @@
using Microsoft.Scripting.Runtime;
using Microsoft.Scripting.Utils;
-using Mono.Unix;
using IronPython.Runtime.Operations;
+using PythonErrno = IronPython.Runtime.Exceptions.PythonExceptions._OSError.Errno;
namespace IronPython.Runtime {
@@ -214,25 +214,6 @@ internal class PythonFileManager {
///
public const int LIMIT_OFILE = 0x100000; // hard limit on Linux
-
- // Values of the Errno codes below have to be identical with values defined in PythonErrorNumber
-
- #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 EBADF = 9;
- internal const int EACCES = 13;
- internal const int EINVAL = 22;
- internal const int EMFILE = 24;
-
- // *** END GENERATED CODE ***
-
- #endregion
-
-
private readonly object _synchObject = new();
private readonly Dictionary _table = new();
private const int _offset = 3; // number of lowest keys that are not automatically allocated
@@ -247,7 +228,7 @@ public int Add(int id, StreamBox streams) {
ContractUtils.Requires(id >= 0, nameof(id));
lock (_synchObject) {
if (_table.ContainsKey(id)) {
- throw PythonOps.OSError(EBADF, "Bad file descriptor", id.ToString());
+ throw PythonOps.OSError(PythonErrno.EBADF, "Bad file descriptor", id.ToString());
}
streams.Id = id;
_table.Add(id, streams);
@@ -265,7 +246,7 @@ public int Add(StreamBox streams) {
while (_table.ContainsKey(_current)) {
_current++;
if (_current >= LIMIT_OFILE)
- throw PythonOps.OSError(EMFILE, "Too many open files");
+ throw PythonOps.OSError(PythonErrno.EMFILE, "Too many open files");
}
streams.Id = _current;
_table.Add(_current, streams);
@@ -302,7 +283,7 @@ public StreamBox GetStreams(int id) {
if (TryGetStreams(id, out StreamBox? streams)) {
return streams;
}
- throw PythonOps.OSError(EBADF, "Bad file descriptor");
+ throw PythonOps.OSError(PythonErrno.EBADF, "Bad file descriptor");
}
public int GetOrAssignId(StreamBox streams) {
diff --git a/Src/Scripts/generate_os_codes.py b/Src/Scripts/generate_os_codes.py
index 57719d5ee..b69069634 100644
--- a/Src/Scripts/generate_os_codes.py
+++ b/Src/Scripts/generate_os_codes.py
@@ -102,7 +102,7 @@ def darwin_code_expr(codes, fmt):
def linux_code_expr(codes, fmt):
return fmt(codes[linux_idx])
-common_errno_codes = ['ENOENT', 'EBADF', 'EACCES', 'EINVAL', 'EMFILE']
+common_errno_codes = ['ENOENT', 'E2BIG', 'ENOEXEC', 'EBADF', 'ECHILD', 'EAGAIN', 'ENOMEM', 'EACCES', 'EEXIST', 'EXDEV', 'ENOTDIR', 'EMFILE', 'ENOSPC', 'EPIPE', 'ENOTEMPTY', 'EILSEQ', 'EINVAL']
def generate_common_errno_codes(cw):
for name in common_errno_codes: