Skip to content

Commit a0c3377

Browse files
committed
[GR-53369][GR-53762][GR-53765] Implement fcntl.ioctl
PullRequest: graalpython/3321
2 parents 47abfbb + 02149c1 commit a0c3377

File tree

22 files changed

+497
-253
lines changed

22 files changed

+497
-253
lines changed

graalpython/com.oracle.graal.python.test/src/tests/test_fcntl.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved.
1+
# Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
22
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
33
#
44
# The Universal Permissive License (UPL), Version 1.0
@@ -111,3 +111,28 @@ def test_flock_s_and_s(self):
111111
finally:
112112
fcntl.flock(file, fcntl.LOCK_UN)
113113
os.close(file)
114+
115+
116+
@unittest.skipUnless(sys.platform == 'linux', "Linux only test")
117+
@unittest.skipUnless(__graalpython__.posix_module_backend() != 'java', "No ioctl in emulated backend")
118+
class IoctlTests(unittest.TestCase):
119+
# Taken from CPython test_ioctl.py which unfortunately skips the whole file when not in a terminal
120+
def test_ioctl_signed_unsigned_code_param(self):
121+
import pty, termios, struct
122+
mfd, sfd = pty.openpty()
123+
try:
124+
if termios.TIOCSWINSZ < 0:
125+
set_winsz_opcode_maybe_neg = termios.TIOCSWINSZ
126+
set_winsz_opcode_pos = termios.TIOCSWINSZ & 0xffffffff
127+
else:
128+
set_winsz_opcode_pos = termios.TIOCSWINSZ
129+
set_winsz_opcode_maybe_neg, = struct.unpack("i",
130+
struct.pack("I", termios.TIOCSWINSZ))
131+
132+
our_winsz = struct.pack("HHHH",80,25,0,0)
133+
# test both with a positive and potentially negative ioctl code
134+
new_winsz = fcntl.ioctl(mfd, set_winsz_opcode_pos, our_winsz)
135+
new_winsz = fcntl.ioctl(mfd, set_winsz_opcode_maybe_neg, our_winsz)
136+
finally:
137+
os.close(mfd)
138+
os.close(sfd)

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/FcntlModuleBuiltins.java

Lines changed: 161 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* The Universal Permissive License (UPL), Version 1.0
@@ -60,6 +60,9 @@
6060
import com.oracle.graal.python.builtins.modules.FcntlModuleBuiltinsClinicProviders.FlockNodeClinicProviderGen;
6161
import com.oracle.graal.python.builtins.modules.PosixModuleBuiltins.FileDescriptorConversionNode;
6262
import com.oracle.graal.python.builtins.objects.PNone;
63+
import com.oracle.graal.python.builtins.objects.buffer.PythonBufferAccessLibrary;
64+
import com.oracle.graal.python.builtins.objects.buffer.PythonBufferAcquireLibrary;
65+
import com.oracle.graal.python.lib.PyLongAsIntNode;
6366
import com.oracle.graal.python.lib.PyLongAsLongNode;
6467
import com.oracle.graal.python.nodes.ErrorMessages;
6568
import com.oracle.graal.python.nodes.PConstructAndRaiseNode;
@@ -68,10 +71,16 @@
6871
import com.oracle.graal.python.nodes.function.builtins.PythonBinaryClinicBuiltinNode;
6972
import com.oracle.graal.python.nodes.function.builtins.PythonClinicBuiltinNode;
7073
import com.oracle.graal.python.nodes.function.builtins.clinic.ArgumentClinicProvider;
74+
import com.oracle.graal.python.nodes.util.CannotCastException;
75+
import com.oracle.graal.python.nodes.util.CastToTruffleStringNode;
76+
import com.oracle.graal.python.runtime.GilNode;
77+
import com.oracle.graal.python.runtime.IndirectCallData;
7178
import com.oracle.graal.python.runtime.PosixConstants;
7279
import com.oracle.graal.python.runtime.PosixConstants.IntConstant;
7380
import com.oracle.graal.python.runtime.PosixSupportLibrary;
7481
import com.oracle.graal.python.runtime.PosixSupportLibrary.PosixException;
82+
import com.oracle.graal.python.runtime.exception.PException;
83+
import com.oracle.graal.python.runtime.object.PythonObjectFactory;
7584
import com.oracle.truffle.api.dsl.Bind;
7685
import com.oracle.truffle.api.dsl.Cached;
7786
import com.oracle.truffle.api.dsl.GenerateNodeFactory;
@@ -80,6 +89,7 @@
8089
import com.oracle.truffle.api.frame.VirtualFrame;
8190
import com.oracle.truffle.api.library.CachedLibrary;
8291
import com.oracle.truffle.api.nodes.Node;
92+
import com.oracle.truffle.api.strings.TruffleString;
8393

8494
@CoreFunctions(defineModule = "fcntl")
8595
public final class FcntlModuleBuiltins extends PythonBuiltins {
@@ -176,4 +186,154 @@ protected ArgumentClinicProvider getArgumentClinic() {
176186
return FcntlModuleBuiltinsClinicProviders.LockfNodeClinicProviderGen.INSTANCE;
177187
}
178188
}
189+
190+
@Builtin(name = "ioctl", minNumOfPositionalArgs = 2, parameterNames = {"fd", "request", "arg", "mutate_flag"})
191+
@ArgumentClinic(name = "fd", conversionClass = FileDescriptorConversionNode.class)
192+
@ArgumentClinic(name = "request", conversion = ClinicConversion.Long)
193+
@ArgumentClinic(name = "mutate_flag", conversion = ClinicConversion.Boolean, defaultValue = "true")
194+
@GenerateNodeFactory
195+
abstract static class IoctlNode extends PythonClinicBuiltinNode {
196+
private static final int IOCTL_BUFSZ = 1024;
197+
198+
@Specialization
199+
Object ioctl(VirtualFrame frame, int fd, long request, Object arg, boolean mutateArg,
200+
@Bind("this") Node inliningTarget,
201+
@CachedLibrary("getPosixSupport()") PosixSupportLibrary posixLib,
202+
@CachedLibrary(limit = "3") PythonBufferAcquireLibrary acquireLib,
203+
@CachedLibrary(limit = "3") PythonBufferAccessLibrary bufferLib,
204+
@Cached("createFor(this)") IndirectCallData indirectCallData,
205+
@Cached PyLongAsIntNode asIntNode,
206+
@Cached CastToTruffleStringNode castToString,
207+
@Cached TruffleString.SwitchEncodingNode switchEncodingNode,
208+
@Cached TruffleString.CopyToByteArrayNode copyToByteArrayNode,
209+
@Cached GilNode gilNode,
210+
@Cached PRaiseNode.Lazy raiseNode,
211+
@Cached PConstructAndRaiseNode.Lazy constructAndRaiseNode,
212+
@Cached PythonObjectFactory factory,
213+
@Cached SysModuleBuiltins.AuditNode auditNode) {
214+
auditNode.audit(inliningTarget, "fcnt.ioctl", fd, request, arg);
215+
216+
try {
217+
int intArg = 0;
218+
if (arg != PNone.NO_VALUE) {
219+
Object buffer = null;
220+
// Buffer argument
221+
if (acquireLib.hasBuffer(arg)) {
222+
boolean writable = false;
223+
try {
224+
buffer = acquireLib.acquireWritable(arg, frame, indirectCallData);
225+
writable = true;
226+
} catch (PException e) {
227+
try {
228+
buffer = acquireLib.acquireReadonly(arg, frame, indirectCallData);
229+
} catch (PException e1) {
230+
// ignore
231+
}
232+
}
233+
if (buffer != null) {
234+
try {
235+
int len = bufferLib.getBufferLength(buffer);
236+
boolean writeBack = false;
237+
boolean releaseGil = true;
238+
byte[] ioctlArg = null;
239+
if (writable && mutateArg) {
240+
writeBack = true;
241+
if (bufferLib.hasInternalByteArray(buffer)) {
242+
byte[] internalArray = bufferLib.getInternalByteArray(buffer);
243+
if (internalArray.length > len && internalArray[len] == 0) {
244+
writeBack = false;
245+
releaseGil = false; // Could resize concurrently
246+
ioctlArg = internalArray;
247+
}
248+
}
249+
} else {
250+
if (len > IOCTL_BUFSZ) {
251+
throw raiseNode.get(inliningTarget).raise(ValueError, ErrorMessages.IOCTL_STRING_ARG_TOO_LONG);
252+
}
253+
}
254+
if (ioctlArg == null) {
255+
ioctlArg = new byte[len + 1];
256+
bufferLib.readIntoByteArray(buffer, 0, ioctlArg, 0, len);
257+
}
258+
try {
259+
int ret = callIoctlBytes(frame, inliningTarget, fd, request, ioctlArg, releaseGil, posixLib, gilNode, constructAndRaiseNode);
260+
if (writable && mutateArg) {
261+
return ret;
262+
} else {
263+
return factory.createBytes(ioctlArg, len);
264+
}
265+
} finally {
266+
if (writeBack) {
267+
bufferLib.writeFromByteArray(buffer, 0, ioctlArg, 0, len);
268+
}
269+
}
270+
} finally {
271+
bufferLib.release(buffer, frame, indirectCallData);
272+
}
273+
}
274+
}
275+
// string arg
276+
TruffleString stringArg = null;
277+
try {
278+
stringArg = castToString.execute(inliningTarget, arg);
279+
} catch (CannotCastException e) {
280+
// ignore
281+
}
282+
if (stringArg != null) {
283+
TruffleString.Encoding utf8 = TruffleString.Encoding.UTF_8;
284+
stringArg = switchEncodingNode.execute(stringArg, utf8);
285+
int len = stringArg.byteLength(utf8);
286+
if (len > IOCTL_BUFSZ) {
287+
throw raiseNode.get(inliningTarget).raise(ValueError, ErrorMessages.IOCTL_STRING_ARG_TOO_LONG);
288+
}
289+
byte[] ioctlArg = new byte[len + 1];
290+
copyToByteArrayNode.execute(stringArg, 0, ioctlArg, 0, len, utf8);
291+
callIoctlBytes(frame, inliningTarget, fd, request, ioctlArg, true, posixLib, gilNode, constructAndRaiseNode);
292+
return factory.createBytes(ioctlArg, len);
293+
}
294+
295+
// int arg
296+
intArg = asIntNode.execute(frame, inliningTarget, arg);
297+
// fall through
298+
}
299+
300+
// default arg or int arg
301+
try {
302+
gilNode.release(true);
303+
try {
304+
return posixLib.ioctlInt(getPosixSupport(), fd, request, intArg);
305+
} finally {
306+
gilNode.acquire();
307+
}
308+
} catch (PosixException e) {
309+
throw constructAndRaiseNode.get(inliningTarget).raiseOSErrorFromPosixException(frame, e);
310+
}
311+
} catch (PosixSupportLibrary.UnsupportedPosixFeatureException e) {
312+
throw constructAndRaiseNode.get(inliningTarget).raiseOSErrorUnsupported(frame, e);
313+
}
314+
}
315+
316+
private int callIoctlBytes(VirtualFrame frame, Node inliningTarget, int fd, long request, byte[] ioctlArg, boolean releaseGil, PosixSupportLibrary posixLib, GilNode gilNode,
317+
PConstructAndRaiseNode.Lazy constructAndRaiseNode) {
318+
try {
319+
if (releaseGil) {
320+
gilNode.release(true);
321+
}
322+
try {
323+
return posixLib.ioctlBytes(getPosixSupport(), fd, request, ioctlArg);
324+
} finally {
325+
if (releaseGil) {
326+
gilNode.acquire();
327+
}
328+
}
329+
} catch (PosixException e) {
330+
throw constructAndRaiseNode.get(inliningTarget).raiseOSErrorFromPosixException(frame, e);
331+
}
332+
}
333+
334+
@Override
335+
protected ArgumentClinicProvider getArgumentClinic() {
336+
return FcntlModuleBuiltinsClinicProviders.IoctlNodeClinicProviderGen.INSTANCE;
337+
}
338+
}
179339
}

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/SREModuleBuiltins.java

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
import com.oracle.graal.python.builtins.objects.PNone;
6060
import com.oracle.graal.python.builtins.objects.buffer.PythonBufferAccessLibrary;
6161
import com.oracle.graal.python.builtins.objects.buffer.PythonBufferAcquireLibrary;
62+
import com.oracle.graal.python.builtins.objects.cext.common.NativePointer;
6263
import com.oracle.graal.python.builtins.objects.exception.PBaseException;
6364
import com.oracle.graal.python.builtins.objects.module.PythonModule;
6465
import com.oracle.graal.python.builtins.objects.object.PythonObject;
@@ -81,7 +82,6 @@
8182
import com.oracle.graal.python.nodes.function.builtins.PythonSenaryBuiltinNode;
8283
import com.oracle.graal.python.nodes.function.builtins.PythonTernaryBuiltinNode;
8384
import com.oracle.graal.python.nodes.truffle.PythonArithmeticTypes;
84-
import com.oracle.graal.python.nodes.util.BufferToTruffleStringNode;
8585
import com.oracle.graal.python.nodes.util.CannotCastException;
8686
import com.oracle.graal.python.nodes.util.CastToTruffleStringNode;
8787
import com.oracle.graal.python.runtime.IndirectCallData;
@@ -810,7 +810,7 @@ Object doCached(VirtualFrame frame, @SuppressWarnings("unused") Object callable,
810810
binaryProfile.enter(inliningTarget);
811811
// It's bytes or other buffer object
812812
buffer = bufferAcquireLib.acquireReadonly(inputStringOrBytes, frame, indirectCallData);
813-
input = getBufferToTruffleStringNode().execute(buffer, 0);
813+
input = getBufferToTruffleStringNode().execute(buffer);
814814
}
815815
try {
816816
return interop.invokeMember(cachedCallable, "exec", input, fromIndex);
@@ -844,5 +844,47 @@ private BufferToTruffleStringNode getBufferToTruffleStringNode() {
844844
}
845845
return bufferToTruffleStringNode;
846846
}
847+
848+
@GenerateInline(false)
849+
abstract static class BufferToTruffleStringNode extends PNodeWithContext {
850+
851+
public abstract TruffleString execute(Object buffer);
852+
853+
@Specialization(limit = "4")
854+
static TruffleString convert(Object buffer,
855+
@Bind("this") Node inliningTarget,
856+
@CachedLibrary(value = "buffer") PythonBufferAccessLibrary bufferLib,
857+
@Cached TruffleString.FromByteArrayNode fromByteArrayNode,
858+
@Cached TruffleString.FromNativePointerNode fromNativePointerNode,
859+
@Cached InlinedBranchProfile internalArrayProfile,
860+
@Cached InlinedBranchProfile nativeProfile,
861+
@Cached InlinedBranchProfile fallbackProfile) {
862+
PythonBufferAccessLibrary.assertIsBuffer(buffer);
863+
int len = bufferLib.getBufferLength(buffer);
864+
if (bufferLib.hasInternalByteArray(buffer)) {
865+
internalArrayProfile.enter(inliningTarget);
866+
byte[] bytes = bufferLib.getInternalByteArray(buffer);
867+
return fromByteArrayNode.execute(bytes, 0, len, TruffleString.Encoding.ISO_8859_1, false);
868+
}
869+
if (bufferLib.isNative(buffer)) {
870+
nativeProfile.enter(inliningTarget);
871+
Object ptr = bufferLib.getNativePointer(buffer);
872+
if (ptr != null) {
873+
if (ptr instanceof Long lptr) {
874+
ptr = new NativePointer(lptr);
875+
}
876+
return fromNativePointerNode.execute(ptr, 0, len, TruffleString.Encoding.ISO_8859_1, false);
877+
}
878+
}
879+
fallbackProfile.enter(inliningTarget);
880+
byte[] bytes = bufferLib.getCopiedByteArray(buffer);
881+
return fromByteArrayNode.execute(bytes, 0, len, TruffleString.Encoding.ISO_8859_1, false);
882+
}
883+
884+
@NeverDefault
885+
public static BufferToTruffleStringNode create() {
886+
return SREModuleBuiltinsFactory.TRegexCallExecFactory.BufferToTruffleStringNodeGen.create();
887+
}
888+
}
847889
}
848890
}

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/ctypes/CDataObject.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,20 @@ void writeFromByteArray(int destOffset, byte[] src, int srcOffset, int length,
167167
writeBytesNode.execute(inliningTarget, b_ptr.withOffset(destOffset), src, srcOffset, length);
168168
}
169169

170+
@ExportMessage
171+
boolean isNative(
172+
@Bind("$node") Node inliningTarget,
173+
@Cached PointerNodes.PointerIsNativeNode pointerIsNativeNode) {
174+
return pointerIsNativeNode.execute(inliningTarget, b_ptr);
175+
}
176+
177+
@ExportMessage
178+
Object getNativePointer(
179+
@Bind("$node") Node inliningTarget,
180+
@Cached PointerNodes.GetPointerValueAsObjectNode getPointerValue) {
181+
return getPointerValue.execute(inliningTarget, b_ptr);
182+
}
183+
170184
// TODO we could expose the internal array if available
171185

172186
@SuppressWarnings("static-method")

0 commit comments

Comments
 (0)