Skip to content

Commit 0134c8a

Browse files
committed
Native implementation of system()
1 parent 9543b24 commit 0134c8a

File tree

6 files changed

+131
-101
lines changed

6 files changed

+131
-101
lines changed

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -501,6 +501,10 @@ void call_execv(char *data, int64_t *offsets, int32_t offsetsLen) {
501501
execv(pathname, argv);
502502
}
503503

504+
int32_t call_system(const char *pathname) {
505+
return system(pathname);
506+
}
507+
504508
int32_t get_errno() {
505509
return errno;
506510
}

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

Lines changed: 17 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -31,17 +31,11 @@
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.File;
35-
import java.io.IOException;
36-
import java.io.InputStream;
37-
import java.io.OutputStream;
38-
import java.lang.ProcessBuilder.Redirect;
3934
import java.math.BigInteger;
4035
import java.security.NoSuchAlgorithmException;
4136
import java.security.SecureRandom;
4237
import java.util.ArrayList;
4338
import java.util.List;
44-
import java.util.Locale;
4539
import java.util.Map;
4640
import java.util.Map.Entry;
4741

@@ -114,8 +108,6 @@
114108
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
115109
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
116110
import com.oracle.truffle.api.TruffleLanguage.ContextReference;
117-
import com.oracle.truffle.api.TruffleLanguage.Env;
118-
import com.oracle.truffle.api.TruffleLogger;
119111
import com.oracle.truffle.api.dsl.Cached;
120112
import com.oracle.truffle.api.dsl.Cached.Shared;
121113
import com.oracle.truffle.api.dsl.CachedContext;
@@ -216,10 +208,6 @@ public class PosixModuleBuiltins extends PythonBuiltins {
216208
"operating system release", "operating system version", "hardware identifier"
217209
});
218210

219-
private static boolean terminalIsInteractive(PythonContext context) {
220-
return context.getOption(PythonOptions.TerminalIsInteractive);
221-
}
222-
223211
@Override
224212
protected List<? extends NodeFactory<? extends PythonBuiltinBaseNode>> getNodeFactories() {
225213
return PosixModuleBuiltinsFactory.getFactories();
@@ -1870,98 +1858,27 @@ int wstopsig(int status,
18701858
}
18711859
}
18721860

1873-
@Builtin(name = "system", minNumOfPositionalArgs = 1)
1861+
@Builtin(name = "system", minNumOfPositionalArgs = 1, parameterNames = {"command"})
1862+
@ArgumentClinic(name = "command", conversionClass = FsConverterNode.class)
18741863
@GenerateNodeFactory
1875-
@TypeSystemReference(PythonArithmeticTypes.class)
1876-
abstract static class SystemNode extends PythonBuiltinNode {
1877-
private static final TruffleLogger LOGGER = PythonLanguage.getLogger(SystemNode.class);
1878-
1879-
static final String[] shell;
1880-
static {
1881-
String osProperty = System.getProperty("os.name");
1882-
shell = osProperty != null && osProperty.toLowerCase(Locale.ENGLISH).startsWith("windows") ? new String[]{"cmd.exe", "/c"}
1883-
: new String[]{(System.getenv().getOrDefault("SHELL", "sh")), "-c"};
1884-
}
1885-
1886-
static class PipePump extends Thread {
1887-
private static final int MAX_READ = 8192;
1888-
private final InputStream in;
1889-
private final OutputStream out;
1890-
private final byte[] buffer;
1891-
private volatile boolean finish;
1892-
1893-
public PipePump(String name, InputStream in, OutputStream out) {
1894-
this.setName(name);
1895-
this.in = in;
1896-
this.out = out;
1897-
this.buffer = new byte[MAX_READ];
1898-
this.finish = false;
1899-
}
1900-
1901-
@Override
1902-
public void run() {
1903-
try {
1904-
while (!finish || in.available() > 0) {
1905-
if (Thread.interrupted()) {
1906-
finish = true;
1907-
}
1908-
int read = in.read(buffer, 0, Math.min(MAX_READ, in.available()));
1909-
if (read == -1) {
1910-
return;
1911-
}
1912-
out.write(buffer, 0, read);
1913-
}
1914-
} catch (IOException e) {
1915-
}
1916-
}
1917-
1918-
public void finish() {
1919-
finish = true;
1920-
// Make ourselves max priority to flush data out as quickly as possible
1921-
setPriority(Thread.MAX_PRIORITY);
1922-
Thread.yield();
1923-
}
1864+
abstract static class SystemNode extends PythonUnaryClinicBuiltinNode {
1865+
@Override
1866+
protected ArgumentClinicProvider getArgumentClinic() {
1867+
return PosixModuleBuiltinsClinicProviders.SystemNodeClinicProviderGen.INSTANCE;
19241868
}
19251869

1926-
@TruffleBoundary
19271870
@Specialization
1928-
int system(String cmd) {
1929-
PythonContext context = getContext();
1930-
if (!context.isExecutableAccessAllowed()) {
1931-
return -1;
1932-
}
1933-
LOGGER.fine(() -> "os.system: " + cmd);
1934-
String[] command = new String[]{shell[0], shell[1], cmd};
1935-
Env env = context.getEnv();
1936-
try {
1937-
ProcessBuilder pb = new ProcessBuilder(command);
1938-
pb.directory(new File(env.getCurrentWorkingDirectory().getPath()));
1939-
PipePump stdout = null, stderr = null;
1940-
boolean stdsArePipes = !terminalIsInteractive(context);
1941-
if (stdsArePipes) {
1942-
pb.redirectInput(Redirect.PIPE);
1943-
pb.redirectOutput(Redirect.PIPE);
1944-
pb.redirectError(Redirect.PIPE);
1945-
} else {
1946-
pb.inheritIO();
1947-
}
1948-
Process proc = pb.start();
1949-
if (stdsArePipes) {
1950-
proc.getOutputStream().close(); // stdin will be closed
1951-
stdout = new PipePump(cmd + " [stdout]", proc.getInputStream(), env.out());
1952-
stderr = new PipePump(cmd + " [stderr]", proc.getErrorStream(), env.err());
1953-
stdout.start();
1954-
stderr.start();
1955-
}
1956-
int exitStatus = proc.waitFor();
1957-
if (stdsArePipes) {
1958-
stdout.finish();
1959-
stderr.finish();
1960-
}
1961-
return exitStatus;
1962-
} catch (IOException | InterruptedException e) {
1963-
return -1;
1964-
}
1871+
int system(VirtualFrame frame, PBytes command,
1872+
@Cached BytesNodes.ToBytesNode toBytesNode,
1873+
@Cached SysModuleBuiltins.AuditNode auditNode,
1874+
@CachedLibrary("getPosixSupport()") PosixSupportLibrary posixLib) {
1875+
// Unlike in other posix builtins, we go through str -> bytes -> byte[] -> String
1876+
// conversions for emulated backend because the bytes version after fsencode conversion
1877+
// is subject to sys.audit.
1878+
auditNode.audit("os.system", command);
1879+
byte[] bytes = toBytesNode.execute(command);
1880+
Object cmdOpaque = posixLib.createPathFromBytes(getPosixSupport(), bytes);
1881+
return posixLib.system(getPosixSupport(), cmdOpaque);
19651882
}
19661883
}
19671884

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/EmulatedPosixSupport.java

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,9 @@
6161
import static java.lang.Math.multiplyExact;
6262

6363
import java.io.BufferedReader;
64+
import java.io.File;
6465
import java.io.IOException;
66+
import java.io.InputStream;
6567
import java.io.InputStreamReader;
6668
import java.io.OutputStream;
6769
import java.net.InetAddress;
@@ -90,11 +92,13 @@
9092
import java.util.HashMap;
9193
import java.util.HashSet;
9294
import java.util.Iterator;
95+
import java.util.Locale;
9396
import java.util.Map;
9497
import java.util.Set;
9598
import java.util.concurrent.TimeUnit;
9699
import java.util.logging.Level;
97100

101+
import com.oracle.truffle.api.TruffleLanguage.Env;
98102
import org.graalvm.nativeimage.ImageInfo;
99103
import org.graalvm.nativeimage.ProcessProperties;
100104
import org.graalvm.polyglot.io.ProcessHandler.Redirect;
@@ -1823,6 +1827,94 @@ private void execvInternal(String[] cmd) throws IOException {
18231827
throw new PythonExitException(null, pr.exitValue());
18241828
}
18251829

1830+
@ExportMessage
1831+
@TruffleBoundary
1832+
public int system(Object commandObj) {
1833+
String cmd = pathToJavaStr(commandObj);
1834+
if (!context.isExecutableAccessAllowed()) {
1835+
return -1;
1836+
}
1837+
LOGGER.fine(() -> "os.system: " + cmd);
1838+
String[] command = new String[]{shell[0], shell[1], cmd};
1839+
Env env = context.getEnv();
1840+
try {
1841+
ProcessBuilder pb = new ProcessBuilder(command);
1842+
pb.directory(new File(env.getCurrentWorkingDirectory().getPath()));
1843+
PipePump stdout = null, stderr = null;
1844+
boolean stdsArePipes = !context.getOption(PythonOptions.TerminalIsInteractive);
1845+
if (stdsArePipes) {
1846+
pb.redirectInput(ProcessBuilder.Redirect.PIPE);
1847+
pb.redirectOutput(ProcessBuilder.Redirect.PIPE);
1848+
pb.redirectError(ProcessBuilder.Redirect.PIPE);
1849+
} else {
1850+
pb.inheritIO();
1851+
}
1852+
Process proc = pb.start();
1853+
if (stdsArePipes) {
1854+
proc.getOutputStream().close(); // stdin will be closed
1855+
stdout = new PipePump(cmd + " [stdout]", proc.getInputStream(), env.out());
1856+
stderr = new PipePump(cmd + " [stderr]", proc.getErrorStream(), env.err());
1857+
stdout.start();
1858+
stderr.start();
1859+
}
1860+
int exitStatus = proc.waitFor();
1861+
if (stdsArePipes) {
1862+
stdout.finish();
1863+
stderr.finish();
1864+
}
1865+
return exitStatus;
1866+
} catch (IOException | InterruptedException e) {
1867+
return -1;
1868+
}
1869+
}
1870+
1871+
private static final String[] shell;
1872+
static {
1873+
String osProperty = System.getProperty("os.name");
1874+
shell = osProperty != null && osProperty.toLowerCase(Locale.ENGLISH).startsWith("windows") ? new String[]{"cmd.exe", "/c"}
1875+
: new String[]{(System.getenv().getOrDefault("SHELL", "sh")), "-c"};
1876+
}
1877+
1878+
static class PipePump extends Thread {
1879+
private static final int MAX_READ = 8192;
1880+
private final InputStream in;
1881+
private final OutputStream out;
1882+
private final byte[] buffer;
1883+
private volatile boolean finish;
1884+
1885+
public PipePump(String name, InputStream in, OutputStream out) {
1886+
this.setName(name);
1887+
this.in = in;
1888+
this.out = out;
1889+
this.buffer = new byte[MAX_READ];
1890+
this.finish = false;
1891+
}
1892+
1893+
@Override
1894+
public void run() {
1895+
try {
1896+
while (!finish || in.available() > 0) {
1897+
if (Thread.interrupted()) {
1898+
finish = true;
1899+
}
1900+
int read = in.read(buffer, 0, Math.min(MAX_READ, in.available()));
1901+
if (read == -1) {
1902+
return;
1903+
}
1904+
out.write(buffer, 0, read);
1905+
}
1906+
} catch (IOException e) {
1907+
}
1908+
}
1909+
1910+
public void finish() {
1911+
finish = true;
1912+
// Make ourselves max priority to flush data out as quickly as possible
1913+
setPriority(Thread.MAX_PRIORITY);
1914+
Thread.yield();
1915+
}
1916+
}
1917+
18261918
// ------------------
18271919
// Path conversions
18281920

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/LoggingPosixSupport.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -732,6 +732,13 @@ final void execv(Object pathname, Object[] args,
732732
}
733733
}
734734

735+
@ExportMessage
736+
final int system(Object command,
737+
@CachedLibrary("this.delegate") PosixSupportLibrary lib) {
738+
logEnter("system", "%s", command);
739+
return logExit("system", "%d", lib.system(delegate, command));
740+
}
741+
735742
@ExportMessage
736743
final Object createPathFromString(String path,
737744
@CachedLibrary("this.delegate") PosixSupportLibrary lib) {

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/NFIPosixSupport.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,8 @@ private enum PosixNativeFunction {
155155
call_ctermid("([sint8]):sint32"),
156156
call_setenv("([sint8], [sint8], sint32):sint32"),
157157
fork_exec("([sint8], [sint64], sint32, sint32, sint32, sint32, sint32, sint32, sint32, sint32, sint32, sint32, sint32, sint32, sint32, sint32, sint32, [sint32], sint64):sint32"),
158-
call_execv("([sint8], [sint64], sint32):void");
158+
call_execv("([sint8], [sint64], sint32):void"),
159+
call_system("([sint8]):sint32");
159160

160161
private final String signature;
161162

@@ -1088,6 +1089,12 @@ public void execv(Object pathname, Object[] args,
10881089
throw getErrnoAndThrowPosixException(invokeNode);
10891090
}
10901091

1092+
@ExportMessage
1093+
public int system(Object command,
1094+
@Shared("invoke") @Cached InvokeNativeFunction invokeNode) {
1095+
return invokeNode.callInt(this, PosixNativeFunction.call_system, pathToCString(command));
1096+
}
1097+
10911098
private static long addLengthsOfCStrings(long prevLen, Object[] src) throws OverflowException {
10921099
long len = prevLen;
10931100
for (Object o : src) {

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PosixSupportLibrary.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,9 @@ public abstract int forkExec(Object receiver, Object[] executables, Object[] arg
283283
// args.length must be > 0
284284
public abstract void execv(Object receiver, Object pathname, Object[] args) throws PosixException;
285285

286+
// does not throw, because posix does not exactly define the return value
287+
public abstract int system(Object receiver, Object command);
288+
286289
/**
287290
* Converts a {@code String} into the internal representation of paths used by the library
288291
* implementation. The implementation should return {@code null} if the path after any necessary

0 commit comments

Comments
 (0)