Skip to content

Commit 9543b24

Browse files
committed
Native implementation of execv
1 parent 0c34ea4 commit 9543b24

File tree

9 files changed

+245
-131
lines changed

9 files changed

+245
-131
lines changed

graalpython/com.oracle.graal.python.cext/posix/posix.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,19 @@ int32_t call_setenv(char *name, char *value, int overwrite) {
488488
return setenv(name, value, overwrite);
489489
}
490490

491+
// See comment in NFiPosixSupport.execv() for the description of arguments
492+
void call_execv(char *data, int64_t *offsets, int32_t offsetsLen) {
493+
// We reuse the memory allocated for offsets to avoid the need to allocate and reliably free another array
494+
char **strings = (char **) offsets;
495+
for (int32_t i = 0; i < offsetsLen; ++i) {
496+
strings[i] = offsets[i] == -1 ? NULL : data + offsets[i];
497+
}
498+
499+
char *pathname = strings[0];
500+
char **argv = strings + 1;
501+
execv(pathname, argv);
502+
}
503+
491504
int32_t get_errno() {
492505
return errno;
493506
}

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

Lines changed: 104 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,9 @@
3131
import static com.oracle.graal.python.runtime.exception.PythonErrorType.TypeError;
3232
import static com.oracle.graal.python.runtime.exception.PythonErrorType.ValueError;
3333

34-
import java.io.BufferedReader;
3534
import java.io.File;
3635
import java.io.IOException;
3736
import java.io.InputStream;
38-
import java.io.InputStreamReader;
3937
import java.io.OutputStream;
4038
import java.lang.ProcessBuilder.Redirect;
4139
import java.math.BigInteger;
@@ -58,14 +56,16 @@
5856
import com.oracle.graal.python.builtins.CoreFunctions;
5957
import com.oracle.graal.python.builtins.PythonBuiltinClassType;
6058
import com.oracle.graal.python.builtins.PythonBuiltins;
59+
import com.oracle.graal.python.builtins.modules.SysModuleBuiltins.AuditNode;
6160
import com.oracle.graal.python.builtins.objects.PNone;
6261
import com.oracle.graal.python.builtins.objects.bytes.BytesNodes;
6362
import com.oracle.graal.python.builtins.objects.bytes.BytesUtils;
6463
import com.oracle.graal.python.builtins.objects.bytes.PBytes;
6564
import com.oracle.graal.python.builtins.objects.bytes.PBytesLike;
6665
import com.oracle.graal.python.builtins.objects.common.SequenceNodes.LenNode;
67-
import com.oracle.graal.python.builtins.objects.common.SequenceStorageNodes.GetItemDynamicNode;
66+
import com.oracle.graal.python.builtins.objects.common.SequenceStorageNodes;
6867
import com.oracle.graal.python.builtins.objects.common.SequenceStorageNodes.GetItemNode;
68+
import com.oracle.graal.python.builtins.objects.common.SequenceStorageNodes.ToArrayNode;
6969
import com.oracle.graal.python.builtins.objects.dict.PDict;
7070
import com.oracle.graal.python.builtins.objects.exception.OSErrorEnum;
7171
import com.oracle.graal.python.builtins.objects.floats.PFloat;
@@ -80,6 +80,7 @@
8080
import com.oracle.graal.python.builtins.objects.tuple.StructSequence;
8181
import com.oracle.graal.python.nodes.ErrorMessages;
8282
import com.oracle.graal.python.nodes.PGuards;
83+
import com.oracle.graal.python.nodes.PNodeWithRaise;
8384
import com.oracle.graal.python.nodes.PRaiseNode;
8485
import com.oracle.graal.python.nodes.call.special.LookupAndCallBinaryNode;
8586
import com.oracle.graal.python.nodes.expression.BinaryArithmetic;
@@ -117,6 +118,7 @@
117118
import com.oracle.truffle.api.TruffleLogger;
118119
import com.oracle.truffle.api.dsl.Cached;
119120
import com.oracle.truffle.api.dsl.Cached.Shared;
121+
import com.oracle.truffle.api.dsl.CachedContext;
120122
import com.oracle.truffle.api.dsl.Fallback;
121123
import com.oracle.truffle.api.dsl.GenerateNodeFactory;
122124
import com.oracle.truffle.api.dsl.ImportStatic;
@@ -126,6 +128,7 @@
126128
import com.oracle.truffle.api.frame.VirtualFrame;
127129
import com.oracle.truffle.api.interop.UnsupportedMessageException;
128130
import com.oracle.truffle.api.library.CachedLibrary;
131+
import com.oracle.truffle.api.nodes.Node;
129132
import com.oracle.truffle.api.profiles.BranchProfile;
130133
import com.oracle.truffle.api.profiles.ConditionProfile;
131134

@@ -379,86 +382,65 @@ private void checkEqualSign(byte[] bytes) {
379382
}
380383
}
381384

382-
@Builtin(name = "execv", minNumOfPositionalArgs = 3, declaresExplicitSelf = true)
385+
@Builtin(name = "execv", minNumOfPositionalArgs = 2, parameterNames = {"pathname", "argv"})
386+
@ArgumentClinic(name = "pathname", conversionClass = PathConversionNode.class, args = {"false", "false"})
383387
@GenerateNodeFactory
384-
public abstract static class ExecvNode extends PythonBuiltinNode {
385-
@Child private BytesNodes.ToBytesNode toBytes = BytesNodes.ToBytesNode.create();
388+
public abstract static class ExecvNode extends PythonBinaryClinicBuiltinNode {
386389

387-
@Specialization
388-
Object execute(VirtualFrame frame, PythonModule thisModule, String path, PList args) {
389-
return doExecute(frame, thisModule, path, args);
390+
@Override
391+
protected ArgumentClinicProvider getArgumentClinic() {
392+
return PosixModuleBuiltinsClinicProviders.ExecvNodeClinicProviderGen.INSTANCE;
390393
}
391394

392395
@Specialization
393-
Object execute(VirtualFrame frame, PythonModule thisModule, String path, PTuple args) {
394-
// in case of execl the PList happens to be in the tuples first entry
395-
Object list = GetItemDynamicNode.getUncached().execute(args.getSequenceStorage(), 0);
396-
return doExecute(frame, thisModule, path, list instanceof PList ? (PList) list : args);
396+
Object execvArgsList(VirtualFrame frame, PosixPath path, PList argv,
397+
@CachedLibrary("getPosixSupport()") PosixSupportLibrary posixLib,
398+
@Cached ToArrayNode toArrayNode,
399+
@Cached ObjectToOpaquePathNode toOpaquePathNode,
400+
@Cached SysModuleBuiltins.AuditNode auditNode) {
401+
execv(frame, path, argv, argv.getSequenceStorage(), posixLib, toArrayNode, toOpaquePathNode, auditNode);
402+
throw CompilerDirectives.shouldNotReachHere("execv should not return normally");
397403
}
398404

399-
@Specialization(limit = "1")
400-
Object executePath(VirtualFrame frame, PythonModule thisModule, Object path, PTuple args,
401-
@CachedLibrary("path") PythonObjectLibrary lib) {
402-
return execute(frame, thisModule, lib.asPath(path), args);
405+
@Specialization
406+
Object execvArgsTuple(VirtualFrame frame, PosixPath path, PTuple argv,
407+
@CachedLibrary("getPosixSupport()") PosixSupportLibrary posixLib,
408+
@Cached ToArrayNode toArrayNode,
409+
@Cached ObjectToOpaquePathNode toOpaquePathNode,
410+
@Cached AuditNode auditNode) {
411+
execv(frame, path, argv, argv.getSequenceStorage(), posixLib, toArrayNode, toOpaquePathNode, auditNode);
412+
throw CompilerDirectives.shouldNotReachHere("execv should not return normally");
403413
}
404414

405-
@Specialization(limit = "1")
406-
Object executePath(VirtualFrame frame, PythonModule thisModule, Object path, PList args,
407-
@CachedLibrary("path") PythonObjectLibrary lib) {
408-
return doExecute(frame, thisModule, lib.asPath(path), args);
415+
@Specialization(guards = {"!isList(argv)", "!isTuple(argv)"})
416+
@SuppressWarnings("unused")
417+
Object execvInvalidArgs(VirtualFrame frame, PosixPath path, Object argv) {
418+
throw raise(TypeError, ErrorMessages.ARG_D_MUST_BE_S, "execv()", 2, "tuple or list");
409419
}
410420

411-
Object doExecute(VirtualFrame frame, PythonModule thisModule, String path, PSequence args) {
412-
if (!getContext().isExecutableAccessAllowed()) {
413-
throw raiseOSError(frame, OSErrorEnum.EPERM);
421+
private void execv(VirtualFrame frame, PosixPath path, Object argv, SequenceStorage argvStorage,
422+
PosixSupportLibrary posixLib,
423+
SequenceStorageNodes.ToArrayNode toArrayNode,
424+
ObjectToOpaquePathNode toOpaquePathNode,
425+
SysModuleBuiltins.AuditNode auditNode) {
426+
Object[] args = toArrayNode.execute(argvStorage);
427+
if (args.length < 1) {
428+
throw raise(ValueError, ErrorMessages.ARG_MUST_NOT_BE_EMPTY, "execv()", 2);
414429
}
415-
try {
416-
return doExecuteInternal(thisModule, path, args);
417-
} catch (Exception e) {
418-
throw raiseOSError(frame, e, path);
430+
Object[] opaqueArgs = new Object[args.length];
431+
for (int i = 0; i < args.length; ++i) {
432+
opaqueArgs[i] = toOpaquePathNode.execute(frame, args[i]);
419433
}
420-
}
434+
//TODO ValueError "execv() arg 2 first element cannot be empty"
435+
436+
auditNode.audit("os.exec", path.originalObject, argv, PNone.NONE);
421437

422-
@TruffleBoundary
423-
Object doExecuteInternal(PythonModule thisModule, String path, PSequence args) throws IOException {
424-
int size = args.getSequenceStorage().length();
425-
if (size == 0) {
426-
throw raise(ValueError, ErrorMessages.ARG_D_MUST_NOT_BE_EMPTY, 2);
427-
}
428-
String[] cmd = new String[size];
429-
// We don't need the path variable because it's already in the array
430-
// but I need to process it for CI gate
431-
cmd[0] = path;
432-
for (int i = 0; i < size; i++) {
433-
cmd[i] = GetItemDynamicNode.getUncached().execute(args.getSequenceStorage(), i).toString();
434-
}
435-
PDict environ = (PDict) thisModule.getAttribute("environ");
436-
ProcessBuilder builder = new ProcessBuilder(cmd);
437-
Map<String, String> environment = builder.environment();
438-
environ.entries().forEach(entry -> {
439-
environment.put(new String(toBytes.execute(entry.key)), new String(toBytes.execute(entry.value)));
440-
});
441-
Process pr = builder.start();
442-
BufferedReader bfr = new BufferedReader(new InputStreamReader(pr.getInputStream()));
443-
OutputStream stream = getContext().getEnv().out();
444-
String line = "";
445-
while ((line = bfr.readLine()) != null) {
446-
stream.write(line.getBytes());
447-
stream.write("\n".getBytes());
448-
}
449-
BufferedReader stderr = new BufferedReader(new InputStreamReader(pr.getErrorStream()));
450-
OutputStream errStream = getContext().getEnv().err();
451-
line = "";
452-
while ((line = stderr.readLine()) != null) {
453-
errStream.write(line.getBytes());
454-
errStream.write("\n".getBytes());
455-
}
456438
try {
457-
pr.waitFor();
458-
} catch (InterruptedException e) {
459-
throw new IOException(e);
439+
posixLib.execv(getPosixSupport(), path.value, opaqueArgs);
440+
} catch (PosixException e) {
441+
throw raiseOSErrorFromPosixException(frame, e);
460442
}
461-
throw new PythonExitException(this, pr.exitValue());
443+
throw CompilerDirectives.shouldNotReachHere("execv should not return normally");
462444
}
463445
}
464446

@@ -2141,6 +2123,61 @@ PBytes doBytes(PBytes bytes) {
21412123
}
21422124
}
21432125

2126+
/**
2127+
* Helper node that accepts either str or bytes and converts it to a representation specific to
2128+
* the {@link PosixSupportLibrary} in use. Basically equivalent of
2129+
* {@code PyUnicode_EncodeFSDefault}.
2130+
*/
2131+
abstract static class StringOrBytesToOpaquePathNode extends PNodeWithRaise {
2132+
abstract Object execute(Object obj);
2133+
2134+
@Specialization(limit = "1")
2135+
Object doString(String str,
2136+
@CachedContext(PythonLanguage.class) PythonContext context,
2137+
@CachedLibrary("context.getPosixSupport()") PosixSupportLibrary posixLib) {
2138+
return checkPath(posixLib.createPathFromString(context.getPosixSupport(), str));
2139+
}
2140+
2141+
@Specialization(limit = "1")
2142+
Object doPString(PString pstr,
2143+
@Cached CastToJavaStringNode castToJavaStringNode,
2144+
@CachedContext(PythonLanguage.class) PythonContext context,
2145+
@CachedLibrary("context.getPosixSupport()") PosixSupportLibrary posixLib) {
2146+
String str = castToJavaStringNode.execute(pstr);
2147+
return checkPath(posixLib.createPathFromString(context.getPosixSupport(), str));
2148+
}
2149+
2150+
@Specialization(limit = "1")
2151+
Object doBytes(PBytes bytes,
2152+
@Cached BytesNodes.ToBytesNode toBytesNode,
2153+
@CachedContext(PythonLanguage.class) PythonContext context,
2154+
@CachedLibrary("context.getPosixSupport()") PosixSupportLibrary posixLib) {
2155+
return checkPath(posixLib.createPathFromBytes(context.getPosixSupport(), toBytesNode.execute(bytes)));
2156+
}
2157+
2158+
private Object checkPath(Object path) {
2159+
if (path == null) {
2160+
throw raise(ValueError, ErrorMessages.EMBEDDED_NULL_BYTE);
2161+
}
2162+
return path;
2163+
}
2164+
}
2165+
2166+
/**
2167+
* Similar to {@code PyUnicode_FSConverter}, but the actual conversion is delegated to the
2168+
* {@link PosixSupportLibrary} implementation.
2169+
*/
2170+
abstract static class ObjectToOpaquePathNode extends Node {
2171+
abstract Object execute(VirtualFrame frame, Object obj);
2172+
2173+
@Specialization
2174+
Object doIt(VirtualFrame frame, Object obj,
2175+
@Cached FspathNode fspathNode,
2176+
@Cached StringOrBytesToOpaquePathNode stringOrBytesToOpaquePathNode) {
2177+
return stringOrBytesToOpaquePathNode.execute(fspathNode.call(frame, obj));
2178+
}
2179+
}
2180+
21442181
abstract static class ConvertToTimespecBaseNode extends PythonBuiltinBaseNode {
21452182
abstract void execute(VirtualFrame frame, Object obj, long[] timespec, int offset);
21462183
}

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

Lines changed: 1 addition & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -55,22 +55,19 @@
5555
import com.oracle.graal.python.builtins.Builtin;
5656
import com.oracle.graal.python.builtins.CoreFunctions;
5757
import com.oracle.graal.python.builtins.PythonBuiltins;
58-
import com.oracle.graal.python.builtins.modules.PosixModuleBuiltins.FspathNode;
58+
import com.oracle.graal.python.builtins.modules.PosixModuleBuiltins.ObjectToOpaquePathNode;
5959
import com.oracle.graal.python.builtins.modules.PosixSubprocessModuleBuiltinsClinicProviders.NewForkExecNodeClinicProviderGen;
6060
import com.oracle.graal.python.builtins.modules.PosixSubprocessModuleBuiltinsFactory.EnvConversionNodeGen;
6161
import com.oracle.graal.python.builtins.modules.PosixSubprocessModuleBuiltinsFactory.ProcessArgsConversionNodeGen;
6262
import com.oracle.graal.python.builtins.objects.PNone;
6363
import com.oracle.graal.python.builtins.objects.bytes.BytesNodes.ToBytesNode;
64-
import com.oracle.graal.python.builtins.objects.bytes.PBytes;
6564
import com.oracle.graal.python.builtins.objects.common.SequenceNodes.GetSequenceStorageNode;
6665
import com.oracle.graal.python.builtins.objects.common.SequenceStorageNodes.GetItemNode;
6766
import com.oracle.graal.python.builtins.objects.common.SequenceStorageNodes.LenNode;
6867
import com.oracle.graal.python.builtins.objects.object.PythonObjectLibrary;
69-
import com.oracle.graal.python.builtins.objects.str.PString;
7068
import com.oracle.graal.python.builtins.objects.tuple.PTuple;
7169
import com.oracle.graal.python.nodes.ErrorMessages;
7270
import com.oracle.graal.python.nodes.PGuards;
73-
import com.oracle.graal.python.nodes.PNodeWithRaise;
7471
import com.oracle.graal.python.nodes.builtins.ListNodes.FastConstructListNode;
7572
import com.oracle.graal.python.nodes.function.PythonBuiltinBaseNode;
7673
import com.oracle.graal.python.nodes.function.builtins.PythonClinicBuiltinNode;
@@ -79,7 +76,6 @@
7976
import com.oracle.graal.python.nodes.object.IsBuiltinClassProfile;
8077
import com.oracle.graal.python.nodes.util.CannotCastException;
8178
import com.oracle.graal.python.nodes.util.CastToJavaIntExactNode;
82-
import com.oracle.graal.python.nodes.util.CastToJavaStringNode;
8379
import com.oracle.graal.python.runtime.PosixSupportLibrary;
8480
import com.oracle.graal.python.runtime.PosixSupportLibrary.PosixException;
8581
import com.oracle.graal.python.runtime.PythonContext;
@@ -96,7 +92,6 @@
9692
import com.oracle.truffle.api.dsl.Specialization;
9793
import com.oracle.truffle.api.frame.VirtualFrame;
9894
import com.oracle.truffle.api.library.CachedLibrary;
99-
import com.oracle.truffle.api.nodes.Node;
10095

10196
@CoreFunctions(defineModule = "_posixsubprocess")
10297
public class PosixSubprocessModuleBuiltins extends PythonBuiltins {
@@ -105,61 +100,6 @@ protected List<? extends NodeFactory<? extends PythonBuiltinBaseNode>> getNodeFa
105100
return PosixSubprocessModuleBuiltinsFactory.getFactories();
106101
}
107102

108-
/**
109-
* Helper node that accepts either str or bytes and converts it to a representation specific to
110-
* the {@link PosixSupportLibrary} in use. Basically equivalent of
111-
* {@code PyUnicode_EncodeFSDefault}.
112-
*/
113-
abstract static class StringOrBytesToOpaquePathNode extends PNodeWithRaise {
114-
abstract Object execute(Object obj);
115-
116-
@Specialization(limit = "1")
117-
Object doString(String str,
118-
@CachedContext(PythonLanguage.class) PythonContext context,
119-
@CachedLibrary("context.getPosixSupport()") PosixSupportLibrary posixLib) {
120-
return checkPath(posixLib.createPathFromString(context.getPosixSupport(), str));
121-
}
122-
123-
@Specialization(limit = "1")
124-
Object doPString(PString pstr,
125-
@Cached CastToJavaStringNode castToJavaStringNode,
126-
@CachedContext(PythonLanguage.class) PythonContext context,
127-
@CachedLibrary("context.getPosixSupport()") PosixSupportLibrary posixLib) {
128-
String str = castToJavaStringNode.execute(pstr);
129-
return checkPath(posixLib.createPathFromString(context.getPosixSupport(), str));
130-
}
131-
132-
@Specialization(limit = "1")
133-
Object doBytes(PBytes bytes,
134-
@Cached ToBytesNode toBytesNode,
135-
@CachedContext(PythonLanguage.class) PythonContext context,
136-
@CachedLibrary("context.getPosixSupport()") PosixSupportLibrary posixLib) {
137-
return checkPath(posixLib.createPathFromBytes(context.getPosixSupport(), toBytesNode.execute(bytes)));
138-
}
139-
140-
private Object checkPath(Object path) {
141-
if (path == null) {
142-
throw raise(ValueError, ErrorMessages.EMBEDDED_NULL_BYTE);
143-
}
144-
return path;
145-
}
146-
}
147-
148-
/**
149-
* Similar to {@code PyUnicode_FSConverter}, but the actual conversion is delegated to the
150-
* {@link PosixSupportLibrary} implementation.
151-
*/
152-
abstract static class ObjectToOpaquePathNode extends Node {
153-
abstract Object execute(VirtualFrame frame, Object obj);
154-
155-
@Specialization
156-
Object doIt(VirtualFrame frame, Object obj,
157-
@Cached FspathNode fspathNode,
158-
@Cached StringOrBytesToOpaquePathNode stringOrBytesToOpaquePathNode) {
159-
return stringOrBytesToOpaquePathNode.execute(fspathNode.call(frame, obj));
160-
}
161-
}
162-
163103
/**
164104
* Helper converter which iterates the argv argument and converts each element to the opaque
165105
* path representation used by {@link PosixSupportLibrary}.

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ public abstract class ErrorMessages {
6262
public static final String ARG_MUST_BE_STRING_OR_NUMBER = "%s argument must be a string or a number, not '%p'";
6363
public static final String ARG_MUST_BE_UNICODE = "%s argument %d must be a unicode character, not %p";
6464
public static final String ARG_MUST_NOT_BE_ZERO = "%s arg %d must not be zero";
65+
public static final String ARG_MUST_NOT_BE_EMPTY = "%s arg %d must not be empty";
6566
public static final String ARG_NOT_IN_RANGE = "%s arg not in range(%s)";
6667
public static final String ARG_SHOULD_BE_INT_BYTESLIKE_OBJ = "argument should be integer or bytes-like object, not '%p'";
6768
public static final String ARG_SHOULD_BE_INT_OR_NONE = "argument should be integer or None, not %p";

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/PGuards.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,10 @@ public static boolean isList(Object o) {
233233
return o instanceof PList;
234234
}
235235

236+
public static boolean isTuple(Object o) {
237+
return o instanceof PTuple;
238+
}
239+
236240
public static boolean isObjectStorageIterator(PSequenceIterator iterator) {
237241
if (!iterator.isPSequence()) {
238242
return false;

0 commit comments

Comments
 (0)