From d0fb8e18757f8042baa086bbda81cd570fb1ab3d Mon Sep 17 00:00:00 2001 From: Pavel Koneski Date: Fri, 13 Dec 2024 15:53:27 -0800 Subject: [PATCH 1/2] Use symbolic error codes with OSError --- Src/IronPython.Modules/nt.cs | 10 ++++----- Src/IronPython/Modules/_fileio.cs | 8 +++---- Src/IronPython/Modules/_io.cs | 4 +++- Src/IronPython/Runtime/PythonFileManager.cs | 24 ++++++++++++++++++--- Src/Scripts/generate_os_codes.py | 13 +++++++++++ 5 files changed, 46 insertions(+), 13 deletions(-) diff --git a/Src/IronPython.Modules/nt.cs b/Src/IronPython.Modules/nt.cs index 5ef569320..21e340e4e 100644 --- a/Src/IronPython.Modules/nt.cs +++ b/Src/IronPython.Modules/nt.cs @@ -378,7 +378,7 @@ public static int dup2(CodeContext/*!*/ context, int fd, int fd2) { } if (!fileManager.ValidateFdRange(fd2)) { - throw PythonOps.OSError(9, "Bad file descriptor"); + throw PythonOps.OSError(PythonErrorNumber.EBADF, "Bad file descriptor"); } if (fileManager.TryGetStreams(fd2, out _)) { @@ -455,7 +455,7 @@ public static object fstat(CodeContext/*!*/ context, int fd) { if (streams.IsStandardIOStream()) return new stat_result(0x1000); if (StatStream(streams.ReadStream) is not null and var res) return res; } - return LightExceptions.Throw(PythonOps.OSError(9, "Bad file descriptor")); + return LightExceptions.Throw(PythonOps.OSError(PythonErrorNumber.EBADF, "Bad file descriptor")); static object? StatStream(Stream stream) { if (stream is FileStream fs) return lstat(fs.Name, new Dictionary(1)); @@ -481,7 +481,7 @@ public static void fsync(CodeContext context, int fd) { try { streams.Flush(); } catch (IOException) { - throw PythonOps.OSError(9, "Bad file descriptor"); + throw PythonOps.OSError(PythonErrorNumber.EBADF, "Bad file descriptor"); } } @@ -988,7 +988,7 @@ public static Bytes read(CodeContext/*!*/ context, int fd, int buffersize) { try { PythonContext pythonContext = context.LanguageContext; var streams = pythonContext.FileManager.GetStreams(fd); - if (!streams.ReadStream.CanRead) throw PythonOps.OSError(9, "Bad file descriptor"); + if (!streams.ReadStream.CanRead) throw PythonOps.OSError(PythonErrorNumber.EBADF, "Bad file descriptor"); return Bytes.Make(streams.Read(buffersize)); } catch (Exception e) { @@ -1872,7 +1872,7 @@ public static int write(CodeContext/*!*/ context, int fd, [NotNone] IBufferProto using var buffer = data.GetBuffer(); PythonContext pythonContext = context.LanguageContext; var streams = pythonContext.FileManager.GetStreams(fd); - if (!streams.WriteStream.CanWrite) throw PythonOps.OSError(9, "Bad file descriptor"); + if (!streams.WriteStream.CanWrite) throw PythonOps.OSError(PythonErrorNumber.EBADF, "Bad file descriptor"); return streams.Write(buffer); } catch (Exception e) { diff --git a/Src/IronPython/Modules/_fileio.cs b/Src/IronPython/Modules/_fileio.cs index 700a6691a..5e093c6fb 100644 --- a/Src/IronPython/Modules/_fileio.cs +++ b/Src/IronPython/Modules/_fileio.cs @@ -149,7 +149,7 @@ public FileIO(CodeContext/*!*/ context, string name, string mode = "r", bool clo } if (!_context.FileManager.TryGetStreams(fd, out _streams)) { - throw PythonOps.OSError(9, "Bad file descriptor"); + throw PythonOps.OSError(PythonFileManager.EBADF, "Bad file descriptor"); } } else { throw PythonOps.TypeError("expected integer from opener"); @@ -504,13 +504,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(2, "No such file or directory", filename: name); + if (string.IsNullOrWhiteSpace(name)) throw PythonOps.OSError(PythonFileManager.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(13, "Permission denied", name); + throw PythonOps.OSError(PythonFileManager.EACCES, "Permission denied", name); } catch (FileNotFoundException) { - throw PythonOps.OSError(2, "No such file or directory", name); + throw PythonOps.OSError(PythonFileManager.ENOENT, "No such file or directory", name); } catch (IOException e) { AddFilename(context, name, e); throw; diff --git a/Src/IronPython/Modules/_io.cs b/Src/IronPython/Modules/_io.cs index daaa3caa6..94cb80a30 100644 --- a/Src/IronPython/Modules/_io.cs +++ b/Src/IronPython/Modules/_io.cs @@ -31,7 +31,8 @@ public static partial class PythonIOModule { private static readonly object _blockingIOErrorKey = new object(); private static readonly object _unsupportedOperationKey = new object(); - // Values of the O_flags below has to be identical with flags defined in PythonNT + + // Values of the O_flags below have to be identical with flags defined in PythonNT #region Generated Common O_Flags @@ -57,6 +58,7 @@ public static partial class PythonIOModule { #endregion + [SpecialName] public static void PerformModuleReload(PythonContext/*!*/ context, PythonDictionary/*!*/ dict) { context.EnsureModuleException( diff --git a/Src/IronPython/Runtime/PythonFileManager.cs b/Src/IronPython/Runtime/PythonFileManager.cs index c98811608..890cdb6b7 100644 --- a/Src/IronPython/Runtime/PythonFileManager.cs +++ b/Src/IronPython/Runtime/PythonFileManager.cs @@ -211,6 +211,24 @@ 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 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 @@ -225,7 +243,7 @@ public int Add(int id, StreamBox streams) { ContractUtils.Requires(id >= 0, nameof(id)); lock (_synchObject) { if (_table.ContainsKey(id)) { - throw PythonOps.OSError(9, "Bad file descriptor", id.ToString()); + throw PythonOps.OSError(EBADF, "Bad file descriptor", id.ToString()); } streams.Id = id; _table.Add(id, streams); @@ -243,7 +261,7 @@ public int Add(StreamBox streams) { while (_table.ContainsKey(_current)) { _current++; if (_current >= LIMIT_OFILE) - throw PythonOps.OSError(24, "Too many open files"); + throw PythonOps.OSError(EMFILE, "Too many open files"); } streams.Id = _current; _table.Add(_current, streams); @@ -280,7 +298,7 @@ public StreamBox GetStreams(int id) { if (TryGetStreams(id, out StreamBox? streams)) { return streams; } - throw PythonOps.OSError(9, "Bad file descriptor"); + throw PythonOps.OSError(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 811ca0178..912923bd6 100644 --- a/Src/Scripts/generate_os_codes.py +++ b/Src/Scripts/generate_os_codes.py @@ -102,6 +102,18 @@ def darwin_code_expr(codes, fmt): def linux_code_expr(codes, fmt): return fmt(codes[linux_idx]) +common_errno_codes = ['ENOENT', 'EBADF', 'EACCES', 'EMFILE'] + +def generate_common_errno_codes(cw): + for name in common_errno_codes: + codes = errorvalues[name] + + value = windows_code_expr(codes, str) + if (all(c.isdigit() for c in value)): + cw.write(f"internal const int {name} = {value};") + else: + cw.write(f"internal static int {name} => {value};") + def generate_errno_names(cw): def is_windows_alias(name): return "WSA" + name in errorvalues and name in errorvalues and errorvalues["WSA" + name][windows_idx] == errorvalues[name][windows_idx] @@ -208,6 +220,7 @@ def generate_common_O_flags(cw): def main(): return generate( ("Errno Codes", generate_errno_codes), + ("Common Errno Codes", generate_common_errno_codes), ("Errno Names", generate_errno_names), ("O_Flags", generate_all_O_flags), ("Common O_Flags", generate_common_O_flags), From 80bd0be8d1547ca44d263009056e2b998dd2ed5e Mon Sep 17 00:00:00 2001 From: Pavel Koneski Date: Fri, 13 Dec 2024 16:51:11 -0800 Subject: [PATCH 2/2] Fix typos --- Src/IronPython/Runtime/PythonFileManager.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Src/IronPython/Runtime/PythonFileManager.cs b/Src/IronPython/Runtime/PythonFileManager.cs index 890cdb6b7..03ee1d9b3 100644 --- a/Src/IronPython/Runtime/PythonFileManager.cs +++ b/Src/IronPython/Runtime/PythonFileManager.cs @@ -183,7 +183,7 @@ public void CloseStreams(PythonFileManager? manager) { /// /// /// PythonFileManager emulates a file descriptor table. On Windows, .NET uses Win32 API which uses file handles - /// rather than file descriptors. The emulation is necesary to support Python API, which in some places uses file descriptors. + /// rather than file descriptors. The emulation is necessary to support Python API, which in some places uses file descriptors. /// /// The manager maintains a mapping between open files (or system file-like objects) and a "descriptor", being a small non-negative integer. /// Unlike in CPython, the descriptors are allocated lazily, meaning they are allocated only when they become exposed (requested) @@ -198,7 +198,7 @@ public void CloseStreams(PythonFileManager? manager) { /// In such situations, only one of the FileIO may be opened with flag `closefd` (CPython rule). /// /// The second lever of sharing of open files is below the file descriptor level. A file descriptor can be duplicated using dup/dup2, - /// but the duplicated descriptor is still refering to the same open file in the filesystem. In such case, the manager maintains + /// but the duplicated descriptor is still referring to the same open file in the filesystem. In such case, the manager maintains /// a separate StreamBox for the duplicated descriptor, but the StreamBoxes for both descriptors share the underlying Streams. /// Both such descriptors have to be closed independently by the user code (either explicitly by os.close(fd) or through close() /// on the FileIO objects), but the underlying shared streams are closed only when all such duplicated descriptors are closed. @@ -236,7 +236,7 @@ internal class PythonFileManager { private readonly ConcurrentDictionary _refs = new(); // This version of Add is used with genuine file descriptors (Unix). - // Exception: support dup2 on all frameworks/platfroms. + // Exception: support dup2 on all frameworks/platforms. public int Add(int id, StreamBox streams) { ContractUtils.RequiresNotNull(streams, nameof(streams)); ContractUtils.Requires(streams.Id < 0, nameof(streams));