diff --git a/Src/IronPython.Modules/_overlapped.cs b/Src/IronPython.Modules/_overlapped.cs index b0f468af7..5a680a207 100644 --- a/Src/IronPython.Modules/_overlapped.cs +++ b/Src/IronPython.Modules/_overlapped.cs @@ -5,11 +5,14 @@ using System; using System.Numerics; using System.Runtime.InteropServices; +using System.Runtime.Versioning; using IronPython.Runtime; [assembly: PythonModule("_overlapped", typeof(IronPython.Modules.PythonOverlapped), PlatformsAttribute.PlatformFamily.Windows)] namespace IronPython.Modules { + + [SupportedOSPlatform("windows")] public static class PythonOverlapped { public const int ERROR_NETNAME_DELETED = 64; public const int ERROR_SEM_TIMEOUT = 121; diff --git a/Src/IronPython.Modules/_winapi.cs b/Src/IronPython.Modules/_winapi.cs index dcfacd5d4..d26d03ec6 100644 --- a/Src/IronPython.Modules/_winapi.cs +++ b/Src/IronPython.Modules/_winapi.cs @@ -9,6 +9,7 @@ using System; using System.Numerics; using System.Runtime.InteropServices; +using System.Runtime.Versioning; using System.Text; using IronPython.Runtime; @@ -16,6 +17,7 @@ [assembly: PythonModule("_winapi", typeof(IronPython.Modules.PythonWinApi), PlatformsAttribute.PlatformFamily.Windows)] namespace IronPython.Modules { + [SupportedOSPlatform("windows")] public static class PythonWinApi { #region Public API diff --git a/Src/IronPython.Modules/mmap.cs b/Src/IronPython.Modules/mmap.cs index 3875301dc..942f35a7d 100644 --- a/Src/IronPython.Modules/mmap.cs +++ b/Src/IronPython.Modules/mmap.cs @@ -5,7 +5,6 @@ #if FEATURE_MMAP using System; -using System.ComponentModel; using System.Diagnostics; using System.Globalization; using System.IO; @@ -205,12 +204,12 @@ public static class MmapModule { public static readonly string __doc__ = null; - private static string FormatError(int errorCode) { - return new Win32Exception(errorCode).Message; - } - - private static Exception WindowsError(int code) { - return PythonExceptions.CreateThrowable(PythonExceptions.OSError, code, FormatError(code)); + private static Exception WindowsError(int winerror) { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { + return PythonNT.GetWin32Error(winerror); + } else { + return PythonNT.GetOsError(PythonExceptions._OSError.WinErrorToErrno(winerror)); + } } public static PythonType error => PythonExceptions.OSError; @@ -234,7 +233,7 @@ public MmapUnix(CodeContext/*!*/ context, int fileno, long length, int flags = M private static MemoryMappedFileAccess ToMmapFileAccess(int flags, int prot, int access) { if (access == ACCESS_DEFAULT) { if ((flags & (MAP_PRIVATE | MAP_SHARED)) == 0) { - throw PythonOps.OSError(PythonErrno.EINVAL, "Invalid argument"); + throw PythonNT.GetOsError(PythonErrno.EINVAL); } if ((prot & PROT_WRITE) != 0) { prot |= PROT_READ; @@ -339,13 +338,13 @@ public MmapDefault(CodeContext/*!*/ context, int fileno, long length, string tag #if NET8_0_OR_GREATER // On .NET 8.0+ we can create a MemoryMappedFile directly from a file descriptor stream.Flush(); - CheckFileAccessAndSize(stream); - fileno = Dup(fileno); + CheckFileAccessAndSize(stream, isWindows: false); + fileno = PythonNT.dupUnix(fileno, closeOnExec: true); _handle = new SafeFileHandle((IntPtr)fileno, ownsHandle: true); _file = MemoryMappedFile.CreateFromFile(_handle, _mapName, stream.Length, _fileAccess, HandleInheritability.None, leaveOpen: true); #else // On .NET 6.0 on POSIX we need to create a FileStream from the file descriptor - fileno = Dup(fileno); + fileno = PythonNT.dupUnix(fileno, closeOnExec: true); _handle = new SafeFileHandle((IntPtr)fileno, ownsHandle: true); FileAccess fa = stream.CanWrite ? stream.CanRead ? FileAccess.ReadWrite : FileAccess.Write : FileAccess.Read; // This FileStream constructor may or may not work on Mono, but on Mono streams.ReadStream is FileStream @@ -356,7 +355,7 @@ public MmapDefault(CodeContext/*!*/ context, int fileno, long length, string tag } // otherwise leaves _file as null and _sourceStream as null } else { - throw PythonOps.OSError(PythonExceptions._OSError.ERROR_INVALID_BLOCK, "Bad file descriptor"); + throw PythonNT.GetOsError(PythonErrno.EBADF); } if (_file is null) { @@ -369,7 +368,7 @@ public MmapDefault(CodeContext/*!*/ context, int fileno, long length, string tag length = _sourceStream.Length - _offset; } - CheckFileAccessAndSize(_sourceStream); + CheckFileAccessAndSize(_sourceStream, RuntimeInformation.IsOSPlatform(OSPlatform.Windows)); long capacity = checked(_offset + length); @@ -402,7 +401,7 @@ public MmapDefault(CodeContext/*!*/ context, int fileno, long length, string tag } _position = 0L; - void CheckFileAccessAndSize(Stream stream) { + void CheckFileAccessAndSize(Stream stream, bool isWindows) { bool isValid = _fileAccess switch { MemoryMappedFileAccess.Read => stream.CanRead, MemoryMappedFileAccess.ReadWrite => stream.CanRead && stream.CanWrite, @@ -414,10 +413,10 @@ void CheckFileAccessAndSize(Stream stream) { try { if (!isValid) { - throw PythonOps.OSError(PythonExceptions._OSError.ERROR_ACCESS_DENIED, "Invalid access mode"); + throw WindowsError(PythonExceptions._OSError.ERROR_ACCESS_DENIED); } - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { + if (!isWindows) { // Unix map does not support increasing size on open if (length != 0 && _offset + length > stream.Length) { throw PythonOps.ValueError("mmap length is greater than file size"); @@ -437,29 +436,6 @@ void CheckFileAccessAndSize(Stream stream) { } // end of constructor - // TODO: Move to PythonNT - POSIX - private static int Dup(int fd) { - int fd2 = Mono.Unix.Native.Syscall.dup(fd); - if (fd2 == -1) throw PythonNT.GetLastUnixError(); - - try { - // set close-on-exec flag - int flags = Mono.Unix.Native.Syscall.fcntl(fd2, Mono.Unix.Native.FcntlCommand.F_GETFD); - if (flags == -1) throw PythonNT.GetLastUnixError(); - - const int FD_CLOEXEC = 1; // TODO: Move to module fcntl - flags |= FD_CLOEXEC; - flags = Mono.Unix.Native.Syscall.fcntl(fd2, Mono.Unix.Native.FcntlCommand.F_SETFD, flags); - if (flags == -1) throw PythonNT.GetLastUnixError(); - } catch { - Mono.Unix.Native.Syscall.close(fd2); - throw; - } - - return fd2; - } - - public object __len__() { using (new MmapLocker(this)) { return ReturnLong(_view.Capacity); @@ -825,12 +801,20 @@ public void resize(long newsize) { throw PythonOps.TypeError("mmap can't resize a readonly or copy-on-write memory map."); } + if (newsize < 0) { + throw PythonOps.ValueError("new size out of range"); + } + if (_handle is not null && (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) || RuntimeInformation.IsOSPlatform(OSPlatform.Linux))) { // resize on Posix platforms try { if (_handle.IsInvalid) { - throw PythonOps.OSError(PythonErrno.EBADF, "Bad file descriptor"); + throw PythonNT.GetOsError(PythonErrno.EBADF); + } + if (newsize == 0) { + // resizing to an empty mapped region is not allowed + throw PythonNT.GetOsError(PythonErrno.EINVAL); } _view.Flush(); _view.Dispose(); @@ -1132,20 +1116,18 @@ internal Bytes GetSearchString() { } } - [SupportedOSPlatform("linux"), SupportedOSPlatform("macos")] + [SupportedOSPlatform("linux")] + [SupportedOSPlatform("macos")] private static long GetFileSizeUnix(SafeFileHandle handle) { long size; if (handle.IsInvalid) { - throw PythonOps.OSError(PythonExceptions._OSError.ERROR_INVALID_HANDLE, "Invalid file handle"); + throw PythonNT.GetOsError(PythonErrno.EBADF); } if (Mono.Unix.Native.Syscall.fstat((int)handle.DangerousGetHandle(), out Mono.Unix.Native.Stat status) == 0) { size = status.st_size; } else { - Mono.Unix.Native.Errno errno = Mono.Unix.Native.Stdlib.GetLastError(); - string msg = Mono.Unix.UnixMarshal.GetErrorDescription(errno); - int error = Mono.Unix.Native.NativeConvert.FromErrno(errno); - throw PythonOps.OSError(error, msg); + throw PythonNT.GetLastUnixError(); } return size; diff --git a/Src/IronPython.Modules/nt.cs b/Src/IronPython.Modules/nt.cs index add82e654..b77b65a51 100644 --- a/Src/IronPython.Modules/nt.cs +++ b/Src/IronPython.Modules/nt.cs @@ -36,7 +36,7 @@ [assembly: PythonModule("nt", typeof(IronPython.Modules.PythonNT))] namespace IronPython.Modules { - public static class PythonNT { + public static partial class PythonNT { public const string __doc__ = "Provides low-level operating system access for files, the environment, etc..."; /* TODO: missing functions/classes: @@ -301,12 +301,6 @@ public static void chdir(CodeContext context, [NotNone] Bytes path) public static void chdir(CodeContext context, object? path) => chdir(ConvertToFsString(context, path, nameof(path))); - // Isolate Mono.Unix from the rest of the method so that we don't try to load the Mono.Unix assembly on Windows. - private static void chmodUnix(string path, int mode) { - if (Mono.Unix.Native.Syscall.chmod(path, Mono.Unix.Native.NativeConvert.ToFilePermissions((uint)mode)) == 0) return; - throw GetLastUnixError(path); - } - [Documentation("chmod(path, mode, *, dir_fd=None, follow_symlinks=True)")] public static void chmod([NotNone] string path, int mode, [ParamDictionary, NotNone] IDictionary kwargs) { foreach (var key in kwargs.Keys) { @@ -371,7 +365,7 @@ public static int dup(CodeContext/*!*/ context, int fd) { StreamBox streams = fileManager.GetStreams(fd); // OSError if fd not valid if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { - int fd2 = UnixDup(fd, -1, out Stream? dupstream); + int fd2 = DuplicateStreamDescriptorUnix(fd, -1, out Stream? dupstream); if (dupstream is not null) { return fileManager.Add(fd2, new(dupstream)); } else { @@ -413,7 +407,7 @@ public static int dup2(CodeContext/*!*/ context, int fd, int fd2) { fd = fs.SafeFileHandle.DangerousGetHandle().ToInt32(); fs.Seek(pos, SeekOrigin.Begin); } - fd2 = UnixDup(fd, fd2, out Stream? dupstream); // closes fd2 atomically if reopened in the meantime + fd2 = DuplicateStreamDescriptorUnix(fd, fd2, out Stream? dupstream); // closes fd2 atomically if reopened in the meantime fileManager.Remove(fd2); if (dupstream is not null) { return fileManager.Add(fd2, new(dupstream)); @@ -431,41 +425,6 @@ public static int dup2(CodeContext/*!*/ context, int fd, int fd2) { } - [SupportedOSPlatform("linux"), SupportedOSPlatform("osx")] - private static int UnixDup(int fd, int fd2, out Stream? stream) { - int res = fd2 < 0 ? Mono.Unix.Native.Syscall.dup(fd) : Mono.Unix.Native.Syscall.dup2(fd, fd2); - if (res < 0) throw GetLastUnixError(); - if (ClrModule.IsMono) { - // Elaborate workaround on Mono to avoid UnixStream as out - stream = new Mono.Unix.UnixStream(res, ownsHandle: false); - FileAccess fileAccess = stream.CanWrite ? stream.CanRead ? FileAccess.ReadWrite : FileAccess.Write : FileAccess.Read; - stream.Dispose(); - try { - // FileStream on Mono created with a file descriptor might not work: https://github.com/mono/mono/issues/12783 - // Test if it does, without closing the handle if it doesn't - var sfh = new SafeFileHandle((IntPtr)res, ownsHandle: false); - stream = new FileStream(sfh, fileAccess); - // No exception? Great! We can use FileStream. - stream.Dispose(); - sfh.Dispose(); - stream = null; // Create outside of try block - } catch (IOException) { - // Fall back to UnixStream - stream = new Mono.Unix.UnixStream(res, ownsHandle: true); - } - if (stream is null) { - // FileStream is safe - var sfh = new SafeFileHandle((IntPtr)res, ownsHandle: true); - stream = new FileStream(sfh, fileAccess); - } - } else { - // normal case - stream = new PosixFileStream(res); - } - return res; - } - - #if FEATURE_PROCESS /// /// single instance of environment dictionary is shared between multiple runtimes because the environment @@ -563,11 +522,6 @@ static void linkWindows(string src, string dst) { if (!CreateHardLink(dst, src, IntPtr.Zero)) throw GetLastWin32Error(src, dst); } - - static void linkUnix(string src, string dst) { - if (Mono.Unix.Native.Syscall.link(src, dst) == 0) return; - throw GetLastUnixError(src, dst); - } } public static bool isatty(CodeContext context, int fd) { @@ -779,11 +733,6 @@ public static void symlink([NotNone] string src, [NotNone] string dst, [ParamDic } else { throw new NotImplementedException(); } - - static void symlinkUnix(string src, string dst) { - if (Mono.Unix.Native.Syscall.symlink(src, dst) == 0) return; - throw GetLastUnixError(src, dst); - } } [Documentation("")] @@ -825,23 +774,6 @@ public override string __repr__(CodeContext context) { } } - [PythonHidden(PlatformsAttribute.PlatformFamily.Windows)] - public static uname_result uname() { - Mono.Unix.Native.Utsname info; - Mono.Unix.Native.Syscall.uname(out info); - return new uname_result(info.sysname, info.nodename, info.release, info.version, info.machine); - } - - [PythonHidden(PlatformsAttribute.PlatformFamily.Windows)] - public static BigInteger getuid() { - return Mono.Unix.Native.Syscall.getuid(); - } - - [PythonHidden(PlatformsAttribute.PlatformFamily.Windows)] - public static BigInteger geteuid() { - return Mono.Unix.Native.Syscall.geteuid(); - } - #endif #if FEATURE_FILESYSTEM @@ -1012,17 +944,14 @@ public static PythonTuple pipe(CodeContext context) { manager.Add(new(inPipe)), manager.Add(new(outPipe)) ); - } else { + } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { var pipeStreams = CreatePipeStreamsUnix(); return PythonTuple.MakeTuple( manager.Add(pipeStreams.Item1, new(pipeStreams.Item2)), manager.Add(pipeStreams.Item3, new(pipeStreams.Item4)) ); - } - - static Tuple CreatePipeStreamsUnix() { - Mono.Unix.UnixPipes pipes = Mono.Unix.UnixPipes.CreatePipes(); - return Tuple.Create(pipes.Reading.Handle, pipes.Reading, pipes.Writing.Handle, pipes.Writing); + } else { + throw new PlatformNotSupportedException(); } } #endif @@ -1094,11 +1023,6 @@ public static void rename(CodeContext context, object? src, object? dst, [ParamD [DllImport("kernel32.dll", EntryPoint = "MoveFileExW", SetLastError = true, CharSet = CharSet.Unicode, BestFitMapping = false)] private static extern bool MoveFileEx(string src, string dst, uint flags); - private static void renameUnix(string src, string dst) { - if (Mono.Unix.Native.Syscall.rename(src, dst) == 0) return; - throw GetLastUnixError(src, dst); - } - [Documentation("replace(src, dst, *, src_dir_fd=None, dst_dir_fd=None)")] public static void replace([NotNone] string src, [NotNone] string dst, [ParamDictionary, NotNone] IDictionary kwargs) { foreach (var key in kwargs.Keys) { @@ -1345,12 +1269,15 @@ public sealed class stat_result : PythonTuple { internal stat_result(int mode) : this(new object[10] { mode, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, null) { } + [SupportedOSPlatform("linux")] + [SupportedOSPlatform("macos")] internal stat_result(Mono.Unix.Native.Stat stat) : this(new object[16] {Mono.Unix.Native.NativeConvert.FromFilePermissions(stat.st_mode), ToInt(stat.st_ino), ToInt(stat.st_dev), ToInt(stat.st_nlink), ToInt(stat.st_uid), ToInt(stat.st_gid), ToInt(stat.st_size), ToInt(stat.st_atime), ToInt(stat.st_mtime), ToInt(stat.st_ctime), stat.st_atime + stat.st_atime_nsec / (double)nanosecondsPerSeconds, stat.st_mtime + stat.st_mtime_nsec / (double)nanosecondsPerSeconds, stat.st_ctime + stat.st_ctime_nsec / (double)nanosecondsPerSeconds, ToInt(stat.st_atime * nanosecondsPerSeconds + stat.st_atime_nsec), ToInt(stat.st_mtime * nanosecondsPerSeconds + stat.st_mtime_nsec), ToInt(stat.st_ctime * nanosecondsPerSeconds + stat.st_ctime_nsec) }, null) { } + [SupportedOSPlatform("windows")] internal stat_result(int mode, ulong fileidx, long size, long st_atime_ns, long st_mtime_ns, long st_ctime_ns) : this(new object[16] { mode, ToInt(fileidx), 0, 0, 0, 0, ToInt(size), ToInt(st_atime_ns / nanosecondsPerSeconds), ToInt(st_mtime_ns / nanosecondsPerSeconds), ToInt(st_ctime_ns / nanosecondsPerSeconds), @@ -1467,21 +1394,6 @@ private static bool HasExecutableExtension(string path) { return (extension == ".exe" || extension == ".dll" || extension == ".com" || extension == ".bat"); } - // Isolate Mono.Unix from the rest of the method so that we don't try to load the Mono.Unix assembly on Windows. - private static object statUnix(string path) { - if (Mono.Unix.Native.Syscall.stat(path, out Mono.Unix.Native.Stat buf) == 0) { - return new stat_result(buf); - } - return LightExceptions.Throw(GetLastUnixError(path)); - } - - private static object fstatUnix(int fd) { - if (Mono.Unix.Native.Syscall.fstat(fd, out Mono.Unix.Native.Stat buf) == 0) { - return new stat_result(buf); - } - return LightExceptions.Throw(GetLastUnixError()); - } - private const int OPEN_EXISTING = 3; private const int FILE_ATTRIBUTE_NORMAL = 0x00000080; private const int FILE_READ_ATTRIBUTES = 0x0080; @@ -1616,9 +1528,12 @@ public static string strerror(int code) { const int bufsize = 0x1FF; var buffer = new StringBuilder(bufsize); - int result = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? - Interop.Ucrtbase.strerror(code, buffer) : - strerror_r(code, buffer); + int result = -1; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { + result = Interop.Ucrtbase.strerror(code, buffer); + } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { + result = strerror_r(code, buffer); + } if (result == 0) { var msg = buffer.ToString(); @@ -1630,12 +1545,6 @@ public static string strerror(int code) { return "Unknown error " + code; } -#if FEATURE_NATIVE - // Isolate Mono.Unix from the rest of the method so that we don't try to load the Mono.Unix assembly on Windows. - private static int strerror_r(int code, StringBuilder buffer) - => Mono.Unix.Native.Syscall.strerror_r(Mono.Unix.Native.NativeConvert.ToErrno(code), buffer); -#endif - #if FEATURE_PROCESS [Documentation("system(command) -> int\nExecute the command (a string) in a subshell.")] public static int system([NotNone] string command) { @@ -1724,18 +1633,6 @@ public static void ftruncate(CodeContext context, int fd, BigInteger length) { } - [SupportedOSPlatform("linux"), SupportedOSPlatform("osx")] - internal static void ftruncateUnix(int fd, long length) { - int result; - Mono.Unix.Native.Errno errno; - do { - result = Mono.Unix.Native.Syscall.ftruncate(fd, length); - } while (Mono.Unix.UnixMarshal.ShouldRetrySyscall(result, out errno)); - - if (errno != 0) - throw GetOsError(Mono.Unix.Native.NativeConvert.FromErrno(errno)); - } - #if FEATURE_FILESYSTEM public static object times() { @@ -1841,18 +1738,6 @@ public static int umask(CodeContext/*!*/ context, object? mask) #if FEATURE_FILESYSTEM - private static void utimeUnix(string path, long atime_ns, long utime_ns) { - var atime = new Mono.Unix.Native.Timespec(); - atime.tv_sec = atime_ns / 1_000_000_000; - atime.tv_nsec = atime_ns % 1_000_000_000; - var utime = new Mono.Unix.Native.Timespec(); - utime.tv_sec = utime_ns / 1_000_000_000; - utime.tv_nsec = utime_ns % 1_000_000_000; - - if (Mono.Unix.Native.Syscall.utimensat(Mono.Unix.Native.Syscall.AT_FDCWD, path, new[] { atime, utime }, 0) == 0) return; - throw GetLastUnixError(path); - } - [Documentation("utime(path, times=None, *[, ns], dir_fd=None, follow_symlinks=True)")] public static void utime([NotNone] string path, [ParamDictionary, NotNone] IDictionary kwargs, [NotNone] params object[] args) { var numArgs = args.Length; @@ -1979,8 +1864,7 @@ public static int write(CodeContext/*!*/ context, int fd, [NotNone] IBufferProto public static void kill(CodeContext/*!*/ context, int pid, int sig) { if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { - if (Mono.Unix.Native.Syscall.kill(pid, Mono.Unix.Native.NativeConvert.ToSignum(sig)) == 0) return; - throw GetLastUnixError(); + killUnix(pid, sig); } else { if (PythonSignal.NativeSignal.GenerateConsoleCtrlEvent((uint)sig, (uint)pid)) return; @@ -2234,8 +2118,9 @@ private static Exception ToPythonException(Exception e, string? filename = null) message = e.Message; isWindowsError = true; } else if (e is UnauthorizedAccessException unauth) { - errorCode = PythonExceptions._OSError.ERROR_ACCESS_DENIED; - return PythonOps.OSError(errorCode, "Access is denied", filename, errorCode); + return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? + GetWin32Error(PythonExceptions._OSError.ERROR_ACCESS_DENIED, filename) : + GetOsError(PythonErrno.EACCES, filename); } else { var ioe = e as IOException; Exception? pe = IOExceptionToPythonException(ioe, error, filename); @@ -2257,7 +2142,7 @@ private static Exception ToPythonException(Exception e, string? filename = null) } if (isWindowsError) { - return PythonOps.OSError(errorCode, message, filename, errorCode); + return PythonOps.OSError(PythonExceptions._OSError.WinErrorToErrno(errorCode), message, filename, errorCode); } return PythonOps.OSError(errorCode, message, filename); @@ -2414,20 +2299,19 @@ private static Exception DirectoryExistsError(string? filename) { return GetOsError(PythonErrno.EEXIST, filename); } -#if FEATURE_NATIVE - - internal static Exception GetLastUnixError(string? filename = null, string? filename2 = null) - => GetOsError(Mono.Unix.Native.NativeConvert.FromErrno(Mono.Unix.Native.Syscall.GetLastError()), filename, filename2); -#endif - - private static Exception GetOsError(int error, string? filename = null, string? filename2 = null) - => PythonOps.OSError(error, strerror(error), filename, null, filename2); + internal static Exception GetOsError(int errno, string? filename = null, string? filename2 = null) + => PythonOps.OSError(errno, strerror(errno), filename, null, filename2); #if FEATURE_NATIVE || FEATURE_CTYPES + [SupportedOSPlatform("windows")] + internal static Exception GetLastWin32Error(string? filename = null, string? filename2 = null) + => GetWin32Error(Marshal.GetLastWin32Error(), filename, filename2); + // Gets an error message for a Win32 error code. - internal static string GetMessage(int errorCode) { + [SupportedOSPlatform("windows")] + private static string GetWin32ErrorMessage(int errorCode) { string msg = new Win32Exception(errorCode).Message; // error codes: https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes if (errorCode is not (< 0 or >= 8200 or 34 or 106 or 317 or 718)) { @@ -2440,12 +2324,10 @@ internal static string GetMessage(int errorCode) { return msg.TrimEnd('\r', '\n', '.'); } - internal static Exception GetLastWin32Error(string? filename = null, string? filename2 = null) - => GetWin32Error(Marshal.GetLastWin32Error(), filename, filename2); - - private static Exception GetWin32Error(int error, string? filename = null, string? filename2 = null) { - var msg = GetMessage(error); - return PythonOps.OSError(0, msg, filename, error, filename2); + [SupportedOSPlatform("windows")] + internal static Exception GetWin32Error(int winerror, string? filename = null, string? filename2 = null) { + var msg = GetWin32ErrorMessage(winerror); + return PythonOps.OSError(0, msg, filename, winerror, filename2); } #endif diff --git a/Src/IronPython.Modules/posix.cs b/Src/IronPython.Modules/posix.cs new file mode 100644 index 000000000..6c8412126 --- /dev/null +++ b/Src/IronPython.Modules/posix.cs @@ -0,0 +1,231 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information. + +#nullable enable + +using System; +using System.IO; +using System.Numerics; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; +using System.Text; + +using Microsoft.Scripting.Runtime; +using Microsoft.Win32.SafeHandles; + +using Mono.Unix; +using Mono.Unix.Native; + +using IronPython.Runtime; + +namespace IronPython.Modules { + // This file contains exclusively functions used on POSIX systems, heavily dependent on Mono.Unix. + // Every function in this part of PythonNT must have a platform guard preventing it from being used on Windows. + // This isolates Mono.Unix from the rest of the code so that we don't try to load the Mono.Unix assembly on Windows. + // The main implementation of module `nt` still may contain some code that is POSIX-specific, but without using Mono.Unix. + public static partial class PythonNT { +#if FEATURE_NATIVE + + [SupportedOSPlatform("linux")] + [SupportedOSPlatform("macos")] + internal static Exception GetLastUnixError(string? filename = null, string? filename2 = null) + // On POSIX, GetLastWin32Error returns the errno value, same as GetLastPInvokeError + => GetOsError(Marshal.GetLastWin32Error(), filename, filename2); + + + [SupportedOSPlatform("linux")] + [SupportedOSPlatform("macos")] + private static int strerror_r(int code, StringBuilder buffer) + => Syscall.strerror_r(NativeConvert.ToErrno(code), buffer); + + +#if FEATURE_PIPES + [SupportedOSPlatform("linux")] + [SupportedOSPlatform("macos")] + private static Tuple CreatePipeStreamsUnix() { + UnixPipes pipes = UnixPipes.CreatePipes(); + return Tuple.Create(pipes.Reading.Handle, pipes.Reading, pipes.Writing.Handle, pipes.Writing); + } +#endif + + + [SupportedOSPlatform("linux")] + [SupportedOSPlatform("macos")] + private static int DuplicateStreamDescriptorUnix(int fd, int targetfd, out Stream? stream) { + int res = targetfd < 0 ? Syscall.dup(fd) : Syscall.dup2(fd, targetfd); + if (res < 0) throw GetLastUnixError(); + + if (ClrModule.IsMono) { + // Elaborate workaround on Mono to avoid UnixStream as out + stream = new UnixStream(res, ownsHandle: false); + FileAccess fileAccess = stream.CanWrite ? stream.CanRead ? FileAccess.ReadWrite : FileAccess.Write : FileAccess.Read; + stream.Dispose(); + try { + // FileStream on Mono created with a file descriptor might not work: https://github.com/mono/mono/issues/12783 + // Test if it does, without closing the handle if it doesn't + var sfh = new SafeFileHandle((IntPtr)res, ownsHandle: false); + stream = new FileStream(sfh, fileAccess); + // No exception? Great! We can use FileStream. + stream.Dispose(); + sfh.Dispose(); + stream = null; // Create outside of try block + } catch (IOException) { + // Fall back to UnixStream + stream = new UnixStream(res, ownsHandle: true); + } + if (stream is null) { + // FileStream is safe + var sfh = new SafeFileHandle((IntPtr)res, ownsHandle: true); + stream = new FileStream(sfh, fileAccess); + } + } else { + // normal case + stream = new PosixFileStream(res); + } + return res; + } + + + [SupportedOSPlatform("linux")] + [SupportedOSPlatform("macos")] + internal static int dupUnix(int fd, bool closeOnExec) { + int fd2 = Syscall.dup(fd); + if (fd2 == -1) throw GetLastUnixError(); + + if (closeOnExec) { + try { + // set close-on-exec flag + int flags = Syscall.fcntl(fd2, FcntlCommand.F_GETFD); + if (flags == -1) throw GetLastUnixError(); + + const int FD_CLOEXEC = 1; // TODO: Move to module fcntl + flags |= FD_CLOEXEC; + flags = Syscall.fcntl(fd2, FcntlCommand.F_SETFD, flags); + if (flags == -1) throw GetLastUnixError(); + } catch { + Syscall.close(fd2); + throw; + } + } + + return fd2; + } + + + [SupportedOSPlatform("linux")] + [SupportedOSPlatform("macos")] + private static void chmodUnix(string path, int mode) { + if (Syscall.chmod(path, NativeConvert.ToFilePermissions((uint)mode)) == 0) return; + throw GetLastUnixError(path); + } + + + [SupportedOSPlatform("linux")] + [SupportedOSPlatform("macos")] + private static void linkUnix(string src, string dst) { + if (Syscall.link(src, dst) == 0) return; + throw GetLastUnixError(src, dst); + } + + + [SupportedOSPlatform("linux")] + [SupportedOSPlatform("macos")] + private static void symlinkUnix(string src, string dst) { + if (Syscall.symlink(src, dst) == 0) return; + throw GetLastUnixError(src, dst); + } + + + [SupportedOSPlatform("linux")] + [SupportedOSPlatform("macos")] + private static void renameUnix(string src, string dst) { + if (Syscall.rename(src, dst) == 0) return; + throw GetLastUnixError(src, dst); + } + + + [SupportedOSPlatform("linux")] + [SupportedOSPlatform("macos")] + private static object statUnix(string path) { + if (Syscall.stat(path, out Stat buf) == 0) { + return new stat_result(buf); + } + return LightExceptions.Throw(GetLastUnixError(path)); + } + + + [SupportedOSPlatform("linux")] + [SupportedOSPlatform("macos")] + private static object fstatUnix(int fd) { + if (Syscall.fstat(fd, out Stat buf) == 0) { + return new stat_result(buf); + } + return LightExceptions.Throw(GetLastUnixError()); + } + + + [SupportedOSPlatform("linux")] + [SupportedOSPlatform("macos")] + internal static void ftruncateUnix(int fd, long length) { + int result; + Errno errno; + do { + result = Syscall.ftruncate(fd, length); + } while (UnixMarshal.ShouldRetrySyscall(result, out errno)); + + if (errno != 0) + throw GetOsError(NativeConvert.FromErrno(errno)); + } + + + [PythonHidden(PlatformsAttribute.PlatformFamily.Windows)] + [SupportedOSPlatform("linux")] + [SupportedOSPlatform("macos")] + public static uname_result uname() { + if (Syscall.uname(out Utsname info) == 0) { + return new uname_result(info.sysname, info.nodename, info.release, info.version, info.machine); + } + throw GetLastUnixError(); // rare + } + + [PythonHidden(PlatformsAttribute.PlatformFamily.Windows)] + [SupportedOSPlatform("linux")] + [SupportedOSPlatform("macos")] + public static BigInteger getuid() { + return Syscall.getuid(); + } + + [PythonHidden(PlatformsAttribute.PlatformFamily.Windows)] + [SupportedOSPlatform("linux")] + [SupportedOSPlatform("macos")] + public static BigInteger geteuid() { + return Syscall.geteuid(); + } + + + [SupportedOSPlatform("linux")] + [SupportedOSPlatform("macos")] + private static void utimeUnix(string path, long atime_ns, long utime_ns) { + var atime = new Timespec(); + atime.tv_sec = atime_ns / 1_000_000_000; + atime.tv_nsec = atime_ns % 1_000_000_000; + var utime = new Timespec(); + utime.tv_sec = utime_ns / 1_000_000_000; + utime.tv_nsec = utime_ns % 1_000_000_000; + + if (Syscall.utimensat(Syscall.AT_FDCWD, path, new[] { atime, utime }, 0) == 0) return; + throw GetLastUnixError(path); + } + + + [SupportedOSPlatform("linux")] + [SupportedOSPlatform("macos")] + private static void killUnix(int pid, int sig) { + if (Syscall.kill(pid, NativeConvert.ToSignum(sig)) == 0) return; + throw GetLastUnixError(); + } + +#endif + } +} diff --git a/Src/IronPython.Modules/resource.cs b/Src/IronPython.Modules/resource.cs index f83898a1c..4baf012b6 100644 --- a/Src/IronPython.Modules/resource.cs +++ b/Src/IronPython.Modules/resource.cs @@ -329,8 +329,7 @@ private static object LimitsArgError() => LightExceptions.Throw(PythonOps.ValueError("expected a tuple of 2 integers")); private static object GetPInvokeError() { - int errno = Marshal.GetLastWin32Error(); // despite its name, on Posix it retrieves errno set by the last p/Invoke call - return LightExceptions.Throw(PythonOps.OSError(errno, PythonNT.strerror(errno))); + return LightExceptions.Throw(PythonNT.GetLastUnixError()); } private static object ToPythonInt(this ulong value) diff --git a/Src/IronPython/Runtime/Exceptions/PythonExceptions.cs b/Src/IronPython/Runtime/Exceptions/PythonExceptions.cs index 81753a7bf..9c48ded0b 100644 --- a/Src/IronPython/Runtime/Exceptions/PythonExceptions.cs +++ b/Src/IronPython/Runtime/Exceptions/PythonExceptions.cs @@ -167,14 +167,14 @@ public override void __init__(params object[] args) { } if (args.Length >= 4) { winerror = args[3] ?? Undefined; - if (winerror is int) { - errno = WinErrorToErrno((int)winerror); + if (winerror is int err) { + errno = WinErrorToErrno(err); } } if (args.Length >= 5) { filename2 = args[4] ?? Undefined; } - args = new object[] { errno, strerror }; + args = [errno, strerror]; } base.__init__(args); }