Skip to content

Commit 0c34ea4

Browse files
committed
Implementation of putenv()
1 parent 23b228a commit 0c34ea4

File tree

7 files changed

+144
-1
lines changed

7 files changed

+144
-1
lines changed

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
#include <fcntl.h>
5454
#include <stdio.h>
5555
#include <stdint.h>
56+
#include <stdlib.h>
5657
#include <string.h>
5758
#include <sys/ioctl.h>
5859
#include <sys/stat.h>
@@ -483,6 +484,10 @@ int32_t call_ctermid(char *buf) {
483484
return ctermid(buf) == NULL ? -1 : 0;
484485
}
485486

487+
int32_t call_setenv(char *name, char *value, int overwrite) {
488+
return setenv(name, value, overwrite);
489+
}
490+
486491
int32_t get_errno() {
487492
return errno;
488493
}

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

Lines changed: 99 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
import com.oracle.graal.python.builtins.PythonBuiltins;
6161
import com.oracle.graal.python.builtins.objects.PNone;
6262
import com.oracle.graal.python.builtins.objects.bytes.BytesNodes;
63+
import com.oracle.graal.python.builtins.objects.bytes.BytesUtils;
6364
import com.oracle.graal.python.builtins.objects.bytes.PBytes;
6465
import com.oracle.graal.python.builtins.objects.bytes.PBytesLike;
6566
import com.oracle.graal.python.builtins.objects.common.SequenceNodes.LenNode;
@@ -282,13 +283,24 @@ public void postInitialize(PythonCore core) {
282283
// fill the environ dictionary with the current environment
283284
Map<String, String> getenv = System.getenv();
284285
PDict environ = core.factory().createDict();
286+
String pyenvLauncherKey = "__PYVENV_LAUNCHER__";
285287
for (Entry<String, String> entry : getenv.entrySet()) {
286288
String value;
287-
if ("__PYVENV_LAUNCHER__".equals(entry.getKey())) {
289+
if (pyenvLauncherKey.equals(entry.getKey())) {
288290
// On Mac, the CPython launcher uses this env variable to specify the real Python
289291
// executable. It will be honored by packages like "site". So, if it is set, we
290292
// overwrite it with our executable to ensure that subprocesses will use us.
291293
value = core.getContext().getOption(PythonOptions.Executable);
294+
295+
try {
296+
PosixSupportLibrary posixLib = PosixSupportLibrary.getUncached();
297+
Object posixSupport = core.getContext().getPosixSupport();
298+
Object k = posixLib.createPathFromString(posixSupport, pyenvLauncherKey);
299+
Object v = posixLib.createPathFromString(posixSupport, value);
300+
posixLib.setenv(posixSupport, k, v, true);
301+
} catch (PosixException e) {
302+
// TODO handle error
303+
}
292304
} else {
293305
value = entry.getValue();
294306
}
@@ -318,6 +330,55 @@ public PTuple generic(VirtualFrame frame, Object cls, Object sequence, Object di
318330
}
319331
}
320332

333+
@Builtin(name = "putenv", minNumOfPositionalArgs = 2, parameterNames = {"name", "value"})
334+
@ArgumentClinic(name = "name", conversionClass = FsConverterNode.class)
335+
@ArgumentClinic(name = "value", conversionClass = FsConverterNode.class)
336+
@GenerateNodeFactory
337+
public abstract static class PutenvNode extends PythonBinaryClinicBuiltinNode {
338+
339+
@Override
340+
protected ArgumentClinicProvider getArgumentClinic() {
341+
return PosixModuleBuiltinsClinicProviders.PutenvNodeClinicProviderGen.INSTANCE;
342+
}
343+
344+
@Specialization
345+
PNone putenv(VirtualFrame frame, PBytes nameBytes, PBytes valueBytes,
346+
@Cached BytesNodes.ToBytesNode toBytesNode,
347+
@Cached SysModuleBuiltins.AuditNode auditNode,
348+
@CachedLibrary("getPosixSupport()") PosixSupportLibrary posixLib) {
349+
// Unlike in other posix builtins, we go through str -> bytes -> byte[] -> String
350+
// conversions for emulated backend because the bytes version after fsencode conversion
351+
// is subject to sys.audit.
352+
byte[] name = toBytesNode.execute(nameBytes);
353+
byte[] value = toBytesNode.execute(valueBytes);
354+
Object nameOpaque = checkNull(posixLib.createPathFromBytes(getPosixSupport(), name));
355+
Object valueOpaque = checkNull(posixLib.createPathFromBytes(getPosixSupport(), value));
356+
checkEqualSign(name);
357+
auditNode.audit("os.putenv", nameBytes, valueBytes);
358+
try {
359+
posixLib.setenv(getPosixSupport(), nameOpaque, valueOpaque, true);
360+
} catch (PosixException e) {
361+
throw raiseOSErrorFromPosixException(frame, e);
362+
}
363+
return PNone.NONE;
364+
}
365+
366+
private Object checkNull(Object value) {
367+
if (value == null) {
368+
throw raise(ValueError, ErrorMessages.EMBEDDED_NULL_BYTE);
369+
}
370+
return value;
371+
}
372+
373+
private void checkEqualSign(byte[] bytes) {
374+
for (byte b : bytes) {
375+
if (b == '=') {
376+
throw raise(ValueError, ErrorMessages.ILLEGAL_ENVIRONMENT_VARIABLE_NAME);
377+
}
378+
}
379+
}
380+
}
381+
321382
@Builtin(name = "execv", minNumOfPositionalArgs = 3, declaresExplicitSelf = true)
322383
@GenerateNodeFactory
323384
public abstract static class ExecvNode extends PythonBuiltinNode {
@@ -2057,6 +2118,29 @@ protected static boolean isPath(Object obj) {
20572118
// ------------------
20582119
// Helpers
20592120

2121+
/**
2122+
* Helper node that accepts either str or bytes and converts it to {@code PBytes}.
2123+
*/
2124+
abstract static class StringOrBytesToBytesNode extends PythonBuiltinBaseNode {
2125+
abstract PBytes execute(Object obj);
2126+
2127+
@Specialization
2128+
PBytes doString(String str) {
2129+
return factory().createBytes(BytesUtils.utf8StringToBytes(str));
2130+
}
2131+
2132+
@Specialization
2133+
PBytes doPString(PString pstr,
2134+
@Cached CastToJavaStringNode castToJavaStringNode) {
2135+
return doString(castToJavaStringNode.execute(pstr));
2136+
}
2137+
2138+
@Specialization
2139+
PBytes doBytes(PBytes bytes) {
2140+
return bytes;
2141+
}
2142+
}
2143+
20602144
abstract static class ConvertToTimespecBaseNode extends PythonBuiltinBaseNode {
20612145
abstract void execute(VirtualFrame frame, Object obj, long[] timespec, int offset);
20622146
}
@@ -2213,6 +2297,20 @@ public static PBytes opaquePathToBytes(Object opaquePath, PosixSupportLibrary po
22132297
// ------------------
22142298
// Converters
22152299

2300+
public abstract static class FsConverterNode extends ArgumentCastNodeWithRaise {
2301+
@Specialization
2302+
PBytes convert(VirtualFrame frame, Object value,
2303+
@Cached FspathNode fspathNode,
2304+
@Cached StringOrBytesToBytesNode stringOrBytesToBytesNode) {
2305+
return stringOrBytesToBytesNode.execute(fspathNode.call(frame, value));
2306+
}
2307+
2308+
@ClinicConverterFactory
2309+
public static FsConverterNode create() {
2310+
return PosixModuleBuiltinsFactory.FsConverterNodeGen.create();
2311+
}
2312+
}
2313+
22162314
/**
22172315
* Equivalent of CPython's {@code path_converter()}. Always returns an {@code int}. If the
22182316
* parameter is omitted, returns {@link PosixSupportLibrary#DEFAULT_DIR_FD}.

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
@@ -293,6 +293,7 @@ public abstract class ErrorMessages {
293293
public static final String IF_YOU_GIVE_ONLY_ONE_ARG_TO_DICT = "if you give only one argument to maketrans it must be a dict";
294294
public static final String INVALID_INDEXING_OF_0_DIM_MEMORY = "invalid indexing of 0-dim memory";
295295
public static final String ILLEGAL_ARG = "illegal argument";
296+
public static final String ILLEGAL_ENVIRONMENT_VARIABLE_NAME = "illegal environment variable name";
296297
public static final String ILLEGAL_EXPRESSION_FOR_AUGMENTED_ASSIGNEMNT = "illegal expression for augmented assignment";
297298
public static final String ILLEGAL_IP_STRING_PASSED_TO = "illegal IP address string passed to %s";
298299
public static final String ILLEGAL_SOCKET_ADDR_ARG = "%s: illegal sockaddr argument";

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@
9292
import java.util.concurrent.TimeUnit;
9393
import java.util.logging.Level;
9494

95+
import com.oracle.graal.python.builtins.objects.dict.PDict;
9596
import org.graalvm.nativeimage.ImageInfo;
9697
import org.graalvm.nativeimage.ProcessProperties;
9798
import org.graalvm.polyglot.io.ProcessHandler.Redirect;
@@ -235,12 +236,14 @@ public final class EmulatedPosixSupport extends PosixResources {
235236
private static final int S_IFREG = 0100000;
236237

237238
private final PythonContext context;
239+
private final Map<String, String> environ = new HashMap<>();
238240
private int currentUmask = 0022;
239241
private boolean hasDefaultUmask = true;
240242

241243
public EmulatedPosixSupport(PythonContext context) {
242244
this.context = context;
243245
setEnv(context.getEnv());
246+
environ.putAll(System.getenv());
244247
}
245248

246249
@ExportMessage
@@ -1590,6 +1593,14 @@ public String ctermid() {
15901593
return "/dev/tty";
15911594
}
15921595

1596+
@ExportMessage
1597+
@TruffleBoundary
1598+
public synchronized void setenv(Object name, Object value, boolean overwrite) {
1599+
if (overwrite || !environ.containsKey(name)) {
1600+
environ.put((String) name, (String) value);
1601+
}
1602+
}
1603+
15931604
@ExportMessage
15941605
@TruffleBoundary
15951606
public int forkExec(Object[] executables, Object[] args, Object cwd, Object[] env, int stdinReadFd, int stdinWriteFd, int stdoutReadFd, int stdoutWriteFd, int stderrReadFd, int stderrWriteFd,

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -696,6 +696,17 @@ final String ctermid(@CachedLibrary("this.delegate") PosixSupportLibrary lib) th
696696
}
697697
}
698698

699+
@ExportMessage
700+
final void setenv(Object name, Object value, boolean overwrite,
701+
@CachedLibrary("this.delegate") PosixSupportLibrary lib) throws PosixException {
702+
logEnter("setenv", "%s, %s, %b", name, value, overwrite);
703+
try {
704+
lib.setenv(delegate, name, value, overwrite);
705+
} catch (PosixException e) {
706+
throw logException("setenv", e);
707+
}
708+
}
709+
699710
@ExportMessage
700711
final int forkExec(Object[] executables, Object[] args, Object cwd, Object[] env, int stdinReadFd, int stdinWriteFd, int stdoutReadFd, int stdoutWriteFd, int stderrReadFd, int stderrWriteFd,
701712
int errPipeReadFd, int errPipeWriteFd, boolean closeFds, boolean restoreSignals, boolean callSetsid, int[] fdsToKeep,

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ private enum PosixNativeFunction {
153153
call_getppid("():sint64"),
154154
call_getsid("(sint64):sint64"),
155155
call_ctermid("([sint8]):sint32"),
156+
call_setenv("([sint8], [sint8], sint32):sint32"),
156157
fork_exec("([sint8], [sint64], sint32, sint32, sint32, sint32, sint32, sint32, sint32, sint32, sint32, sint32, sint32, sint32, sint32, sint32, sint32, [sint32], sint64):sint32");
157158

158159
private final String signature;
@@ -926,6 +927,15 @@ public String ctermid(@Shared("invoke") @Cached InvokeNativeFunction invokeNode)
926927
return cStringToJavaString(buf);
927928
}
928929

930+
@ExportMessage
931+
public void setenv(Object name, Object value, boolean overwrite,
932+
@Shared("invoke") @Cached InvokeNativeFunction invokeNode) throws PosixException {
933+
int res = invokeNode.callInt(this, PosixNativeFunction.call_setenv, pathToCString(name), pathToCString(value), overwrite ? 1 : 0);
934+
if (res == -1) {
935+
throw getErrnoAndThrowPosixException(invokeNode);
936+
}
937+
}
938+
929939
@ExportMessage
930940
public int forkExec(Object[] executables, Object[] args, Object cwd, Object[] env, int stdinReadFd, int stdinWriteFd, int stdoutReadFd, int stdoutWriteFd, int stderrReadFd, int stderrWriteFd,
931941
int errPipeReadFd, int errPipeWriteFd, boolean closeFds, boolean restoreSignals, boolean callSetsid, int[] fdsToKeep,
@@ -1102,6 +1112,9 @@ private static Buffer checkPath(byte[] path) {
11021112
return null;
11031113
}
11041114
}
1115+
// TODO we keep a byte[] provided by the caller, who can potentially change it, making our
1116+
// check for embedded nulls pointless. Maybe we should copy it and while on it, might as
1117+
// well add the terminating null character, avoiding the copy we do later in pathToCString.
11051118
return Buffer.wrap(path);
11061119
}
11071120

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,10 @@ public abstract class PosixSupportLibrary extends Library {
273273

274274
public abstract String ctermid(Object receiver) throws PosixException;
275275

276+
// note: this leaks memory in nfi backend and is not synchronized
277+
// TODO is it worth synchronizing at least all accesses made through PosixSupportLibrary?
278+
public abstract void setenv(Object receiver, Object name, Object value, boolean overwrite) throws PosixException;
279+
276280
public abstract int forkExec(Object receiver, Object[] executables, Object[] args, Object cwd, Object[] env, int stdinReadFd, int stdinWriteFd, int stdoutReadFd, int stdoutWriteFd,
277281
int stderrReadFd, int stderrWriteFd, int errPipeReadFd, int errPipeWriteFd, boolean closeFds, boolean restoreSignals, boolean callSetsid, int[] fdsToKeep) throws PosixException;
278282

0 commit comments

Comments
 (0)