Skip to content

Commit fa6799b

Browse files
committed
Implement syscall ioctl
1 parent 65a7afc commit fa6799b

File tree

1 file changed

+169
-20
lines changed

1 file changed

+169
-20
lines changed

src/core/IronPython.Modules/fcntl.cs

Lines changed: 169 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#nullable enable
66

77
using System;
8+
using System.Buffers;
89
using System.Diagnostics;
910
using System.Numerics;
1011
using System.Reflection;
@@ -119,32 +120,180 @@ public static object fcntl(CodeContext context, object? fd, int cmd, object? arg
119120
#endregion
120121

121122

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);
123163

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+
}
129247

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);
136248

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);
141275
} 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);
145277
}
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);
146294
}
147-
throw new NotImplementedException($"ioctl: unsupported command {cmd}");
295+
296+
return ioctl(fileno, request, arg, mutate_flag);
148297
}
149298

150299
#endregion

0 commit comments

Comments
 (0)