Merge pull request #7727 from CyberShadow/process

std.process touchups
merged-on-behalf-of: Nicholas Wilson <thewilsonator@users.noreply.github.com>
This commit is contained in:
The Dlang Bot 2020-12-18 00:42:18 +01:00 committed by GitHub
commit 970317c461
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -108,6 +108,7 @@ version (Windows)
import std.internal.cstring; import std.internal.cstring;
import std.range.primitives; import std.range.primitives;
import std.stdio; import std.stdio;
import std.traits : isSomeChar;
version (OSX) version (OSX)
version = Darwin; version = Darwin;
@ -213,9 +214,7 @@ static:
string opIndex(scope const(char)[] name) @safe string opIndex(scope const(char)[] name) @safe
{ {
import std.exception : enforce; import std.exception : enforce;
string value; return get(name, null).enforce("Environment variable not found: "~name);
enforce(getImpl(name, value), "Environment variable not found: "~name);
return value;
} }
/** /**
@ -253,8 +252,8 @@ static:
string get(scope const(char)[] name, string defaultValue = null) @safe string get(scope const(char)[] name, string defaultValue = null) @safe
{ {
string value; string value;
auto found = getImpl(name, value); getImpl(name, (result) { value = result ? cachedToString(result) : defaultValue; });
return found ? value : defaultValue; return value;
} }
/** /**
@ -322,7 +321,7 @@ static:
multi-threaded programs. See e.g. multi-threaded programs. See e.g.
$(LINK2 https://www.gnu.org/software/libc/manual/html_node/Environment-Access.html#Environment-Access, glibc). $(LINK2 https://www.gnu.org/software/libc/manual/html_node/Environment-Access.html#Environment-Access, glibc).
*/ */
void remove(scope const(char)[] name) @trusted nothrow @nogc // TODO: @safe void remove(scope const(char)[] name) @trusted nothrow @nogc
{ {
version (Windows) SetEnvironmentVariableW(name.tempCStringW(), null); version (Windows) SetEnvironmentVariableW(name.tempCStringW(), null);
else version (Posix) core.sys.posix.stdlib.unsetenv(name.tempCString()); else version (Posix) core.sys.posix.stdlib.unsetenv(name.tempCString());
@ -446,8 +445,13 @@ static:
} }
private: private:
// Retrieves the environment variable, returns false on failure. version (Windows) alias OSChar = WCHAR;
bool getImpl(scope const(char)[] name, out string value) @trusted else version (Posix) alias OSChar = char;
// Retrieves the environment variable. Calls `sink` with a
// temporary buffer of OS characters, or `null` if the variable
// doesn't exist.
void getImpl(scope const(char)[] name, scope void delegate(const(OSChar)[]) @safe sink) @trusted
{ {
version (Windows) version (Windows)
{ {
@ -468,15 +472,12 @@ private:
{ {
immutable err = GetLastError(); immutable err = GetLastError();
if (err == ERROR_ENVVAR_NOT_FOUND) if (err == ERROR_ENVVAR_NOT_FOUND)
return false; return sink(null);
if (err != NO_ERROR) // Some other Windows error, throw. if (err != NO_ERROR) // Some other Windows error, throw.
throw new WindowsException(err); throw new WindowsException(err);
} }
if (len <= 1) if (len <= 1)
{ return sink("");
value = "";
return true;
}
buf.length = len; buf.length = len;
while (true) while (true)
@ -488,21 +489,15 @@ private:
{ {
immutable err = GetLastError(); immutable err = GetLastError();
if (err == NO_ERROR) // sucessfully read a 0-length variable if (err == NO_ERROR) // sucessfully read a 0-length variable
{ return sink("");
value = "";
return true;
}
if (err == ERROR_ENVVAR_NOT_FOUND) // variable didn't exist if (err == ERROR_ENVVAR_NOT_FOUND) // variable didn't exist
return false; return sink(null);
// some other windows error // some other windows error
throw new WindowsException(err); throw new WindowsException(err);
} }
assert(lenRead != buf.length, "impossible according to msft docs"); assert(lenRead != buf.length, "impossible according to msft docs");
if (lenRead < buf.length) // the buffer was long enough if (lenRead < buf.length) // the buffer was long enough
{ return sink(buf[0 .. lenRead]);
value = toUTF8(buf[0 .. lenRead]);
return true;
}
// resize and go around again, because the environment variable grew // resize and go around again, because the environment variable grew
buf.length = lenRead; buf.length = lenRead;
} }
@ -512,26 +507,31 @@ private:
import core.stdc.string : strlen; import core.stdc.string : strlen;
const vz = core.sys.posix.stdlib.getenv(name.tempCString()); const vz = core.sys.posix.stdlib.getenv(name.tempCString());
if (vz == null) return false; if (vz == null) return sink(null);
auto v = vz[0 .. strlen(vz)]; return sink(vz[0 .. strlen(vz)]);
}
// Cache the last call's result.
static string lastResult;
if (v.empty)
{
// Return non-null array for blank result to distinguish from
// not-present result.
lastResult = "";
}
else if (v != lastResult)
{
lastResult = v.idup;
}
value = lastResult;
return true;
}
else static assert(0); else static assert(0);
} }
string cachedToString(C)(scope const(C)[] v) @safe
{
import std.algorithm.comparison : equal;
// Cache the last call's result.
static string lastResult;
if (v.empty)
{
// Return non-null array for blank result to distinguish from
// not-present result.
lastResult = "";
}
else if (!v.equal(lastResult))
{
import std.conv : to;
lastResult = v.to!string;
}
return lastResult;
}
} }
@safe unittest @safe unittest
@ -809,17 +809,17 @@ Pid spawnProcess(scope const(char[])[] args,
const string[string] env = null, const string[string] env = null,
Config config = Config.none, Config config = Config.none,
scope const char[] workDir = null) scope const char[] workDir = null)
@trusted // TODO: Should be @safe @safe
{ {
version (Windows) version (Windows)
{ {
const commandLine = escapeShellArguments(args); const commandLine = escapeShellArguments(args);
const program = args.length ? args[0] : null; const program = args.length ? args[0] : null;
return spawnProcessImpl(commandLine, program, stdin, stdout, stderr, env, config, workDir); return spawnProcessWin(commandLine, program, stdin, stdout, stderr, env, config, workDir);
} }
else version (Posix) else version (Posix)
{ {
return spawnProcessImpl(args, stdin, stdout, stderr, env, config, workDir); return spawnProcessPosix(args, stdin, stdout, stderr, env, config, workDir);
} }
else else
static assert(0); static assert(0);
@ -882,13 +882,13 @@ envz should be a zero-terminated array of zero-terminated strings
on the form "var=value". on the form "var=value".
*/ */
version (Posix) version (Posix)
private Pid spawnProcessImpl(scope const(char[])[] args, private Pid spawnProcessPosix(scope const(char[])[] args,
File stdin, File stdin,
File stdout, File stdout,
File stderr, File stderr,
scope const string[string] env, scope const string[string] env,
Config config, Config config,
scope const(char)[] workDir) scope const(char)[] workDir)
@trusted // TODO: Should be @safe @trusted // TODO: Should be @safe
{ {
import core.exception : RangeError; import core.exception : RangeError;
@ -1217,14 +1217,14 @@ envz must be a pointer to a block of UTF-16 characters on the form
"var1=value1\0var2=value2\0...varN=valueN\0\0". "var1=value1\0var2=value2\0...varN=valueN\0\0".
*/ */
version (Windows) version (Windows)
private Pid spawnProcessImpl(scope const(char)[] commandLine, private Pid spawnProcessWin(scope const(char)[] commandLine,
scope const(char)[] program, scope const(char)[] program,
File stdin, File stdin,
File stdout, File stdout,
File stderr, File stderr,
const string[string] env, scope const string[string] env,
Config config, Config config,
scope const(char)[] workDir) scope const(char)[] workDir)
@trusted @trusted
{ {
import core.exception : RangeError; import core.exception : RangeError;
@ -1291,12 +1291,7 @@ private Pid spawnProcessImpl(scope const(char)[] commandLine,
if (!CreateProcessW(null, commandLine.tempCStringW().buffPtr, if (!CreateProcessW(null, commandLine.tempCStringW().buffPtr,
null, null, true, dwCreationFlags, null, null, true, dwCreationFlags,
envz, workDir.length ? pworkDir : null, &startinfo, &pi)) envz, workDir.length ? pworkDir : null, &startinfo, &pi))
{ throw ProcessException.newFromLastError("Failed to spawn process \"" ~ cast(string) program ~ '"');
if (GetLastError() == ERROR_FILE_NOT_FOUND)
throw new ProcessException(text("Executable file not found: ", program));
else
throw ProcessException.newFromLastError("Failed to spawn new process");
}
// figure out if we should close any of the streams // figure out if we should close any of the streams
if (!(config & Config.retainStdin ) && stdinFD > STDERR_FILENO if (!(config & Config.retainStdin ) && stdinFD > STDERR_FILENO
@ -1434,28 +1429,39 @@ version (Windows) @system unittest
// (checking that it is in fact executable). // (checking that it is in fact executable).
version (Posix) version (Posix)
private string searchPathFor(scope const(char)[] executable) private string searchPathFor(scope const(char)[] executable)
@trusted //TODO: @safe nothrow @safe
{ {
import std.algorithm.iteration : splitter; import std.algorithm.iteration : splitter;
import std.conv : to; import std.conv : text;
import std.path : buildPath; import std.path : chainPath;
auto pathz = core.stdc.stdlib.getenv("PATH"); string result;
if (pathz == null) return null;
foreach (dir; splitter(to!string(pathz), ':')) environment.getImpl("PATH",
{ (scope const(char)[] path)
auto execPath = buildPath(dir, executable); {
if (isExecutable(execPath)) return execPath; if (!path)
} return;
return null; foreach (dir; splitter(path, ":"))
{
auto execPath = chainPath(dir, executable);
if (isExecutable(execPath))
{
result = text(execPath);
return;
}
}
});
return result;
} }
// Checks whether the file exists and can be executed by the // Checks whether the file exists and can be executed by the
// current user. // current user.
version (Posix) version (Posix)
private bool isExecutable(scope const(char)[] path) @trusted nothrow @nogc //TODO: @safe private bool isExecutable(R)(R path) @trusted nothrow @nogc
if (isInputRange!R && isSomeChar!(ElementEncodingType!R))
{ {
return (access(path.tempCString(), X_OK) == 0); return (access(path.tempCString(), X_OK) == 0);
} }
@ -1865,7 +1871,7 @@ Pid spawnShell(scope const(char)[] command,
Config config = Config.none, Config config = Config.none,
scope const(char)[] workDir = null, scope const(char)[] workDir = null,
scope string shellPath = nativeShell) scope string shellPath = nativeShell)
@trusted // TODO: Should be @safe @safe
{ {
version (Windows) version (Windows)
{ {
@ -1875,7 +1881,7 @@ Pid spawnShell(scope const(char)[] command,
// See CMD.EXE /? for details. // See CMD.EXE /? for details.
const commandLine = escapeShellFileName(shellPath) const commandLine = escapeShellFileName(shellPath)
~ ` ` ~ shellSwitch ~ ` "` ~ command ~ `"`; ~ ` ` ~ shellSwitch ~ ` "` ~ command ~ `"`;
return spawnProcessImpl(commandLine, shellPath, stdin, stdout, stderr, env, config, workDir); return spawnProcessWin(commandLine, shellPath, stdin, stdout, stderr, env, config, workDir);
} }
else version (Posix) else version (Posix)
{ {
@ -1883,7 +1889,7 @@ Pid spawnShell(scope const(char)[] command,
args[0] = shellPath; args[0] = shellPath;
args[1] = shellSwitch; args[1] = shellSwitch;
args[2] = command; args[2] = command;
return spawnProcessImpl(args, stdin, stdout, stderr, env, config, workDir); return spawnProcessPosix(args, stdin, stdout, stderr, env, config, workDir);
} }
else else
static assert(0); static assert(0);
@ -3046,7 +3052,7 @@ auto execute(scope const(char[])[] args,
Config config = Config.none, Config config = Config.none,
size_t maxOutput = size_t.max, size_t maxOutput = size_t.max,
scope const(char)[] workDir = null) scope const(char)[] workDir = null)
@trusted //TODO: @safe @safe
{ {
return executeImpl!pipeProcess(args, env, config, maxOutput, workDir); return executeImpl!pipeProcess(args, env, config, maxOutput, workDir);
} }
@ -3057,7 +3063,7 @@ auto execute(scope const(char)[] program,
Config config = Config.none, Config config = Config.none,
size_t maxOutput = size_t.max, size_t maxOutput = size_t.max,
scope const(char)[] workDir = null) scope const(char)[] workDir = null)
@trusted //TODO: @safe @safe
{ {
return executeImpl!pipeProcess(program, env, config, maxOutput, workDir); return executeImpl!pipeProcess(program, env, config, maxOutput, workDir);
} }
@ -3069,7 +3075,7 @@ auto executeShell(scope const(char)[] command,
size_t maxOutput = size_t.max, size_t maxOutput = size_t.max,
scope const(char)[] workDir = null, scope const(char)[] workDir = null,
string shellPath = nativeShell) string shellPath = nativeShell)
@trusted //TODO: @safe @safe
{ {
return executeImpl!pipeShell(command, return executeImpl!pipeShell(command,
env, env,
@ -3087,6 +3093,7 @@ private auto executeImpl(alias pipeFunc, Cmd, ExtraPipeFuncArgs...)(
size_t maxOutput = size_t.max, size_t maxOutput = size_t.max,
scope const(char)[] workDir = null, scope const(char)[] workDir = null,
ExtraPipeFuncArgs extraArgs = ExtraPipeFuncArgs.init) ExtraPipeFuncArgs extraArgs = ExtraPipeFuncArgs.init)
@trusted //TODO: @safe
{ {
import std.algorithm.comparison : min; import std.algorithm.comparison : min;
import std.array : appender; import std.array : appender;