|
5 | 5 | #nullable enable |
6 | 6 |
|
7 | 7 | using System; |
| 8 | +using System.Buffers; |
8 | 9 | using System.Diagnostics; |
9 | 10 | using System.Numerics; |
10 | 11 | using System.Reflection; |
@@ -119,32 +120,180 @@ public static object fcntl(CodeContext context, object? fd, int cmd, object? arg |
119 | 120 | #endregion |
120 | 121 |
|
121 | 122 |
|
122 | | - #region ioctl |
| 123 | + #region ioctl |
| 124 | + |
| 125 | + // The actual signature of ioctl is |
| 126 | + // |
| 127 | + // int ioctl(int, unsigned long, ...) |
| 128 | + // |
| 129 | + // but .NET, as of Jan 2025, still does not support varargs in P/Invoke [1] |
| 130 | + // so as a workaround, nonvararg prototypes are defined for each architecture. |
| 131 | + // [1]: https://github.com/dotnet/runtime/issues/48796 |
| 132 | + |
| 133 | +#if NET10_0_OR_GREATER |
| 134 | +#error Check if this version of .NET supports P/Invoke of variadic functions; if not, change the condition to recheck at next major .NET version |
| 135 | +#endif |
| 136 | + |
| 137 | + [DllImport("libc", SetLastError = true, EntryPoint = "ioctl")] |
| 138 | + private static extern unsafe int _ioctl(int fd, ulong request, void* arg); |
| 139 | + [DllImport("libc", SetLastError = true, EntryPoint = "ioctl")] |
| 140 | + private static extern int _ioctl(int fd, ulong request, long arg); |
| 141 | + |
| 142 | + [DllImport("libc", SetLastError = true, EntryPoint = "ioctl")] |
| 143 | + private static extern unsafe int _ioctl_arm64(int fd, ulong request, |
| 144 | + // pad register arguments (first 8) to force vararg on stack |
| 145 | + // ARM: https://github.com/ARM-software/abi-aa/blob/main/aapcs64/aapcs64.rst#appendix-variable-argument-lists |
| 146 | + // Apple: https://developer.apple.com/documentation/xcode/writing-arm64-code-for-apple-platforms |
| 147 | + nint r2, nint r3, nint r4, nint r5, nint r6, nint r7, |
| 148 | + void* arg); |
| 149 | + [DllImport("libc", SetLastError = true, EntryPoint = "ioctl")] |
| 150 | + private static extern int _ioctl_arm64(int fd, ulong request, |
| 151 | + nint r2, nint r3, nint r4, nint r5, nint r6, nint r7, |
| 152 | + long arg); |
| 153 | + |
| 154 | + |
| 155 | + // request will be int, uint or BigInteger, and in Python is limited to values that can fit in 32 bits (unchecked) |
| 156 | + // long should capture all allowed values |
| 157 | + // return value is int, bytes, or LightException |
| 158 | + [LightThrowing] |
| 159 | + public static object ioctl(int fd, long request, [NotNone] IBufferProtocol arg, bool mutate_flag = true) { |
| 160 | + CheckFileDescriptor(fd); |
| 161 | + |
| 162 | + ulong cmd = unchecked((ulong)request); |
123 | 163 |
|
124 | | - // supporting fcntl.ioctl(fileno, termios.TIOCGWINSZ, buf) |
125 | | - // where buf = array.array('h', [0, 0, 0, 0]) |
126 | | - public static object ioctl(CodeContext context, int fd, int cmd, [NotNone] IBufferProtocol arg, int mutate_flag = 1) { |
127 | | - if (cmd == PythonTermios.TIOCGWINSZ) { |
128 | | - using IPythonBuffer buf = arg.GetBuffer(); |
| 164 | + const int defaultBufSize = 1024; |
| 165 | + int bufSize; |
| 166 | + IPythonBuffer? buf = null; |
| 167 | + |
| 168 | + if (mutate_flag) { |
| 169 | + buf = arg.GetBufferNoThrow(BufferFlags.Writable); |
| 170 | + } |
| 171 | + if (buf is not null) { |
| 172 | + bufSize = buf.AsReadOnlySpan().Length; |
| 173 | + } else { |
| 174 | + buf = arg.GetBuffer(BufferFlags.Simple); |
| 175 | + bufSize = buf.AsReadOnlySpan().Length; |
| 176 | + if (bufSize > defaultBufSize) { |
| 177 | + buf.Dispose(); |
| 178 | + throw PythonOps.ValueError("ioctl bytes arg too long"); |
| 179 | + } |
| 180 | + mutate_flag = false; // return a buffer, not integer |
| 181 | + } |
| 182 | + bool in_place = bufSize > defaultBufSize; // only large buffers are mutated in place |
| 183 | + Debug.Assert(!in_place || mutate_flag); // in_place implies mutate_flag |
| 184 | + |
| 185 | +#if !NETCOREAPP |
| 186 | + throw new PlatformNotSupportedException("ioctl is not supported on Mono"); |
| 187 | +#else |
| 188 | + try { |
| 189 | + unsafe { |
| 190 | + MemoryHandle hmem = default; |
| 191 | + void* ptr = null; |
| 192 | + if (in_place) { |
| 193 | + hmem = buf.Pin(); |
| 194 | + } else { |
| 195 | + ptr = NativeMemory.AllocZeroed(defaultBufSize + 1); // +1 for extra nul byte |
| 196 | + } |
| 197 | + try { |
| 198 | + if (in_place) { |
| 199 | + ptr = hmem.Pointer; |
| 200 | + } else { |
| 201 | + Debug.Assert(bufSize <= defaultBufSize); |
| 202 | + var dest = new Span<byte>((byte*)ptr, bufSize); |
| 203 | + buf.AsReadOnlySpan().CopyTo(dest); |
| 204 | + } |
| 205 | + Debug.Assert(ptr != null); |
| 206 | + |
| 207 | + int result; |
| 208 | + Errno errno; |
| 209 | + do { |
| 210 | + if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64) { |
| 211 | + // workaround for Arm64 vararg calling convention (but not for ARM64EC on Windows) |
| 212 | + result = _ioctl_arm64(fd, cmd, 0, 0, 0, 0, 0, 0, ptr); |
| 213 | + } else { |
| 214 | + result = _ioctl(fd, cmd, ptr); |
| 215 | + } |
| 216 | + } while (UnixMarshal.ShouldRetrySyscall(result, out errno)); |
| 217 | + |
| 218 | + if (result == -1) { |
| 219 | + return LightExceptions.Throw(PythonNT.GetOsError(NativeConvert.FromErrno(errno))); |
| 220 | + } |
| 221 | + if (mutate_flag) { |
| 222 | + if (!in_place) { |
| 223 | + var src = new Span<byte>((byte*)ptr, bufSize); |
| 224 | + src.CopyTo(buf.AsSpan()); |
| 225 | + } |
| 226 | + return ScriptingRuntimeHelpers.Int32ToObject(result); |
| 227 | + } else { |
| 228 | + Debug.Assert(!in_place); |
| 229 | + byte[] response = new byte[bufSize]; |
| 230 | + var src = new Span<byte>((byte*)ptr, bufSize); |
| 231 | + src.CopyTo(response); |
| 232 | + return Bytes.Make(response); |
| 233 | + } |
| 234 | + } finally { |
| 235 | + if (in_place) { |
| 236 | + hmem.Dispose(); |
| 237 | + } else { |
| 238 | + NativeMemory.Free(ptr); |
| 239 | + } |
| 240 | + } |
| 241 | + } |
| 242 | + } finally { |
| 243 | + buf.Dispose(); |
| 244 | + } |
| 245 | +#endif |
| 246 | + } |
129 | 247 |
|
130 | | - Span<short> winsize = stackalloc short[4]; |
131 | | - winsize[0] = (short)Console.WindowHeight; |
132 | | - winsize[1] = (short)Console.WindowWidth; |
133 | | - winsize[2] = (short)Console.BufferHeight; // buffer height and width are not accurate on macOS |
134 | | - winsize[3] = (short)Console.BufferWidth; |
135 | | - Span<byte> payload = MemoryMarshal.Cast<short, byte>(winsize); |
136 | 248 |
|
137 | | - if (buf.IsReadOnly || mutate_flag == 0) { |
138 | | - byte[] res = buf.ToArray(); |
139 | | - payload.Slice(0, Math.Min(payload.Length, res.Length)).CopyTo(res); |
140 | | - return Bytes.Make(res); |
| 249 | + [LightThrowing] |
| 250 | + public static object ioctl(int fd, long request, [Optional] object? arg, bool mutate_flag = true) { |
| 251 | + CheckFileDescriptor(fd); |
| 252 | + |
| 253 | + ulong cmd = unchecked((ulong)request); |
| 254 | + |
| 255 | + long data = arg switch { |
| 256 | + Missing => 0, |
| 257 | + int i => i, |
| 258 | + uint ui => ui, |
| 259 | + long l => l, |
| 260 | + ulong ul => (long)ul, |
| 261 | + BigInteger bi => (long)bi, |
| 262 | + Extensible<BigInteger> ebi => (long)ebi.Value, |
| 263 | + _ => throw PythonOps.TypeErrorForBadInstance("integer argument expected, got {0}", arg) |
| 264 | + }; |
| 265 | + |
| 266 | +#if !NETCOREAPP |
| 267 | + throw new PlatformNotSupportedException("ioctl is not supported on Mono"); |
| 268 | +#else |
| 269 | + int result; |
| 270 | + Errno errno; |
| 271 | + do { |
| 272 | + if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64) { |
| 273 | + // workaround for Arm64 vararg calling convention (but not for ARM64EC on Windows) |
| 274 | + result = _ioctl_arm64(fd, cmd, 0, 0, 0, 0, 0, 0, data); |
141 | 275 | } else { |
142 | | - var res = buf.AsSpan(); |
143 | | - payload.Slice(0, Math.Min(payload.Length, res.Length)).CopyTo(res); |
144 | | - return 0; |
| 276 | + result = _ioctl(fd, cmd, data); |
145 | 277 | } |
| 278 | + } while (UnixMarshal.ShouldRetrySyscall(result, out errno)); |
| 279 | + |
| 280 | + if (result == -1) { |
| 281 | + return LightExceptions.Throw(PythonNT.GetOsError(NativeConvert.FromErrno(errno))); |
| 282 | + } |
| 283 | + return ScriptingRuntimeHelpers.Int32ToObject(result); |
| 284 | +#endif |
| 285 | + } |
| 286 | + |
| 287 | + |
| 288 | + [LightThrowing] |
| 289 | + public static object ioctl(CodeContext context, object? fd, long request, [Optional] object? arg, bool mutate_flag = true) { |
| 290 | + int fileno = GetFileDescriptor(context, fd); |
| 291 | + |
| 292 | + if (arg is IBufferProtocol bp) { |
| 293 | + return ioctl(fileno, request, bp, mutate_flag); |
146 | 294 | } |
147 | | - throw new NotImplementedException($"ioctl: unsupported command {cmd}"); |
| 295 | + |
| 296 | + return ioctl(fileno, request, arg, mutate_flag); |
148 | 297 | } |
149 | 298 |
|
150 | 299 | #endregion |
|
0 commit comments