-
Notifications
You must be signed in to change notification settings - Fork 306
Implement fcntl syscalls
#1893
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement fcntl syscalls
#1893
Changes from all commits
3a8429c
38d35c4
42d8610
73da5cb
a6a14d8
2b89d44
b57ad4a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,12 +5,20 @@ | |
| #nullable enable | ||
|
|
||
| using System; | ||
| using System.Diagnostics; | ||
| using System.Numerics; | ||
| using System.Reflection; | ||
| using System.Runtime.InteropServices; | ||
| using System.Runtime.Versioning; | ||
|
|
||
| using Mono.Unix; | ||
| using Mono.Unix.Native; | ||
|
|
||
| using Microsoft.Scripting.Runtime; | ||
|
|
||
| using IronPython.Runtime; | ||
| using IronPython.Runtime.Operations; | ||
|
|
||
|
|
||
| [assembly: PythonModule("fcntl", typeof(IronPython.Modules.PythonFcntl), PlatformsAttribute.PlatformFamily.Unix)] | ||
| namespace IronPython.Modules; | ||
|
|
@@ -27,6 +35,92 @@ a file object. | |
| """; | ||
|
|
||
|
|
||
| #region fcntl | ||
|
|
||
| [LightThrowing] | ||
| public static object fcntl(int fd, int cmd, [NotNone] Bytes arg) { | ||
| CheckFileDescriptor(fd); | ||
|
|
||
| const int maxArgSize = 1024; // 1 KiB | ||
| int argSize = arg.Count; | ||
| if (argSize > maxArgSize) { | ||
| throw PythonOps.ValueError("fcntl bytes arg too long"); | ||
| } | ||
|
|
||
| if (!NativeConvert.TryToFcntlCommand(cmd, out FcntlCommand fcntlCommand)) { | ||
| throw PythonOps.OSError(PythonErrno.EINVAL, "unsupported fcntl command"); | ||
| } | ||
|
|
||
| var buf = new byte[maxArgSize]; | ||
| Array.Copy(arg.UnsafeByteArray, buf, argSize); | ||
|
|
||
| int result; | ||
| Errno errno; | ||
| unsafe { | ||
| fixed (byte* ptr = buf) { | ||
| do { | ||
| result = Syscall.fcntl(fd, fcntlCommand, (IntPtr)ptr); | ||
| } while (UnixMarshal.ShouldRetrySyscall(result, out errno)); | ||
| } | ||
| } | ||
|
|
||
| if (result == -1) { | ||
| return LightExceptions.Throw(PythonNT.GetOsError(NativeConvert.FromErrno(errno))); | ||
| } | ||
| byte[] response = new byte[argSize]; | ||
| Array.Copy(buf, response, argSize); | ||
| return Bytes.Make(response); | ||
| } | ||
|
|
||
|
|
||
| [LightThrowing] | ||
| public static object fcntl(int fd, int cmd, [Optional] object? arg) { | ||
| CheckFileDescriptor(fd); | ||
|
|
||
| long data = arg switch { | ||
| Missing => 0, | ||
| int i => i, | ||
| uint ui => ui, | ||
| long l => l, | ||
| ulong ul => (long)ul, | ||
| BigInteger bi => (long)bi, | ||
| Extensible<BigInteger> ebi => (long)ebi.Value, | ||
| _ => throw PythonOps.TypeErrorForBadInstance("integer argument expected, got {0}", arg) | ||
| }; | ||
|
|
||
| if (!NativeConvert.TryToFcntlCommand(cmd, out FcntlCommand fcntlCommand)) { | ||
| throw PythonOps.OSError(PythonErrno.EINVAL, "unsupported fcntl command"); | ||
| } | ||
|
|
||
| int result; | ||
| Errno errno; | ||
| do { | ||
| result = Syscall.fcntl(fd, fcntlCommand, data); | ||
| } while (UnixMarshal.ShouldRetrySyscall(result, out errno)); | ||
|
|
||
| if (result == -1) { | ||
| return LightExceptions.Throw(PythonNT.GetOsError(NativeConvert.FromErrno(errno))); | ||
| } | ||
| return ScriptingRuntimeHelpers.Int32ToObject(result); | ||
| } | ||
|
|
||
|
|
||
| [LightThrowing] | ||
| public static object fcntl(CodeContext context, object? fd, int cmd, object? arg = null) { | ||
| int fileno = GetFileDescriptor(context, fd); | ||
|
|
||
| if (arg is Bytes bytes) { | ||
| return fcntl(fileno, cmd, bytes); | ||
| } | ||
|
|
||
| return fcntl(fileno, cmd, arg); | ||
| } | ||
|
|
||
| #endregion | ||
|
|
||
|
|
||
| #region ioctl | ||
|
|
||
| // supporting fcntl.ioctl(fileno, termios.TIOCGWINSZ, buf) | ||
| // where buf = array.array('h', [0, 0, 0, 0]) | ||
| public static object ioctl(CodeContext context, int fd, int cmd, [NotNone] IBufferProtocol arg, int mutate_flag = 1) { | ||
|
|
@@ -53,10 +147,109 @@ public static object ioctl(CodeContext context, int fd, int cmd, [NotNone] IBuff | |
| throw new NotImplementedException($"ioctl: unsupported command {cmd}"); | ||
| } | ||
|
|
||
| #endregion | ||
|
|
||
|
|
||
| #region flock | ||
|
|
||
| [DllImport("libc", SetLastError = true, EntryPoint = "flock")] | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wonder what happens on Alpine. 😉
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will check 🤷
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It works! However,
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess my comment was more of a joke, but still unfortunate that |
||
| private static extern int _flock(int fd, int op); | ||
|
|
||
| [LightThrowing] | ||
| public static object? flock(int fd, int operation) { | ||
| CheckFileDescriptor(fd); | ||
|
|
||
| int result; | ||
| int errno = 0; | ||
| do { | ||
| result = _flock(fd, operation); | ||
| } while (result == -1 && (errno = Marshal.GetLastWin32Error()) == PythonErrno.EINTR); | ||
|
|
||
| if (result == -1) { | ||
| return LightExceptions.Throw(PythonNT.GetOsError(errno)); | ||
| } | ||
| return null; | ||
| } | ||
|
|
||
|
|
||
| [LightThrowing] | ||
| public static object? flock(CodeContext context, object? fd, int operation) | ||
| => flock(GetFileDescriptor(context, fd), operation); | ||
|
|
||
| #endregion | ||
|
|
||
|
|
||
| #region lockf | ||
|
|
||
| [LightThrowing] | ||
| public static object? lockf(int fd, int cmd, long len = 0, long start = 0, int whence = 0) { | ||
| CheckFileDescriptor(fd); | ||
|
|
||
| Flock flock = new() { | ||
| l_whence = (SeekFlags)whence, | ||
| l_start = start, | ||
| l_len = len | ||
| }; | ||
| if (cmd == LOCK_UN) { | ||
| flock.l_type = LockType.F_UNLCK; | ||
| } else if ((cmd & LOCK_SH) != 0) { | ||
| flock.l_type = LockType.F_RDLCK; | ||
| } else if ((cmd & LOCK_EX) != 0) { | ||
| flock.l_type = LockType.F_WRLCK; | ||
| } else { | ||
| throw PythonOps.ValueError("unrecognized lockf argument"); | ||
| } | ||
|
|
||
| int result; | ||
| Errno errno; | ||
| do { | ||
| result = Syscall.fcntl(fd, (cmd & LOCK_NB) != 0 ? FcntlCommand.F_SETLK : FcntlCommand.F_SETLKW, ref flock); | ||
| } while (UnixMarshal.ShouldRetrySyscall(result, out errno)); | ||
|
|
||
| if (result == -1) { | ||
| return LightExceptions.Throw(PythonNT.GetOsError(NativeConvert.FromErrno(errno))); | ||
| } | ||
| return null; | ||
| } | ||
|
|
||
|
|
||
| [LightThrowing] | ||
| public static object? lockf(CodeContext context, object? fd, int cmd, long len = 0, long start = 0, int whence = 0) | ||
| => lockf(GetFileDescriptor(context, fd), cmd, len, start, whence); | ||
|
|
||
| #endregion | ||
|
|
||
|
|
||
| #region Helper Methods | ||
|
|
||
| private static int GetFileDescriptor(CodeContext context, object? obj) { | ||
| if (!PythonOps.TryGetBoundAttr(context, obj, "fileno", out object? filenoMeth)) { | ||
| throw PythonOps.TypeError("argument must be an int, or have a fileno() method."); | ||
| } | ||
| return PythonCalls.Call(context, filenoMeth) switch { | ||
| int i => i, | ||
| uint ui => (int)ui, | ||
| BigInteger bi => (int)bi, | ||
| Extensible<BigInteger> ebi => (int)ebi.Value, | ||
| _ => throw PythonOps.TypeError("fileno() returned a non-integer") | ||
| }; | ||
| } | ||
|
|
||
|
|
||
| private static void CheckFileDescriptor(int fd) { | ||
| if (fd < 0) { | ||
| throw PythonOps.ValueError("file descriptor cannot be a negative integer ({0})", fd); | ||
| } | ||
| } | ||
|
|
||
| #endregion | ||
|
|
||
|
|
||
| // FD Flags | ||
| public static int FD_CLOEXEC = 1; | ||
| public static int FASYNC => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 0x0040 : 0x2000; | ||
|
|
||
| // O_* flags under F* name | ||
| public static int FASYNC => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 0x0040 : 0x2000; // O_ASYNC | ||
|
|
||
|
|
||
| #region Generated FD Commands | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the light throwing path expected to occur in normal use? If not then I would consider making a regular exception.
Similar question for the other light throwing methods below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It depends on the command but yes, sometimes. Those errors are very inexpensive to return so the usage pattern for some commands is simply try/error. For example handling errors
EAGAIN,EWOULDBLOCK.,EACCES. HavingLightThrowingmakes it relatively inexpensive here too. Nota bene the code still throws regular exceptions for programming errors like invalid value. That's intentional so that the errors do not go unnoticed when these calls are done from C#.