Skip to content

Commit

Permalink
Use symbolic error codes with OSError (#1840)
Browse files Browse the repository at this point in the history
* Use symbolic error codes with OSError

* Fix typos
  • Loading branch information
BCSharp authored Dec 14, 2024
1 parent 05cfb0e commit ddd1c69
Show file tree
Hide file tree
Showing 5 changed files with 49 additions and 16 deletions.
10 changes: 5 additions & 5 deletions Src/IronPython.Modules/nt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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 _)) {
Expand Down Expand Up @@ -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<string, object>(1));
Expand All @@ -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");
}
}

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down
8 changes: 4 additions & 4 deletions Src/IronPython/Modules/_fileio.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -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;
Expand Down
4 changes: 3 additions & 1 deletion Src/IronPython/Modules/_io.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -57,6 +58,7 @@ public static partial class PythonIOModule {

#endregion


[SpecialName]
public static void PerformModuleReload(PythonContext/*!*/ context, PythonDictionary/*!*/ dict) {
context.EnsureModuleException(
Expand Down
30 changes: 24 additions & 6 deletions Src/IronPython/Runtime/PythonFileManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ public void CloseStreams(PythonFileManager? manager) {
/// </summary>
/// <remarks>
/// 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)
Expand All @@ -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.
Expand All @@ -211,21 +211,39 @@ internal class PythonFileManager {
/// </summary>
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<int, StreamBox> _table = new();
private const int _offset = 3; // number of lowest keys that are not automatically allocated
private int _current = _offset; // lowest potentially unused key in _objects at or above _offset
private readonly ConcurrentDictionary<Stream, int> _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));
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);
Expand All @@ -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);
Expand Down Expand Up @@ -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) {
Expand Down
13 changes: 13 additions & 0 deletions Src/Scripts/generate_os_codes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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),
Expand Down

0 comments on commit ddd1c69

Please sign in to comment.