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