|  | // Written in the D programming language. | 
|  |  | 
|  | /** | 
|  | Functions for starting and interacting with other processes, and for | 
|  | working with the current process' execution environment. | 
|  |  | 
|  | Process_handling: | 
|  | $(UL $(LI | 
|  | $(LREF spawnProcess) spawns a new process, optionally assigning it an | 
|  | arbitrary set of standard input, output, and error streams. | 
|  | The function returns immediately, leaving the child process to execute | 
|  | in parallel with its parent.  All other functions in this module that | 
|  | spawn processes are built around `spawnProcess`.) | 
|  | $(LI | 
|  | $(LREF wait) makes the parent process wait for a child process to | 
|  | terminate.  In general one should always do this, to avoid | 
|  | child processes becoming "zombies" when the parent process exits. | 
|  | Scope guards are perfect for this – see the $(LREF spawnProcess) | 
|  | documentation for examples.  $(LREF tryWait) is similar to `wait`, | 
|  | but does not block if the process has not yet terminated.) | 
|  | $(LI | 
|  | $(LREF pipeProcess) also spawns a child process which runs | 
|  | in parallel with its parent.  However, instead of taking | 
|  | arbitrary streams, it automatically creates a set of | 
|  | pipes that allow the parent to communicate with the child | 
|  | through the child's standard input, output, and/or error streams. | 
|  | This function corresponds roughly to C's `popen` function.) | 
|  | $(LI | 
|  | $(LREF execute) starts a new process and waits for it | 
|  | to complete before returning.  Additionally, it captures | 
|  | the process' standard output and error streams and returns | 
|  | the output of these as a string.) | 
|  | $(LI | 
|  | $(LREF spawnShell), $(LREF pipeShell) and $(LREF executeShell) work like | 
|  | `spawnProcess`, `pipeProcess` and `execute`, respectively, | 
|  | except that they take a single command string and run it through | 
|  | the current user's default command interpreter. | 
|  | `executeShell` corresponds roughly to C's `system` function.) | 
|  | $(LI | 
|  | $(LREF kill) attempts to terminate a running process.) | 
|  | ) | 
|  |  | 
|  | The following table compactly summarises the different process creation | 
|  | functions and how they relate to each other: | 
|  | $(BOOKTABLE, | 
|  | $(TR $(TH ) | 
|  | $(TH Runs program directly) | 
|  | $(TH Runs shell command)) | 
|  | $(TR $(TD Low-level process creation) | 
|  | $(TD $(LREF spawnProcess)) | 
|  | $(TD $(LREF spawnShell))) | 
|  | $(TR $(TD Automatic input/output redirection using pipes) | 
|  | $(TD $(LREF pipeProcess)) | 
|  | $(TD $(LREF pipeShell))) | 
|  | $(TR $(TD Execute and wait for completion, collect output) | 
|  | $(TD $(LREF execute)) | 
|  | $(TD $(LREF executeShell))) | 
|  | ) | 
|  |  | 
|  | Other_functionality: | 
|  | $(UL | 
|  | $(LI | 
|  | $(LREF pipe) is used to create unidirectional pipes.) | 
|  | $(LI | 
|  | $(LREF environment) is an interface through which the current process' | 
|  | environment variables can be read and manipulated.) | 
|  | $(LI | 
|  | $(LREF escapeShellCommand) and $(LREF escapeShellFileName) are useful | 
|  | for constructing shell command lines in a portable way.) | 
|  | ) | 
|  |  | 
|  | Authors: | 
|  | $(LINK2 https://github.com/kyllingstad, Lars Tandle Kyllingstad), | 
|  | $(LINK2 https://github.com/schveiguy, Steven Schveighoffer), | 
|  | $(HTTP thecybershadow.net, Vladimir Panteleev) | 
|  | Copyright: | 
|  | Copyright (c) 2013, the authors. All rights reserved. | 
|  | License: | 
|  | $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). | 
|  | Source: | 
|  | $(PHOBOSSRC std/process.d) | 
|  | Macros: | 
|  | OBJECTREF=$(REF1 $0, object) | 
|  |  | 
|  | Note: | 
|  | Most of the functionality in this module is not available on iOS, tvOS | 
|  | and watchOS. The only functions available on those platforms are: | 
|  | $(LREF environment), $(LREF thisProcessID) and $(LREF thisThreadID). | 
|  | */ | 
|  | module std.process; | 
|  |  | 
|  | import core.thread : ThreadID; | 
|  |  | 
|  | version (Posix) | 
|  | { | 
|  | import core.sys.posix.sys.wait; | 
|  | import core.sys.posix.unistd; | 
|  | } | 
|  | version (Windows) | 
|  | { | 
|  | import core.stdc.stdio; | 
|  | import core.sys.windows.winbase; | 
|  | import core.sys.windows.winnt; | 
|  | import std.utf; | 
|  | import std.windows.syserror; | 
|  | } | 
|  |  | 
|  | import std.internal.cstring; | 
|  | import std.range; | 
|  | import std.stdio; | 
|  |  | 
|  | version (OSX) | 
|  | version = Darwin; | 
|  | else version (iOS) | 
|  | { | 
|  | version = Darwin; | 
|  | version = iOSDerived; | 
|  | } | 
|  | else version (TVOS) | 
|  | { | 
|  | version = Darwin; | 
|  | version = iOSDerived; | 
|  | } | 
|  | else version (WatchOS) | 
|  | { | 
|  | version = Darwin; | 
|  | version = iOSDerived; | 
|  | } | 
|  |  | 
|  |  | 
|  | // Some of the following should be moved to druntime. | 
|  | private | 
|  | { | 
|  | // Microsoft Visual C Runtime (MSVCRT) declarations. | 
|  | version (CRuntime_Microsoft) | 
|  | { | 
|  | import core.stdc.stdint; | 
|  | enum | 
|  | { | 
|  | STDIN_FILENO  = 0, | 
|  | STDOUT_FILENO = 1, | 
|  | STDERR_FILENO = 2, | 
|  | } | 
|  | } | 
|  |  | 
|  | // POSIX API declarations. | 
|  | version (Posix) | 
|  | { | 
|  | import core.sys.posix.unistd : getEnvironPtr = environ; | 
|  |  | 
|  | @system unittest | 
|  | { | 
|  | import core.thread : Thread; | 
|  | new Thread({assert(getEnvironPtr !is null);}).start(); | 
|  | } | 
|  | } | 
|  | } // private | 
|  |  | 
|  | // ============================================================================= | 
|  | // Environment variable manipulation. | 
|  | // ============================================================================= | 
|  |  | 
|  | /** | 
|  | Manipulates _environment variables using an associative-array-like | 
|  | interface. | 
|  |  | 
|  | This class contains only static methods, and cannot be instantiated. | 
|  | See below for examples of use. | 
|  | */ | 
|  | abstract final class environment | 
|  | { | 
|  | static import core.sys.posix.stdlib; | 
|  | import core.stdc.errno : errno, EINVAL; | 
|  |  | 
|  | static: | 
|  | /** | 
|  | Retrieves the value of the environment variable with the given `name`. | 
|  | --- | 
|  | auto path = environment["PATH"]; | 
|  | --- | 
|  |  | 
|  | Throws: | 
|  | $(OBJECTREF Exception) if the environment variable does not exist, | 
|  | or $(REF UTFException, std,utf) if the variable contains invalid UTF-16 | 
|  | characters (Windows only). | 
|  |  | 
|  | See_also: | 
|  | $(LREF environment.get), which doesn't throw on failure. | 
|  | */ | 
|  | string opIndex(scope const(char)[] name) @safe | 
|  | { | 
|  | import std.exception : enforce; | 
|  | return get(name, null).enforce("Environment variable not found: "~name); | 
|  | } | 
|  |  | 
|  | /** | 
|  | Retrieves the value of the environment variable with the given `name`, | 
|  | or a default value if the variable doesn't exist. | 
|  |  | 
|  | Unlike $(LREF environment.opIndex), this function never throws on Posix. | 
|  | --- | 
|  | auto sh = environment.get("SHELL", "/bin/sh"); | 
|  | --- | 
|  | This function is also useful in checking for the existence of an | 
|  | environment variable. | 
|  | --- | 
|  | auto myVar = environment.get("MYVAR"); | 
|  | if (myVar is null) | 
|  | { | 
|  | // Environment variable doesn't exist. | 
|  | // Note that we have to use 'is' for the comparison, since | 
|  | // myVar == null is also true if the variable exists but is | 
|  | // empty. | 
|  | } | 
|  | --- | 
|  | Params: | 
|  | name = name of the environment variable to retrieve | 
|  | defaultValue = default value to return if the environment variable doesn't exist. | 
|  |  | 
|  | Returns: | 
|  | the value of the environment variable if found, otherwise | 
|  | `null` if the environment doesn't exist. | 
|  |  | 
|  | Throws: | 
|  | $(REF UTFException, std,utf) if the variable contains invalid UTF-16 | 
|  | characters (Windows only). | 
|  | */ | 
|  | string get(scope const(char)[] name, string defaultValue = null) @safe | 
|  | { | 
|  | string value; | 
|  | getImpl(name, (result) { value = result ? cachedToString(result) : defaultValue; }); | 
|  | return value; | 
|  | } | 
|  |  | 
|  | /** | 
|  | Assigns the given `value` to the environment variable with the given | 
|  | `name`. | 
|  | If `value` is null the variable is removed from environment. | 
|  |  | 
|  | If the variable does not exist, it will be created. If it already exists, | 
|  | it will be overwritten. | 
|  | --- | 
|  | environment["foo"] = "bar"; | 
|  | --- | 
|  |  | 
|  | Throws: | 
|  | $(OBJECTREF Exception) if the environment variable could not be added | 
|  | (e.g. if the name is invalid). | 
|  |  | 
|  | Note: | 
|  | On some platforms, modifying environment variables may not be allowed in | 
|  | multi-threaded programs. See e.g. | 
|  | $(LINK2 https://www.gnu.org/software/libc/manual/html_node/Environment-Access.html#Environment-Access, glibc). | 
|  | */ | 
|  | inout(char)[] opIndexAssign(return scope inout char[] value, scope const(char)[] name) @trusted | 
|  | { | 
|  | version (Posix) | 
|  | { | 
|  | import std.exception : enforce, errnoEnforce; | 
|  | if (value is null) | 
|  | { | 
|  | remove(name); | 
|  | return value; | 
|  | } | 
|  | if (core.sys.posix.stdlib.setenv(name.tempCString(), value.tempCString(), 1) != -1) | 
|  | { | 
|  | return value; | 
|  | } | 
|  | // The default errno error message is very uninformative | 
|  | // in the most common case, so we handle it manually. | 
|  | enforce(errno != EINVAL, | 
|  | "Invalid environment variable name: '"~name~"'"); | 
|  | errnoEnforce(false, | 
|  | "Failed to add environment variable"); | 
|  | assert(0); | 
|  | } | 
|  | else version (Windows) | 
|  | { | 
|  | import std.windows.syserror : wenforce; | 
|  | wenforce( | 
|  | SetEnvironmentVariableW(name.tempCStringW(), value.tempCStringW()), | 
|  | ); | 
|  | return value; | 
|  | } | 
|  | else static assert(0); | 
|  | } | 
|  |  | 
|  | /** | 
|  | Removes the environment variable with the given `name`. | 
|  |  | 
|  | If the variable isn't in the environment, this function returns | 
|  | successfully without doing anything. | 
|  |  | 
|  | Note: | 
|  | On some platforms, modifying environment variables may not be allowed in | 
|  | 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 | 
|  | { | 
|  | version (Windows)    SetEnvironmentVariableW(name.tempCStringW(), null); | 
|  | else version (Posix) core.sys.posix.stdlib.unsetenv(name.tempCString()); | 
|  | else static assert(0); | 
|  | } | 
|  |  | 
|  | /** | 
|  | Identify whether a variable is defined in the environment. | 
|  |  | 
|  | Because it doesn't return the value, this function is cheaper than `get`. | 
|  | However, if you do need the value as well, you should just check the | 
|  | return of `get` for `null` instead of using this function first. | 
|  |  | 
|  | Example: | 
|  | ------------- | 
|  | // good usage | 
|  | if ("MY_ENV_FLAG" in environment) | 
|  | doSomething(); | 
|  |  | 
|  | // bad usage | 
|  | if ("MY_ENV_VAR" in environment) | 
|  | doSomething(environment["MY_ENV_VAR"]); | 
|  |  | 
|  | // do this instead | 
|  | if (auto var = environment.get("MY_ENV_VAR")) | 
|  | doSomething(var); | 
|  | ------------- | 
|  | */ | 
|  | bool opBinaryRight(string op : "in")(scope const(char)[] name) @trusted | 
|  | { | 
|  | if (name is null) | 
|  | return false; | 
|  | version (Posix) | 
|  | return core.sys.posix.stdlib.getenv(name.tempCString()) !is null; | 
|  | else version (Windows) | 
|  | { | 
|  | SetLastError(NO_ERROR); | 
|  | if (GetEnvironmentVariableW(name.tempCStringW, null, 0) > 0) | 
|  | return true; | 
|  | immutable err = GetLastError(); | 
|  | if (err == NO_ERROR) | 
|  | return true; // zero-length environment variable on Wine / XP | 
|  | if (err == ERROR_ENVVAR_NOT_FOUND) | 
|  | return false; | 
|  | // Some other Windows error, throw. | 
|  | throw new WindowsException(err); | 
|  | } | 
|  | else static assert(0); | 
|  | } | 
|  |  | 
|  | /** | 
|  | Copies all environment variables into an associative array. | 
|  |  | 
|  | Windows_specific: | 
|  | While Windows environment variable names are case insensitive, D's | 
|  | built-in associative arrays are not.  This function will store all | 
|  | variable names in uppercase (e.g. `PATH`). | 
|  |  | 
|  | Throws: | 
|  | $(OBJECTREF Exception) if the environment variables could not | 
|  | be retrieved (Windows only). | 
|  | */ | 
|  | string[string] toAA() @trusted | 
|  | { | 
|  | import std.conv : to; | 
|  | string[string] aa; | 
|  | version (Posix) | 
|  | { | 
|  | auto environ = getEnvironPtr; | 
|  | for (int i=0; environ[i] != null; ++i) | 
|  | { | 
|  | import std.string : indexOf; | 
|  |  | 
|  | immutable varDef = to!string(environ[i]); | 
|  | immutable eq = indexOf(varDef, '='); | 
|  | assert(eq >= 0); | 
|  |  | 
|  | immutable name = varDef[0 .. eq]; | 
|  | immutable value = varDef[eq+1 .. $]; | 
|  |  | 
|  | // In POSIX, environment variables may be defined more | 
|  | // than once.  This is a security issue, which we avoid | 
|  | // by checking whether the key already exists in the array. | 
|  | // For more info: | 
|  | // http://www.dwheeler.com/secure-programs/Secure-Programs-HOWTO/environment-variables.html | 
|  | if (name !in aa)  aa[name] = value; | 
|  | } | 
|  | } | 
|  | else version (Windows) | 
|  | { | 
|  | import std.exception : enforce; | 
|  | import std.uni : toUpper; | 
|  | auto envBlock = GetEnvironmentStringsW(); | 
|  | enforce(envBlock, "Failed to retrieve environment variables."); | 
|  | scope(exit) FreeEnvironmentStringsW(envBlock); | 
|  |  | 
|  | for (int i=0; envBlock[i] != '\0'; ++i) | 
|  | { | 
|  | auto start = i; | 
|  | while (envBlock[i] != '=') ++i; | 
|  | immutable name = toUTF8(toUpper(envBlock[start .. i])); | 
|  |  | 
|  | start = i+1; | 
|  | while (envBlock[i] != '\0') ++i; | 
|  |  | 
|  | // Ignore variables with empty names. These are used internally | 
|  | // by Windows to keep track of each drive's individual current | 
|  | // directory. | 
|  | if (!name.length) | 
|  | continue; | 
|  |  | 
|  | // Just like in POSIX systems, environment variables may be | 
|  | // defined more than once in an environment block on Windows, | 
|  | // and it is just as much of a security issue there.  Moreso, | 
|  | // in fact, due to the case insensensitivity of variable names, | 
|  | // which is not handled correctly by all programs. | 
|  | auto val = toUTF8(envBlock[start .. i]); | 
|  | if (name !in aa) aa[name] = val is null ? "" : val; | 
|  | } | 
|  | } | 
|  | else static assert(0); | 
|  | return aa; | 
|  | } | 
|  |  | 
|  | private: | 
|  | 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 | 
|  | { | 
|  | // fix issue https://issues.dlang.org/show_bug.cgi?id=24549 | 
|  | if (name is null) | 
|  | return sink(null); | 
|  |  | 
|  | version (Windows) | 
|  | { | 
|  | // first we ask windows how long the environment variable is, | 
|  | // then we try to read it in to a buffer of that length. Lots | 
|  | // of error conditions because the windows API is nasty. | 
|  |  | 
|  | import std.conv : to; | 
|  | const namezTmp = name.tempCStringW(); | 
|  | WCHAR[] buf; | 
|  |  | 
|  | // clear error because GetEnvironmentVariable only says it sets it | 
|  | // if the environment variable is missing, not on other errors. | 
|  | SetLastError(NO_ERROR); | 
|  | // len includes terminating null | 
|  | immutable len = GetEnvironmentVariableW(namezTmp, null, 0); | 
|  | if (len == 0) | 
|  | { | 
|  | immutable err = GetLastError(); | 
|  | if (err == ERROR_ENVVAR_NOT_FOUND) | 
|  | return sink(null); | 
|  | if (err != NO_ERROR) // Some other Windows error, throw. | 
|  | throw new WindowsException(err); | 
|  | } | 
|  | if (len <= 1) | 
|  | return sink(""); | 
|  | buf.length = len; | 
|  |  | 
|  | while (true) | 
|  | { | 
|  | // lenRead is either the number of bytes read w/o null - if buf was long enough - or | 
|  | // the number of bytes necessary *including* null if buf wasn't long enough | 
|  | immutable lenRead = GetEnvironmentVariableW(namezTmp, buf.ptr, to!DWORD(buf.length)); | 
|  | if (lenRead == 0) | 
|  | { | 
|  | immutable err = GetLastError(); | 
|  | if (err == NO_ERROR) // sucessfully read a 0-length variable | 
|  | return sink(""); | 
|  | if (err == ERROR_ENVVAR_NOT_FOUND) // variable didn't exist | 
|  | 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 | 
|  | return sink(buf[0 .. lenRead]); | 
|  | // resize and go around again, because the environment variable grew | 
|  | buf.length = lenRead; | 
|  | } | 
|  | } | 
|  | else version (Posix) | 
|  | { | 
|  | import core.stdc.string : strlen; | 
|  |  | 
|  | const vz = core.sys.posix.stdlib.getenv(name.tempCString()); | 
|  | 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 | 
|  | { | 
|  | import std.exception : assertThrown; | 
|  | // New variable | 
|  | environment["std_process"] = "foo"; | 
|  | assert(environment["std_process"] == "foo"); | 
|  | assert("std_process" in environment); | 
|  |  | 
|  | // Set variable again (also tests length 1 case) | 
|  | environment["std_process"] = "b"; | 
|  | assert(environment["std_process"] == "b"); | 
|  | assert("std_process" in environment); | 
|  |  | 
|  | // Remove variable | 
|  | environment.remove("std_process"); | 
|  | assert("std_process" !in environment); | 
|  |  | 
|  | // Remove again, should succeed | 
|  | environment.remove("std_process"); | 
|  | assert("std_process" !in environment); | 
|  |  | 
|  | // Throw on not found. | 
|  | assertThrown(environment["std_process"]); | 
|  |  | 
|  | // get() without default value | 
|  | assert(environment.get("std_process") is null); | 
|  |  | 
|  | // get() with default value | 
|  | assert(environment.get("std_process", "baz") == "baz"); | 
|  |  | 
|  | // get() on an empty (but present) value | 
|  | environment["std_process"] = ""; | 
|  | auto res = environment.get("std_process"); | 
|  | assert(res !is null); | 
|  | assert(res == ""); | 
|  | assert("std_process" in environment); | 
|  |  | 
|  | // Important to do the following round-trip after the previous test | 
|  | // because it tests toAA with an empty var | 
|  |  | 
|  | // Convert to associative array | 
|  | auto aa = environment.toAA(); | 
|  | assert(aa.length > 0); | 
|  | foreach (n, v; aa) | 
|  | { | 
|  | // Wine has some bugs related to environment variables: | 
|  | //  - Wine allows the existence of an env. variable with the name | 
|  | //    "\0", but GetEnvironmentVariable refuses to retrieve it. | 
|  | //    As of 2.067 we filter these out anyway (see comment in toAA). | 
|  |  | 
|  | assert(v == environment[n]); | 
|  | } | 
|  |  | 
|  | // ... and back again. | 
|  | foreach (n, v; aa) | 
|  | environment[n] = v; | 
|  |  | 
|  | // Complete the roundtrip | 
|  | auto aa2 = environment.toAA(); | 
|  | import std.conv : text; | 
|  | assert(aa == aa2, text(aa, " != ", aa2)); | 
|  | assert("std_process" in environment); | 
|  |  | 
|  | // Setting null must have the same effect as remove | 
|  | environment["std_process"] = null; | 
|  | assert("std_process" !in environment); | 
|  | } | 
|  |  | 
|  | // https://issues.dlang.org/show_bug.cgi?id=24549 | 
|  | @safe unittest | 
|  | { | 
|  | import std.exception : assertThrown; | 
|  | assert(environment.get(null) is null); | 
|  | assertThrown(environment[null]); | 
|  | assert(!(null in environment)); | 
|  | } | 
|  |  | 
|  | // ============================================================================= | 
|  | // Functions and classes for process management. | 
|  | // ============================================================================= | 
|  |  | 
|  | /** | 
|  | * Returns the process ID of the current process, | 
|  | * which is guaranteed to be unique on the system. | 
|  | * | 
|  | * Example: | 
|  | * --- | 
|  | * writefln("Current process ID: %d", thisProcessID); | 
|  | * --- | 
|  | */ | 
|  | @property int thisProcessID() @trusted nothrow @nogc //TODO: @safe | 
|  | { | 
|  | version (Windows)    return GetCurrentProcessId(); | 
|  | else version (Posix) return core.sys.posix.unistd.getpid(); | 
|  | } | 
|  |  | 
|  |  | 
|  | /** | 
|  | * Returns the process ID of the current thread, | 
|  | * which is guaranteed to be unique within the current process. | 
|  | * | 
|  | * Returns: | 
|  | * A $(REF ThreadID, core,thread) value for the calling thread. | 
|  | * | 
|  | * Example: | 
|  | * --- | 
|  | * writefln("Current thread ID: %s", thisThreadID); | 
|  | * --- | 
|  | */ | 
|  | @property ThreadID thisThreadID() @trusted nothrow @nogc //TODO: @safe | 
|  | { | 
|  | version (Windows) | 
|  | return GetCurrentThreadId(); | 
|  | else | 
|  | version (Posix) | 
|  | { | 
|  | import core.sys.posix.pthread : pthread_self; | 
|  | return pthread_self(); | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | @system unittest | 
|  | { | 
|  | int pidA, pidB; | 
|  | ThreadID tidA, tidB; | 
|  | pidA = thisProcessID; | 
|  | tidA = thisThreadID; | 
|  |  | 
|  | import core.thread; | 
|  | auto t = new Thread({ | 
|  | pidB = thisProcessID; | 
|  | tidB = thisThreadID; | 
|  | }); | 
|  | t.start(); | 
|  | t.join(); | 
|  |  | 
|  | assert(pidA == pidB); | 
|  | assert(tidA != tidB); | 
|  | } | 
|  |  | 
|  |  | 
|  | package(std) string uniqueTempPath() @safe | 
|  | { | 
|  | import std.file : tempDir; | 
|  | import std.path : buildPath; | 
|  | import std.uuid : randomUUID; | 
|  | // Path should contain spaces to test escaping whitespace | 
|  | return buildPath(tempDir(), "std.process temporary file " ~ | 
|  | randomUUID().toString()); | 
|  | } | 
|  |  | 
|  |  | 
|  | version (iOSDerived) {} | 
|  | else: | 
|  |  | 
|  | /** | 
|  | Spawns a new process, optionally assigning it an arbitrary set of standard | 
|  | input, output, and error streams. | 
|  |  | 
|  | The function returns immediately, leaving the child process to execute | 
|  | in parallel with its parent.  It is recommended to always call $(LREF wait) | 
|  | on the returned $(LREF Pid) unless the process was spawned with | 
|  | `Config.detached` flag, as detailed in the documentation for `wait`. | 
|  |  | 
|  | Command_line: | 
|  | There are four overloads of this function.  The first two take an array | 
|  | of strings, `args`, which should contain the program name as the | 
|  | zeroth element and any command-line arguments in subsequent elements. | 
|  | The third and fourth versions are included for convenience, and may be | 
|  | used when there are no command-line arguments.  They take a single string, | 
|  | `program`, which specifies the program name. | 
|  |  | 
|  | Unless a directory is specified in `args[0]` or `program`, | 
|  | `spawnProcess` will search for the program in a platform-dependent | 
|  | manner.  On POSIX systems, it will look for the executable in the | 
|  | directories listed in the PATH environment variable, in the order | 
|  | they are listed.  On Windows, it will search for the executable in | 
|  | the following sequence: | 
|  | $(OL | 
|  | $(LI The directory from which the application loaded.) | 
|  | $(LI The current directory for the parent process.) | 
|  | $(LI The 32-bit Windows system directory.) | 
|  | $(LI The 16-bit Windows system directory.) | 
|  | $(LI The Windows directory.) | 
|  | $(LI The directories listed in the PATH environment variable.) | 
|  | ) | 
|  | --- | 
|  | // Run an executable called "prog" located in the current working | 
|  | // directory: | 
|  | auto pid = spawnProcess("./prog"); | 
|  | scope(exit) wait(pid); | 
|  | // We can do something else while the program runs.  The scope guard | 
|  | // ensures that the process is waited for at the end of the scope. | 
|  | ... | 
|  |  | 
|  | // Run DMD on the file "myprog.d", specifying a few compiler switches: | 
|  | auto dmdPid = spawnProcess(["dmd", "-O", "-release", "-inline", "myprog.d" ]); | 
|  | if (wait(dmdPid) != 0) | 
|  | writeln("Compilation failed!"); | 
|  | --- | 
|  |  | 
|  | Environment_variables: | 
|  | By default, the child process inherits the environment of the parent | 
|  | process, along with any additional variables specified in the `env` | 
|  | parameter.  If the same variable exists in both the parent's environment | 
|  | and in `env`, the latter takes precedence. | 
|  |  | 
|  | If the $(LREF Config.newEnv) flag is set in `config`, the child | 
|  | process will $(I not) inherit the parent's environment.  Its entire | 
|  | environment will then be determined by `env`. | 
|  | --- | 
|  | wait(spawnProcess("myapp", ["foo" : "bar"], Config.newEnv)); | 
|  | --- | 
|  |  | 
|  | Standard_streams: | 
|  | The optional arguments `stdin`, `stdout` and `stderr` may | 
|  | be used to assign arbitrary $(REF File, std,stdio) objects as the standard | 
|  | input, output and error streams, respectively, of the child process.  The | 
|  | former must be opened for reading, while the latter two must be opened for | 
|  | writing.  The default is for the child process to inherit the standard | 
|  | streams of its parent. | 
|  | --- | 
|  | // Run DMD on the file myprog.d, logging any error messages to a | 
|  | // file named errors.log. | 
|  | auto logFile = File("errors.log", "w"); | 
|  | auto pid = spawnProcess(["dmd", "myprog.d"], | 
|  | std.stdio.stdin, | 
|  | std.stdio.stdout, | 
|  | logFile); | 
|  | if (wait(pid) != 0) | 
|  | writeln("Compilation failed. See errors.log for details."); | 
|  | --- | 
|  |  | 
|  | Note that if you pass a `File` object that is $(I not) | 
|  | one of the standard input/output/error streams of the parent process, | 
|  | that stream will by default be $(I closed) in the parent process when | 
|  | this function returns.  See the $(LREF Config) documentation below for | 
|  | information about how to disable this behaviour. | 
|  |  | 
|  | Beware of buffering issues when passing `File` objects to | 
|  | `spawnProcess`.  The child process will inherit the low-level raw | 
|  | read/write offset associated with the underlying file descriptor, but | 
|  | it will not be aware of any buffered data.  In cases where this matters | 
|  | (e.g. when a file should be aligned before being passed on to the | 
|  | child process), it may be a good idea to use unbuffered streams, or at | 
|  | least ensure all relevant buffers are flushed. | 
|  |  | 
|  | Params: | 
|  | args    = An array which contains the program name as the zeroth element | 
|  | and any command-line arguments in the following elements. | 
|  | stdin   = The standard input stream of the child process. | 
|  | This can be any $(REF File, std,stdio) that is opened for reading. | 
|  | By default the child process inherits the parent's input | 
|  | stream. | 
|  | stdout  = The standard output stream of the child process. | 
|  | This can be any $(REF File, std,stdio) that is opened for writing. | 
|  | By default the child process inherits the parent's output stream. | 
|  | stderr  = The standard error stream of the child process. | 
|  | This can be any $(REF File, std,stdio) that is opened for writing. | 
|  | By default the child process inherits the parent's error stream. | 
|  | env     = Additional environment variables for the child process. | 
|  | config  = Flags that control process creation. See $(LREF Config) | 
|  | for an overview of available flags. | 
|  | workDir = The working directory for the new process. | 
|  | By default the child process inherits the parent's working | 
|  | directory. | 
|  |  | 
|  | Returns: | 
|  | A $(LREF Pid) object that corresponds to the spawned process. | 
|  |  | 
|  | Throws: | 
|  | $(LREF ProcessException) on failure to start the process.$(BR) | 
|  | $(REF StdioException, std,stdio) on failure to pass one of the streams | 
|  | to the child process (Windows only).$(BR) | 
|  | $(REF RangeError, core,exception) if `args` is empty. | 
|  | */ | 
|  | Pid spawnProcess(scope const(char[])[] args, | 
|  | File stdin = std.stdio.stdin, | 
|  | File stdout = std.stdio.stdout, | 
|  | File stderr = std.stdio.stderr, | 
|  | const string[string] env = null, | 
|  | Config config = Config.none, | 
|  | scope const char[] workDir = null) | 
|  | @safe | 
|  | { | 
|  | version (Windows) | 
|  | { | 
|  | const commandLine = escapeShellArguments(args); | 
|  | const program = args.length ? args[0] : null; | 
|  | return spawnProcessWin(commandLine, program, stdin, stdout, stderr, env, config, workDir); | 
|  | } | 
|  | else version (Posix) | 
|  | { | 
|  | return spawnProcessPosix(args, stdin, stdout, stderr, env, config, workDir); | 
|  | } | 
|  | else | 
|  | static assert(0); | 
|  | } | 
|  |  | 
|  | /// ditto | 
|  | Pid spawnProcess(scope const(char[])[] args, | 
|  | const string[string] env, | 
|  | Config config = Config.none, | 
|  | scope const(char)[] workDir = null) | 
|  | @trusted // TODO: Should be @safe | 
|  | { | 
|  | return spawnProcess(args, | 
|  | std.stdio.stdin, | 
|  | std.stdio.stdout, | 
|  | std.stdio.stderr, | 
|  | env, | 
|  | config, | 
|  | workDir); | 
|  | } | 
|  |  | 
|  | /// ditto | 
|  | Pid spawnProcess(scope const(char)[] program, | 
|  | File stdin = std.stdio.stdin, | 
|  | File stdout = std.stdio.stdout, | 
|  | File stderr = std.stdio.stderr, | 
|  | const string[string] env = null, | 
|  | Config config = Config.none, | 
|  | scope const(char)[] workDir = null) | 
|  | @trusted | 
|  | { | 
|  | return spawnProcess((&program)[0 .. 1], | 
|  | stdin, stdout, stderr, env, config, workDir); | 
|  | } | 
|  |  | 
|  | /// ditto | 
|  | Pid spawnProcess(scope const(char)[] program, | 
|  | const string[string] env, | 
|  | Config config = Config.none, | 
|  | scope const(char)[] workDir = null) | 
|  | @trusted | 
|  | { | 
|  | return spawnProcess((&program)[0 .. 1], env, config, workDir); | 
|  | } | 
|  |  | 
|  | version (Posix) private enum InternalError : ubyte | 
|  | { | 
|  | noerror, | 
|  | exec, | 
|  | chdir, | 
|  | getrlimit, | 
|  | doubleFork, | 
|  | malloc, | 
|  | preExec, | 
|  | closefds_dup2, | 
|  | } | 
|  |  | 
|  | /* | 
|  | Implementation of spawnProcess() for POSIX. | 
|  |  | 
|  | envz should be a zero-terminated array of zero-terminated strings | 
|  | on the form "var=value". | 
|  | */ | 
|  | version (Posix) | 
|  | 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; | 
|  | import std.algorithm.searching : any; | 
|  | import std.conv : text; | 
|  | import std.path : isDirSeparator; | 
|  | import std.string : toStringz; | 
|  |  | 
|  | if (args.empty) throw new RangeError(); | 
|  | const(char)[] name = args[0]; | 
|  | if (!any!isDirSeparator(name)) | 
|  | { | 
|  | name = searchPathFor(name); | 
|  | if (name is null) | 
|  | throw new ProcessException(text("Executable file not found: ", args[0])); | 
|  | } | 
|  |  | 
|  | // Convert program name and arguments to C-style strings. | 
|  | auto argz = new const(char)*[args.length+1]; | 
|  | argz[0] = toStringz(name); | 
|  | foreach (i; 1 .. args.length) argz[i] = toStringz(args[i]); | 
|  | argz[$-1] = null; | 
|  |  | 
|  | // Prepare environment. | 
|  | auto envz = createEnv(env, !(config.flags & Config.Flags.newEnv)); | 
|  |  | 
|  | // Open the working directory. | 
|  | // We use open in the parent and fchdir in the child | 
|  | // so that most errors (directory doesn't exist, not a directory) | 
|  | // can be propagated as exceptions before forking. | 
|  | int workDirFD = -1; | 
|  | scope(exit) if (workDirFD >= 0) close(workDirFD); | 
|  | if (workDir.length) | 
|  | { | 
|  | import core.sys.posix.fcntl : open, O_RDONLY, stat_t, fstat, S_ISDIR; | 
|  | workDirFD = open(workDir.tempCString(), O_RDONLY); | 
|  | if (workDirFD < 0) | 
|  | throw ProcessException.newFromErrno("Failed to open working directory"); | 
|  | stat_t s; | 
|  | if (fstat(workDirFD, &s) < 0) | 
|  | throw ProcessException.newFromErrno("Failed to stat working directory"); | 
|  | if (!S_ISDIR(s.st_mode)) | 
|  | throw new ProcessException("Not a directory: " ~ cast(string) workDir); | 
|  | } | 
|  |  | 
|  | static int getFD(ref File f) { return core.stdc.stdio.fileno(f.getFP()); } | 
|  |  | 
|  | // Get the file descriptors of the streams. | 
|  | // These could potentially be invalid, but that is OK.  If so, later calls | 
|  | // to dup2() and close() will just silently fail without causing any harm. | 
|  | auto stdinFD  = getFD(stdin); | 
|  | auto stdoutFD = getFD(stdout); | 
|  | auto stderrFD = getFD(stderr); | 
|  |  | 
|  | // We don't have direct access to the errors that may happen in a child process. | 
|  | // So we use this pipe to deliver them. | 
|  | int[2] forkPipe; | 
|  | if (core.sys.posix.unistd.pipe(forkPipe) == 0) | 
|  | setCLOEXEC(forkPipe[1], true); | 
|  | else | 
|  | throw ProcessException.newFromErrno("Could not create pipe to check startup of child"); | 
|  | scope(exit) close(forkPipe[0]); | 
|  |  | 
|  | /* | 
|  | To create detached process, we use double fork technique | 
|  | but we don't have a direct access to the second fork pid from the caller side thus use a pipe. | 
|  | We also can't reuse forkPipe for that purpose | 
|  | because we can't predict the order in which pid and possible error will be written | 
|  | since the first and the second forks will run in parallel. | 
|  | */ | 
|  | int[2] pidPipe; | 
|  | if (config.flags & Config.Flags.detached) | 
|  | { | 
|  | if (core.sys.posix.unistd.pipe(pidPipe) != 0) | 
|  | throw ProcessException.newFromErrno("Could not create pipe to get process pid"); | 
|  | setCLOEXEC(pidPipe[1], true); | 
|  | } | 
|  | scope(exit) if (config.flags & Config.Flags.detached) close(pidPipe[0]); | 
|  |  | 
|  | static void abortOnError(int forkPipeOut, InternalError errorType, int error) nothrow | 
|  | { | 
|  | core.sys.posix.unistd.write(forkPipeOut, &errorType, errorType.sizeof); | 
|  | core.sys.posix.unistd.write(forkPipeOut, &error, error.sizeof); | 
|  | close(forkPipeOut); | 
|  | core.sys.posix.unistd._exit(1); | 
|  | assert(0); | 
|  | } | 
|  |  | 
|  | void closePipeWriteEnds() | 
|  | { | 
|  | close(forkPipe[1]); | 
|  | if (config.flags & Config.Flags.detached) | 
|  | close(pidPipe[1]); | 
|  | } | 
|  |  | 
|  | auto id = core.sys.posix.unistd.fork(); | 
|  | if (id < 0) | 
|  | { | 
|  | closePipeWriteEnds(); | 
|  | throw ProcessException.newFromErrno("Failed to spawn new process"); | 
|  | } | 
|  |  | 
|  | void forkChild() nothrow @nogc | 
|  | { | 
|  | static import core.sys.posix.stdio; | 
|  |  | 
|  | // Child process | 
|  |  | 
|  | // no need for the read end of pipe on child side | 
|  | if (config.flags & Config.Flags.detached) | 
|  | close(pidPipe[0]); | 
|  | close(forkPipe[0]); | 
|  | auto forkPipeOut = forkPipe[1]; | 
|  | immutable pidPipeOut = pidPipe[1]; | 
|  |  | 
|  | // Set the working directory. | 
|  | if (workDirFD >= 0) | 
|  | { | 
|  | if (fchdir(workDirFD) < 0) | 
|  | { | 
|  | // Fail. It is dangerous to run a program | 
|  | // in an unexpected working directory. | 
|  | abortOnError(forkPipeOut, InternalError.chdir, .errno); | 
|  | } | 
|  | close(workDirFD); | 
|  | } | 
|  |  | 
|  | void execProcess() | 
|  | { | 
|  | // Redirect streams and close the old file descriptors. | 
|  | // In the case that stderr is redirected to stdout, we need | 
|  | // to backup the file descriptor since stdout may be redirected | 
|  | // as well. | 
|  | if (stderrFD == STDOUT_FILENO) stderrFD = dup(stderrFD); | 
|  | dup2(stdinFD,  STDIN_FILENO); | 
|  | dup2(stdoutFD, STDOUT_FILENO); | 
|  | dup2(stderrFD, STDERR_FILENO); | 
|  |  | 
|  | // Ensure that the standard streams aren't closed on execute, and | 
|  | // optionally close all other file descriptors. | 
|  | setCLOEXEC(STDIN_FILENO, false); | 
|  | setCLOEXEC(STDOUT_FILENO, false); | 
|  | setCLOEXEC(STDERR_FILENO, false); | 
|  |  | 
|  | if (!(config.flags & Config.Flags.inheritFDs)) | 
|  | { | 
|  | version (FreeBSD) | 
|  | import core.sys.freebsd.unistd : closefrom; | 
|  | else version (OpenBSD) | 
|  | import core.sys.openbsd.unistd : closefrom; | 
|  |  | 
|  | static if (!__traits(compiles, closefrom)) | 
|  | { | 
|  | void fallback (int lowfd) | 
|  | { | 
|  | import core.sys.posix.dirent : dirent, opendir, readdir, closedir, DIR; | 
|  | import core.sys.posix.unistd : close; | 
|  | import core.sys.posix.stdlib : atoi, malloc, free; | 
|  | import core.sys.posix.sys.resource : rlimit, getrlimit, RLIMIT_NOFILE; | 
|  |  | 
|  | // Get the maximum number of file descriptors that could be open. | 
|  | rlimit r; | 
|  | if (getrlimit(RLIMIT_NOFILE, &r) != 0) | 
|  | abortOnError(forkPipeOut, InternalError.getrlimit, .errno); | 
|  |  | 
|  | immutable maxDescriptors = cast(int) r.rlim_cur; | 
|  |  | 
|  | // Missing druntime declaration | 
|  | pragma(mangle, "dirfd") | 
|  | extern(C) nothrow @nogc int dirfd(DIR* dir); | 
|  |  | 
|  | DIR* dir = null; | 
|  |  | 
|  | // We read from /dev/fd or /proc/self/fd only if the limit is high enough | 
|  | if (maxDescriptors > 128*1024) | 
|  | { | 
|  | // Try to open the directory /dev/fd or /proc/self/fd | 
|  | dir = opendir("/dev/fd"); | 
|  | if (dir is null) dir = opendir("/proc/self/fd"); | 
|  | } | 
|  |  | 
|  | // If we have a directory, close all file descriptors except stdin, stdout, and stderr | 
|  | if (dir) | 
|  | { | 
|  | scope(exit) closedir(dir); | 
|  |  | 
|  | int opendirfd = dirfd(dir); | 
|  |  | 
|  | // Iterate over all file descriptors | 
|  | while (true) | 
|  | { | 
|  | dirent* entry = readdir(dir); | 
|  |  | 
|  | if (entry is null) break; | 
|  | if (entry.d_name[0] == '.') continue; | 
|  |  | 
|  | int fd = atoi(cast(char*) entry.d_name); | 
|  |  | 
|  | // Don't close stdin, stdout, stderr, or the directory file descriptor | 
|  | if (fd < lowfd || fd == opendirfd) continue; | 
|  |  | 
|  | close(fd); | 
|  | } | 
|  | } | 
|  | else | 
|  | { | 
|  | // This is going to allocate 8 bytes for each possible file descriptor from lowfd to r.rlim_cur | 
|  | if (maxDescriptors <= 128*1024) | 
|  | { | 
|  | // NOTE: malloc() and getrlimit() are not on the POSIX async | 
|  | // signal safe functions list, but practically this should | 
|  | // not be a problem. Java VM and CPython also use malloc() | 
|  | // in its own implementation via opendir(). | 
|  | import core.stdc.stdlib : malloc; | 
|  | import core.sys.posix.poll : pollfd, poll, POLLNVAL; | 
|  |  | 
|  | immutable maxToClose = maxDescriptors - lowfd; | 
|  |  | 
|  | // Call poll() to see which ones are actually open: | 
|  | auto pfds = cast(pollfd*) malloc(pollfd.sizeof * maxToClose); | 
|  | if (pfds is null) | 
|  | { | 
|  | abortOnError(forkPipeOut, InternalError.malloc, .errno); | 
|  | } | 
|  |  | 
|  | foreach (i; 0 .. maxToClose) | 
|  | { | 
|  | pfds[i].fd = i + lowfd; | 
|  | pfds[i].events = 0; | 
|  | pfds[i].revents = 0; | 
|  | } | 
|  |  | 
|  | if (poll(pfds, maxToClose, 0) < 0) | 
|  | // couldn't use poll, use the slow path. | 
|  | goto LslowClose; | 
|  |  | 
|  | foreach (i; 0 .. maxToClose) | 
|  | { | 
|  | // POLLNVAL will be set if the file descriptor is invalid. | 
|  | if (!(pfds[i].revents & POLLNVAL)) close(pfds[i].fd); | 
|  | } | 
|  | } | 
|  | else | 
|  | { | 
|  | LslowClose: | 
|  | // Fall back to closing everything. | 
|  | foreach (i; lowfd .. maxDescriptors) | 
|  | { | 
|  | close(i); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // closefrom may not be available on the version of glibc we build against. | 
|  | // Until we find a way to perform this check we will try to use dlsym to | 
|  | // check for the function. See: https://github.com/dlang/phobos/pull/9048 | 
|  | version (CRuntime_Glibc) | 
|  | { | 
|  | void closefrom (int lowfd) { | 
|  | static bool tryGlibcClosefrom (int lowfd) { | 
|  | import core.sys.posix.dlfcn : dlopen, dlclose, dlsym, dlerror, RTLD_LAZY; | 
|  |  | 
|  | void *handle = dlopen("libc.so.6", RTLD_LAZY); | 
|  | if (!handle) | 
|  | return false; | 
|  | scope(exit) dlclose(handle); | 
|  |  | 
|  | // Clear errors | 
|  | dlerror(); | 
|  | alias closefromT = extern(C) void function(int) @nogc @system nothrow; | 
|  | auto closefrom = cast(closefromT) dlsym(handle, "closefrom"); | 
|  | if (dlerror()) | 
|  | return false; | 
|  |  | 
|  | closefrom(lowfd); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | if (!tryGlibcClosefrom(lowfd)) | 
|  | fallback(lowfd); | 
|  | } | 
|  | } | 
|  | else | 
|  | alias closefrom = fallback; | 
|  | } | 
|  |  | 
|  | // We need to close all open file descriptors excluding std{in,out,err} | 
|  | // and forkPipeOut because we still need it. | 
|  | // Since the various libc's provide us with `closefrom` move forkPipeOut | 
|  | // to position 3, right after STDERR_FILENO, and close all FDs following that. | 
|  | if (dup2(forkPipeOut, 3) == -1) | 
|  | abortOnError(forkPipeOut, InternalError.closefds_dup2, .errno); | 
|  | forkPipeOut = 3; | 
|  | // forkPipeOut needs to be closed after we call `exec`. | 
|  | setCLOEXEC(forkPipeOut, true); | 
|  | closefrom(forkPipeOut + 1); | 
|  | } | 
|  | else // This is already done if we don't inherit descriptors. | 
|  | { | 
|  | // Close the old file descriptors, unless they are | 
|  | // either of the standard streams. | 
|  | if (stdinFD  > STDERR_FILENO)  close(stdinFD); | 
|  | if (stdoutFD > STDERR_FILENO)  close(stdoutFD); | 
|  | if (stderrFD > STDERR_FILENO)  close(stderrFD); | 
|  | } | 
|  |  | 
|  | if (config.preExecFunction !is null) | 
|  | { | 
|  | if (config.preExecFunction() != true) | 
|  | { | 
|  | abortOnError(forkPipeOut, InternalError.preExec, .errno); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (config.preExecDelegate !is null) | 
|  | { | 
|  | if (config.preExecDelegate() != true) | 
|  | { | 
|  | abortOnError(forkPipeOut, InternalError.preExec, .errno); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Execute program. | 
|  | core.sys.posix.unistd.execve(argz[0], argz.ptr, envz is null ? getEnvironPtr : envz); | 
|  |  | 
|  | // If execution fails, exit as quickly as possible. | 
|  | abortOnError(forkPipeOut, InternalError.exec, .errno); | 
|  | } | 
|  |  | 
|  | if (config.flags & Config.Flags.detached) | 
|  | { | 
|  | auto secondFork = core.sys.posix.unistd.fork(); | 
|  | if (secondFork == 0) | 
|  | { | 
|  | close(pidPipeOut); | 
|  | execProcess(); | 
|  | } | 
|  | else if (secondFork == -1) | 
|  | { | 
|  | auto secondForkErrno = .errno; | 
|  | close(pidPipeOut); | 
|  | abortOnError(forkPipeOut, InternalError.doubleFork, secondForkErrno); | 
|  | } | 
|  | else | 
|  | { | 
|  | core.sys.posix.unistd.write(pidPipeOut, &secondFork, pid_t.sizeof); | 
|  | close(pidPipeOut); | 
|  | close(forkPipeOut); | 
|  | _exit(0); | 
|  | } | 
|  | } | 
|  | else | 
|  | { | 
|  | execProcess(); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (id == 0) | 
|  | { | 
|  | forkChild(); | 
|  | assert(0); | 
|  | } | 
|  | else | 
|  | { | 
|  | closePipeWriteEnds(); | 
|  | auto status = InternalError.noerror; | 
|  | auto readExecResult = core.sys.posix.unistd.read(forkPipe[0], &status, status.sizeof); | 
|  | // Save error number just in case if subsequent "waitpid" fails and overrides errno | 
|  | immutable lastError = .errno; | 
|  |  | 
|  | if (config.flags & Config.Flags.detached) | 
|  | { | 
|  | // Forked child exits right after creating second fork. So it should be safe to wait here. | 
|  | import core.sys.posix.sys.wait : waitpid; | 
|  | int waitResult; | 
|  | waitpid(id, &waitResult, 0); | 
|  | } | 
|  |  | 
|  | if (readExecResult == -1) | 
|  | throw ProcessException.newFromErrno(lastError, "Could not read from pipe to get child status"); | 
|  |  | 
|  | bool owned = true; | 
|  | if (status != InternalError.noerror) | 
|  | { | 
|  | int error; | 
|  | readExecResult = read(forkPipe[0], &error, error.sizeof); | 
|  | string errorMsg; | 
|  | final switch (status) | 
|  | { | 
|  | case InternalError.chdir: | 
|  | errorMsg = "Failed to set working directory"; | 
|  | break; | 
|  | case InternalError.getrlimit: | 
|  | errorMsg = "getrlimit failed"; | 
|  | break; | 
|  | case InternalError.exec: | 
|  | errorMsg = "Failed to execute '" ~ cast(string) name ~ "'"; | 
|  | break; | 
|  | case InternalError.doubleFork: | 
|  | // Can happen only when starting detached process | 
|  | assert(config.flags & Config.Flags.detached); | 
|  | errorMsg = "Failed to fork twice"; | 
|  | break; | 
|  | case InternalError.malloc: | 
|  | errorMsg = "Failed to allocate memory"; | 
|  | break; | 
|  | case InternalError.preExec: | 
|  | errorMsg = "Failed to execute preExecFunction or preExecDelegate"; | 
|  | break; | 
|  | case InternalError.closefds_dup2: | 
|  | assert(!(config.flags & Config.Flags.inheritFDs)); | 
|  | errorMsg = "Failed to close inherited file descriptors"; | 
|  | break; | 
|  | case InternalError.noerror: | 
|  | assert(false); | 
|  | } | 
|  | if (readExecResult == error.sizeof) | 
|  | throw ProcessException.newFromErrno(error, errorMsg); | 
|  | throw new ProcessException(errorMsg); | 
|  | } | 
|  | else if (config.flags & Config.Flags.detached) | 
|  | { | 
|  | owned = false; | 
|  | if (read(pidPipe[0], &id, id.sizeof) != id.sizeof) | 
|  | throw ProcessException.newFromErrno("Could not read from pipe to get detached process id"); | 
|  | } | 
|  |  | 
|  | // Parent process:  Close streams and return. | 
|  | if (!(config.flags & Config.Flags.retainStdin ) && stdinFD  > STDERR_FILENO | 
|  | && stdinFD  != getFD(std.stdio.stdin )) | 
|  | stdin.close(); | 
|  | if (!(config.flags & Config.Flags.retainStdout) && stdoutFD > STDERR_FILENO | 
|  | && stdoutFD != getFD(std.stdio.stdout)) | 
|  | stdout.close(); | 
|  | if (!(config.flags & Config.Flags.retainStderr) && stderrFD > STDERR_FILENO | 
|  | && stderrFD != getFD(std.stdio.stderr)) | 
|  | stderr.close(); | 
|  | return new Pid(id, owned); | 
|  | } | 
|  | } | 
|  |  | 
|  | version (Posix) | 
|  | @system unittest | 
|  | { | 
|  | import std.concurrency : ownerTid, receiveTimeout, send, spawn; | 
|  | import std.datetime : seconds; | 
|  |  | 
|  | sigset_t ss; | 
|  | sigemptyset(&ss); | 
|  | sigaddset(&ss, SIGINT); | 
|  | pthread_sigmask(SIG_BLOCK, &ss, null); | 
|  |  | 
|  | Config config = { | 
|  | preExecFunction: () @trusted @nogc nothrow { | 
|  | // Reset signal handlers | 
|  | sigset_t ss; | 
|  | if (sigfillset(&ss) != 0) | 
|  | { | 
|  | return false; | 
|  | } | 
|  | if (sigprocmask(SIG_UNBLOCK, &ss, null) != 0) | 
|  | { | 
|  | return false; | 
|  | } | 
|  | return true; | 
|  | }, | 
|  | }; | 
|  |  | 
|  | auto pid = spawnProcess(["sleep", "10000"], | 
|  | std.stdio.stdin, | 
|  | std.stdio.stdout, | 
|  | std.stdio.stderr, | 
|  | null, | 
|  | config, | 
|  | null); | 
|  | scope(failure) | 
|  | { | 
|  | kill(pid, SIGKILL); | 
|  | wait(pid); | 
|  | } | 
|  |  | 
|  | // kill the spawned process with SIGINT | 
|  | // and send its return code | 
|  | spawn((shared Pid pid) { | 
|  | auto p = cast() pid; | 
|  | kill(p, SIGINT); | 
|  | auto code = wait(p); | 
|  | assert(code < 0); | 
|  | send(ownerTid, code); | 
|  | }, cast(shared) pid); | 
|  |  | 
|  | auto received = receiveTimeout(3.seconds, (int) {}); | 
|  | assert(received); | 
|  | } | 
|  |  | 
|  | version (Posix) | 
|  | @system unittest | 
|  | { | 
|  | __gshared int j; | 
|  | foreach (i; 0 .. 3) | 
|  | { | 
|  | auto config = Config( | 
|  | preExecFunction: function() @trusted { | 
|  | j = 1; | 
|  | return true; | 
|  | }, | 
|  | preExecDelegate: delegate() @trusted { | 
|  | // j should now be 1, as preExecFunction is called before | 
|  | // preExecDelegate is. | 
|  | _Exit(i + j); | 
|  | return true; | 
|  | }, | 
|  | ); | 
|  | auto pid = spawnProcess(["false"], config: config); | 
|  | assert(wait(pid) == i + 1); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* | 
|  | Implementation of spawnProcess() for Windows. | 
|  |  | 
|  | commandLine must contain the entire command line, properly | 
|  | quoted/escaped as required by CreateProcessW(). | 
|  |  | 
|  | 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 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; | 
|  | import std.conv : text; | 
|  |  | 
|  | if (commandLine.empty) throw new RangeError("Command line is empty"); | 
|  |  | 
|  | // Prepare environment. | 
|  | auto envz = createEnv(env, !(config.flags & Config.Flags.newEnv)); | 
|  |  | 
|  | // Startup info for CreateProcessW(). | 
|  | STARTUPINFO_W startinfo; | 
|  | startinfo.cb = startinfo.sizeof; | 
|  | static int getFD(ref File f) { return f.isOpen ? f.fileno : -1; } | 
|  |  | 
|  | // Extract file descriptors and HANDLEs from the streams and make the | 
|  | // handles inheritable. | 
|  | static void prepareStream(ref File file, DWORD stdHandle, string which, | 
|  | out int fileDescriptor, out HANDLE handle) | 
|  | { | 
|  | enum _NO_CONSOLE_FILENO = cast(HANDLE)-2; | 
|  | fileDescriptor = getFD(file); | 
|  | handle = null; | 
|  | if (fileDescriptor >= 0) | 
|  | handle = file.windowsHandle; | 
|  | // Windows GUI applications have a fd but not a valid Windows HANDLE. | 
|  | if (handle is null || handle == INVALID_HANDLE_VALUE || handle == _NO_CONSOLE_FILENO) | 
|  | handle = GetStdHandle(stdHandle); | 
|  |  | 
|  | DWORD dwFlags; | 
|  | if (GetHandleInformation(handle, &dwFlags)) | 
|  | { | 
|  | if (!(dwFlags & HANDLE_FLAG_INHERIT)) | 
|  | { | 
|  | if (!SetHandleInformation(handle, | 
|  | HANDLE_FLAG_INHERIT, | 
|  | HANDLE_FLAG_INHERIT)) | 
|  | { | 
|  | throw new StdioException( | 
|  | "Failed to make "~which~" stream inheritable by child process (" | 
|  | ~generateSysErrorMsg() ~ ')', | 
|  | 0); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | int stdinFD = -1, stdoutFD = -1, stderrFD = -1; | 
|  | prepareStream(stdin,  STD_INPUT_HANDLE,  "stdin" , stdinFD,  startinfo.hStdInput ); | 
|  | prepareStream(stdout, STD_OUTPUT_HANDLE, "stdout", stdoutFD, startinfo.hStdOutput); | 
|  | prepareStream(stderr, STD_ERROR_HANDLE,  "stderr", stderrFD, startinfo.hStdError ); | 
|  |  | 
|  | if ((startinfo.hStdInput  != null && startinfo.hStdInput  != INVALID_HANDLE_VALUE) | 
|  | || (startinfo.hStdOutput != null && startinfo.hStdOutput != INVALID_HANDLE_VALUE) | 
|  | || (startinfo.hStdError  != null && startinfo.hStdError  != INVALID_HANDLE_VALUE)) | 
|  | startinfo.dwFlags = STARTF_USESTDHANDLES; | 
|  |  | 
|  | // Create process. | 
|  | PROCESS_INFORMATION pi; | 
|  | DWORD dwCreationFlags = | 
|  | CREATE_UNICODE_ENVIRONMENT | | 
|  | ((config.flags & Config.Flags.suppressConsole) ? CREATE_NO_WINDOW : 0); | 
|  | // workaround until https://issues.dlang.org/show_bug.cgi?id=14696 is fixed | 
|  | auto pworkDir = workDir.tempCStringW(); | 
|  | if (!CreateProcessW(null, commandLine.tempCStringW().buffPtr, | 
|  | null, null, true, dwCreationFlags, | 
|  | envz, workDir.length ? pworkDir : null, &startinfo, &pi)) | 
|  | throw ProcessException.newFromLastError("Failed to spawn process \"" ~ cast(string) program ~ '"'); | 
|  |  | 
|  | // figure out if we should close any of the streams | 
|  | if (!(config.flags & Config.Flags.retainStdin ) && stdinFD  > STDERR_FILENO | 
|  | && stdinFD  != getFD(std.stdio.stdin )) | 
|  | stdin.close(); | 
|  | if (!(config.flags & Config.Flags.retainStdout) && stdoutFD > STDERR_FILENO | 
|  | && stdoutFD != getFD(std.stdio.stdout)) | 
|  | stdout.close(); | 
|  | if (!(config.flags & Config.Flags.retainStderr) && stderrFD > STDERR_FILENO | 
|  | && stderrFD != getFD(std.stdio.stderr)) | 
|  | stderr.close(); | 
|  |  | 
|  | // close the thread handle in the process info structure | 
|  | CloseHandle(pi.hThread); | 
|  | if (config.flags & Config.Flags.detached) | 
|  | { | 
|  | CloseHandle(pi.hProcess); | 
|  | return new Pid(pi.dwProcessId); | 
|  | } | 
|  | return new Pid(pi.dwProcessId, pi.hProcess); | 
|  | } | 
|  |  | 
|  | // Converts childEnv to a zero-terminated array of zero-terminated strings | 
|  | // on the form "name=value", optionally adding those of the current process' | 
|  | // environment strings that are not present in childEnv.  If the parent's | 
|  | // environment should be inherited without modification, this function | 
|  | // returns null. | 
|  | version (Posix) | 
|  | private const(char*)* createEnv(const string[string] childEnv, | 
|  | bool mergeWithParentEnv) | 
|  | { | 
|  | // Determine the number of strings in the parent's environment. | 
|  | int parentEnvLength = 0; | 
|  | auto environ = getEnvironPtr; | 
|  | if (mergeWithParentEnv) | 
|  | { | 
|  | if (childEnv.length == 0) return null; | 
|  | while (environ[parentEnvLength] != null) ++parentEnvLength; | 
|  | } | 
|  |  | 
|  | // Convert the "new" variables to C-style strings. | 
|  | auto envz = new const(char)*[parentEnvLength + childEnv.length + 1]; | 
|  | int pos = 0; | 
|  | foreach (var, val; childEnv) | 
|  | envz[pos++] = (var~'='~val~'\0').ptr; | 
|  |  | 
|  | // Add the parent's environment. | 
|  | foreach (environStr; environ[0 .. parentEnvLength]) | 
|  | { | 
|  | int eqPos = 0; | 
|  | while (environStr[eqPos] != '=' && environStr[eqPos] != '\0') ++eqPos; | 
|  | if (environStr[eqPos] != '=') continue; | 
|  | auto var = environStr[0 .. eqPos]; | 
|  | if (var in childEnv) continue; | 
|  | envz[pos++] = environStr; | 
|  | } | 
|  | envz[pos] = null; | 
|  | return envz.ptr; | 
|  | } | 
|  |  | 
|  | version (Posix) @system unittest | 
|  | { | 
|  | auto e1 = createEnv(null, false); | 
|  | assert(e1 != null && *e1 == null); | 
|  |  | 
|  | auto e2 = createEnv(null, true); | 
|  | assert(e2 == null); | 
|  |  | 
|  | auto e3 = createEnv(["foo" : "bar", "hello" : "world"], false); | 
|  | assert(e3 != null && e3[0] != null && e3[1] != null && e3[2] == null); | 
|  | assert((e3[0][0 .. 8] == "foo=bar\0" && e3[1][0 .. 12] == "hello=world\0") | 
|  | || (e3[0][0 .. 12] == "hello=world\0" && e3[1][0 .. 8] == "foo=bar\0")); | 
|  | } | 
|  |  | 
|  |  | 
|  | // Converts childEnv to a Windows environment block, which is on the form | 
|  | // "name1=value1\0name2=value2\0...nameN=valueN\0\0", optionally adding | 
|  | // those of the current process' environment strings that are not present | 
|  | // in childEnv.  Returns null if the parent's environment should be | 
|  | // inherited without modification, as this is what is expected by | 
|  | // CreateProcess(). | 
|  | version (Windows) | 
|  | private LPVOID createEnv(const string[string] childEnv, | 
|  | bool mergeWithParentEnv) | 
|  | { | 
|  | if (mergeWithParentEnv && childEnv.length == 0) return null; | 
|  | import std.array : appender; | 
|  | import std.uni : toUpper; | 
|  | auto envz = appender!(wchar[])(); | 
|  | void put(string var, string val) | 
|  | { | 
|  | envz.put(var); | 
|  | envz.put('='); | 
|  | envz.put(val); | 
|  | envz.put(cast(wchar) '\0'); | 
|  | } | 
|  |  | 
|  | // Add the variables in childEnv, removing them from parentEnv | 
|  | // if they exist there too. | 
|  | auto parentEnv = mergeWithParentEnv ? environment.toAA() : null; | 
|  | foreach (k, v; childEnv) | 
|  | { | 
|  | auto uk = toUpper(k); | 
|  | put(uk, v); | 
|  | if (uk in parentEnv) parentEnv.remove(uk); | 
|  | } | 
|  |  | 
|  | // Add remaining parent environment variables. | 
|  | foreach (k, v; parentEnv) put(k, v); | 
|  |  | 
|  | // Two final zeros are needed in case there aren't any environment vars, | 
|  | // and the last one does no harm when there are. | 
|  | envz.put("\0\0"w); | 
|  | return envz.data.ptr; | 
|  | } | 
|  |  | 
|  | version (Windows) @system unittest | 
|  | { | 
|  | assert(createEnv(null, true) == null); | 
|  | assert((cast(wchar*) createEnv(null, false))[0 .. 2] == "\0\0"w); | 
|  | auto e1 = (cast(wchar*) createEnv(["foo":"bar", "ab":"c"], false))[0 .. 14]; | 
|  | assert(e1 == "FOO=bar\0AB=c\0\0"w || e1 == "AB=c\0FOO=bar\0\0"w); | 
|  | } | 
|  |  | 
|  | // Searches the PATH variable for the given executable file, | 
|  | // (checking that it is in fact executable). | 
|  | version (Posix) | 
|  | package(std) string searchPathFor(scope const(char)[] executable) | 
|  | @safe | 
|  | { | 
|  | import std.algorithm.iteration : splitter; | 
|  | import std.conv : to; | 
|  | import std.path : chainPath; | 
|  |  | 
|  | typeof(return) result; | 
|  |  | 
|  | environment.getImpl("PATH", | 
|  | (scope const(char)[] path) | 
|  | { | 
|  | if (!path) | 
|  | return; | 
|  |  | 
|  | foreach (dir; splitter(path, ":")) | 
|  | { | 
|  | auto execPath = chainPath(dir, executable); | 
|  | if (isExecutable(execPath)) | 
|  | { | 
|  | result = execPath.to!(typeof(result)); | 
|  | return; | 
|  | } | 
|  | } | 
|  | }); | 
|  |  | 
|  | return result; | 
|  | } | 
|  |  | 
|  | // Checks whether the file exists and can be executed by the | 
|  | // current user. | 
|  | version (Posix) | 
|  | private bool isExecutable(R)(R path) @trusted nothrow @nogc | 
|  | if (isSomeFiniteCharInputRange!R) | 
|  | { | 
|  | return (access(path.tempCString(), X_OK) == 0); | 
|  | } | 
|  |  | 
|  | version (Posix) @safe unittest | 
|  | { | 
|  | import std.algorithm; | 
|  | auto lsPath = searchPathFor("ls"); | 
|  | assert(!lsPath.empty); | 
|  | assert(lsPath[0] == '/'); | 
|  | assert(lsPath.endsWith("ls")); | 
|  | auto unlikely = searchPathFor("lkmqwpoialhggyaofijadsohufoiqezm"); | 
|  | assert(unlikely is null, "Are you kidding me?"); | 
|  | } | 
|  |  | 
|  | // Sets or unsets the FD_CLOEXEC flag on the given file descriptor. | 
|  | version (Posix) | 
|  | private void setCLOEXEC(int fd, bool on) nothrow @nogc | 
|  | { | 
|  | import core.sys.posix.fcntl : fcntl, F_GETFD, FD_CLOEXEC, F_SETFD; | 
|  | auto flags = fcntl(fd, F_GETFD); | 
|  | if (flags >= 0) | 
|  | { | 
|  | if (on) flags |= FD_CLOEXEC; | 
|  | else    flags &= ~(cast(typeof(flags)) FD_CLOEXEC); | 
|  | flags = fcntl(fd, F_SETFD, flags); | 
|  | } | 
|  | assert(flags != -1 || .errno == EBADF); | 
|  | } | 
|  |  | 
|  | @system unittest // Command line arguments in spawnProcess(). | 
|  | { | 
|  | version (Windows) TestScript prog = | 
|  | "if not [%~1]==[foo] ( exit 1 ) | 
|  | if not [%~2]==[bar] ( exit 2 ) | 
|  | exit 0"; | 
|  | else version (Posix) TestScript prog = | 
|  | `if test "$1" != "foo"; then exit 1; fi | 
|  | if test "$2" != "bar"; then exit 2; fi | 
|  | exit 0`; | 
|  | assert(wait(spawnProcess(prog.path)) == 1); | 
|  | assert(wait(spawnProcess([prog.path])) == 1); | 
|  | assert(wait(spawnProcess([prog.path, "foo"])) == 2); | 
|  | assert(wait(spawnProcess([prog.path, "foo", "baz"])) == 2); | 
|  | assert(wait(spawnProcess([prog.path, "foo", "bar"])) == 0); | 
|  | } | 
|  |  | 
|  | // test that file descriptors are correctly closed / left open. | 
|  | // ideally this would be done by the child process making libc | 
|  | // calls, but we make do... | 
|  | version (Posix) @system unittest | 
|  | { | 
|  | import core.stdc.errno : errno; | 
|  | import core.sys.posix.fcntl : open, O_RDONLY; | 
|  | import core.sys.posix.unistd : close; | 
|  | import std.algorithm.searching : canFind, findSplitBefore; | 
|  | import std.array : split; | 
|  | import std.conv : to; | 
|  | static import std.file; | 
|  | import std.functional : reverseArgs; | 
|  | import std.path : buildPath; | 
|  |  | 
|  | auto directory = uniqueTempPath(); | 
|  | std.file.mkdir(directory); | 
|  | scope(exit) std.file.rmdirRecurse(directory); | 
|  | auto path = buildPath(directory, "tmp"); | 
|  | std.file.write(path, null); | 
|  | errno = 0; | 
|  | auto fd = open(path.tempCString, O_RDONLY); | 
|  | if (fd == -1) | 
|  | { | 
|  | import core.stdc.string : strerror; | 
|  | import std.stdio : stderr; | 
|  | import std.string : fromStringz; | 
|  |  | 
|  | // For the CI logs | 
|  | stderr.writefln("%s: could not open '%s': %s", | 
|  | __FUNCTION__, path, strerror(errno).fromStringz); | 
|  | // TODO: should we retry here instead? | 
|  | return; | 
|  | } | 
|  | scope(exit) close(fd); | 
|  |  | 
|  | // command >&2 (or any other number) checks whethether that number | 
|  | // file descriptor is open. | 
|  | // Can't use this for arbitrary descriptors as many shells only support | 
|  | // single digit fds. | 
|  | TestScript testDefaults = `command >&0 && command >&1 && command >&2`; | 
|  | assert(execute(testDefaults.path).status == 0); | 
|  | assert(execute(testDefaults.path, null, Config.inheritFDs).status == 0); | 
|  |  | 
|  | // Try a few different methods to check whether there are any | 
|  | // incorrectly-open files. | 
|  | void testFDs() | 
|  | { | 
|  | // try /proc/<pid>/fd/ on linux | 
|  | version (linux) | 
|  | { | 
|  | TestScript proc = "ls /proc/$$/fd"; | 
|  | auto procRes = execute(proc.path, null); | 
|  | if (procRes.status == 0) | 
|  | { | 
|  | auto fdStr = fd.to!string; | 
|  | assert(!procRes.output.split.canFind(fdStr)); | 
|  | assert(execute(proc.path, null, Config.inheritFDs) | 
|  | .output.split.canFind(fdStr)); | 
|  | return; | 
|  | } | 
|  | } | 
|  |  | 
|  | // try fuser (might sometimes need permissions) | 
|  | TestScript fuser = "echo $$ && fuser -f " ~ path; | 
|  | auto fuserRes = execute(fuser.path, null); | 
|  | if (fuserRes.status == 0) | 
|  | { | 
|  | assert(!reverseArgs!canFind(fuserRes | 
|  | .output.findSplitBefore("\n").expand)); | 
|  | assert(reverseArgs!canFind(execute(fuser.path, null, Config.inheritFDs) | 
|  | .output.findSplitBefore("\n").expand)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // last resort, try lsof (not available on all Posix) | 
|  | TestScript lsof = "lsof -p$$"; | 
|  | auto lsofRes = execute(lsof.path, null); | 
|  | if (lsofRes.status == 0) | 
|  | { | 
|  | assert(!lsofRes.output.canFind(path)); | 
|  | auto lsofOut = execute(lsof.path, null, Config.inheritFDs).output; | 
|  | if (!lsofOut.canFind(path)) | 
|  | { | 
|  | std.stdio.stderr.writeln(__FILE__, ':', __LINE__, | 
|  | ": Warning: unexpected lsof output:", lsofOut); | 
|  | } | 
|  | return; | 
|  | } | 
|  |  | 
|  | std.stdio.stderr.writeln(__FILE__, ':', __LINE__, | 
|  | ": Warning: Couldn't find any way to check open files"); | 
|  | } | 
|  | testFDs(); | 
|  | } | 
|  |  | 
|  | @system unittest // Environment variables in spawnProcess(). | 
|  | { | 
|  | // We really should use set /a on Windows, but Wine doesn't support it. | 
|  | version (Windows) TestScript envProg = | 
|  | `if [%STD_PROCESS_UNITTEST1%] == [1] ( | 
|  | if [%STD_PROCESS_UNITTEST2%] == [2] (exit 3) | 
|  | exit 1 | 
|  | ) | 
|  | if [%STD_PROCESS_UNITTEST1%] == [4] ( | 
|  | if [%STD_PROCESS_UNITTEST2%] == [2] (exit 6) | 
|  | exit 4 | 
|  | ) | 
|  | if [%STD_PROCESS_UNITTEST2%] == [2] (exit 2) | 
|  | exit 0`; | 
|  | version (Posix) TestScript envProg = | 
|  | `if test "$std_process_unittest1" = ""; then | 
|  | std_process_unittest1=0 | 
|  | fi | 
|  | if test "$std_process_unittest2" = ""; then | 
|  | std_process_unittest2=0 | 
|  | fi | 
|  | exit $(($std_process_unittest1+$std_process_unittest2))`; | 
|  |  | 
|  | environment.remove("std_process_unittest1"); // Just in case. | 
|  | environment.remove("std_process_unittest2"); | 
|  | assert(wait(spawnProcess(envProg.path)) == 0); | 
|  | assert(wait(spawnProcess(envProg.path, null, Config.newEnv)) == 0); | 
|  |  | 
|  | environment["std_process_unittest1"] = "1"; | 
|  | assert(wait(spawnProcess(envProg.path)) == 1); | 
|  | assert(wait(spawnProcess(envProg.path, null, Config.newEnv)) == 0); | 
|  |  | 
|  | auto env = ["std_process_unittest2" : "2"]; | 
|  | assert(wait(spawnProcess(envProg.path, env)) == 3); | 
|  | assert(wait(spawnProcess(envProg.path, env, Config.newEnv)) == 2); | 
|  |  | 
|  | env["std_process_unittest1"] = "4"; | 
|  | assert(wait(spawnProcess(envProg.path, env)) == 6); | 
|  | assert(wait(spawnProcess(envProg.path, env, Config.newEnv)) == 6); | 
|  |  | 
|  | environment.remove("std_process_unittest1"); | 
|  | assert(wait(spawnProcess(envProg.path, env)) == 6); | 
|  | assert(wait(spawnProcess(envProg.path, env, Config.newEnv)) == 6); | 
|  | } | 
|  |  | 
|  | @system unittest // Stream redirection in spawnProcess(). | 
|  | { | 
|  | import std.path : buildPath; | 
|  | import std.string; | 
|  | version (Windows) TestScript prog = | 
|  | "set /p INPUT= | 
|  | echo %INPUT% output %~1 | 
|  | echo %INPUT% error %~2 1>&2 | 
|  | echo done > %3"; | 
|  | else version (Posix) TestScript prog = | 
|  | "read INPUT | 
|  | echo $INPUT output $1 | 
|  | echo $INPUT error $2 >&2 | 
|  | echo done > \"$3\""; | 
|  |  | 
|  | // Pipes | 
|  | void testPipes(Config config) | 
|  | { | 
|  | import std.file : tempDir, exists, remove; | 
|  | import std.uuid : randomUUID; | 
|  | import std.exception : collectException; | 
|  | auto pipei = pipe(); | 
|  | auto pipeo = pipe(); | 
|  | auto pipee = pipe(); | 
|  | auto done = buildPath(tempDir(), randomUUID().toString()); | 
|  | auto pid = spawnProcess([prog.path, "foo", "bar", done], | 
|  | pipei.readEnd, pipeo.writeEnd, pipee.writeEnd, null, config); | 
|  | pipei.writeEnd.writeln("input"); | 
|  | pipei.writeEnd.flush(); | 
|  | assert(pipeo.readEnd.readln().chomp() == "input output foo"); | 
|  | assert(pipee.readEnd.readln().chomp().stripRight() == "input error bar"); | 
|  | if (config.flags & Config.Flags.detached) | 
|  | while (!done.exists) Thread.sleep(10.msecs); | 
|  | else | 
|  | wait(pid); | 
|  | while (remove(done).collectException) Thread.sleep(10.msecs); | 
|  | } | 
|  |  | 
|  | // Files | 
|  | void testFiles(Config config) | 
|  | { | 
|  | import std.ascii : newline; | 
|  | import std.file : tempDir, exists, remove, readText, write; | 
|  | import std.uuid : randomUUID; | 
|  | import std.exception : collectException; | 
|  | auto pathi = buildPath(tempDir(), randomUUID().toString()); | 
|  | auto patho = buildPath(tempDir(), randomUUID().toString()); | 
|  | auto pathe = buildPath(tempDir(), randomUUID().toString()); | 
|  | write(pathi, "INPUT" ~ newline); | 
|  | auto filei = File(pathi, "r"); | 
|  | auto fileo = File(patho, "w"); | 
|  | auto filee = File(pathe, "w"); | 
|  | auto done = buildPath(tempDir(), randomUUID().toString()); | 
|  | auto pid = spawnProcess([prog.path, "bar", "baz", done], filei, fileo, filee, null, config); | 
|  | if (config.flags & Config.Flags.detached) | 
|  | while (!done.exists) Thread.sleep(10.msecs); | 
|  | else | 
|  | wait(pid); | 
|  | assert(readText(patho).chomp() == "INPUT output bar"); | 
|  | assert(readText(pathe).chomp().stripRight() == "INPUT error baz"); | 
|  | while (remove(pathi).collectException) Thread.sleep(10.msecs); | 
|  | while (remove(patho).collectException) Thread.sleep(10.msecs); | 
|  | while (remove(pathe).collectException) Thread.sleep(10.msecs); | 
|  | while (remove(done).collectException) Thread.sleep(10.msecs); | 
|  | } | 
|  |  | 
|  | testPipes(Config.none); | 
|  | testFiles(Config.none); | 
|  | testPipes(Config.detached); | 
|  | testFiles(Config.detached); | 
|  | } | 
|  |  | 
|  | @system unittest // Error handling in spawnProcess() | 
|  | { | 
|  | import std.algorithm.searching : canFind; | 
|  | import std.exception : assertThrown, collectExceptionMsg; | 
|  |  | 
|  | static void testNotFoundException(string program) | 
|  | { | 
|  | assert(collectExceptionMsg!ProcessException(spawnProcess(program)).canFind(program)); | 
|  | assert(collectExceptionMsg!ProcessException(spawnProcess(program, null, Config.detached)).canFind(program)); | 
|  | } | 
|  | testNotFoundException("ewrgiuhrifuheiohnmnvqweoijwf"); | 
|  | testNotFoundException("./rgiuhrifuheiohnmnvqweoijwf"); | 
|  |  | 
|  | // can't execute malformed file with executable permissions | 
|  | version (Posix) | 
|  | { | 
|  | import std.path : buildPath; | 
|  | import std.file : remove, write, setAttributes, tempDir; | 
|  | import core.sys.posix.sys.stat : S_IRUSR, S_IWUSR, S_IXUSR, S_IRGRP, S_IXGRP, S_IROTH, S_IXOTH; | 
|  | import std.conv : to; | 
|  | string deleteme = buildPath(tempDir(), "deleteme.std.process.unittest.pid") ~ to!string(thisProcessID); | 
|  | write(deleteme, ""); | 
|  | scope(exit) remove(deleteme); | 
|  | setAttributes(deleteme, S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH); | 
|  | assertThrown!ProcessException(spawnProcess(deleteme)); | 
|  | assertThrown!ProcessException(spawnProcess(deleteme, null, Config.detached)); | 
|  | } | 
|  | } | 
|  |  | 
|  | @system unittest // Specifying a working directory. | 
|  | { | 
|  | import std.path; | 
|  | import std.file; | 
|  | TestScript prog = "echo foo>bar"; | 
|  |  | 
|  | auto directory = uniqueTempPath(); | 
|  | mkdir(directory); | 
|  | scope(exit) rmdirRecurse(directory); | 
|  |  | 
|  | auto pid = spawnProcess([prog.path], null, Config.none, directory); | 
|  | wait(pid); | 
|  | assert(exists(buildPath(directory, "bar"))); | 
|  | } | 
|  |  | 
|  | @system unittest // Specifying a bad working directory. | 
|  | { | 
|  | import std.exception : assertThrown; | 
|  | import std.file; | 
|  | TestScript prog = "echo"; | 
|  |  | 
|  | auto directory = uniqueTempPath(); | 
|  | assertThrown!ProcessException(spawnProcess([prog.path], null, Config.none, directory)); | 
|  | assertThrown!ProcessException(spawnProcess([prog.path], null, Config.detached, directory)); | 
|  |  | 
|  | std.file.write(directory, "foo"); | 
|  | scope(exit) remove(directory); | 
|  | assertThrown!ProcessException(spawnProcess([prog.path], null, Config.none, directory)); | 
|  | assertThrown!ProcessException(spawnProcess([prog.path], null, Config.detached, directory)); | 
|  |  | 
|  | // can't run in directory if user does not have search permission on this directory | 
|  | version (Posix) | 
|  | { | 
|  | if (core.sys.posix.unistd.getuid() != 0) | 
|  | { | 
|  | import core.sys.posix.sys.stat : S_IRUSR; | 
|  | auto directoryNoSearch = uniqueTempPath(); | 
|  | mkdir(directoryNoSearch); | 
|  | scope(exit) rmdirRecurse(directoryNoSearch); | 
|  | setAttributes(directoryNoSearch, S_IRUSR); | 
|  | assertThrown!ProcessException(spawnProcess(prog.path, null, Config.none, directoryNoSearch)); | 
|  | assertThrown!ProcessException(spawnProcess(prog.path, null, Config.detached, directoryNoSearch)); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | @system unittest // Specifying empty working directory. | 
|  | { | 
|  | TestScript prog = ""; | 
|  |  | 
|  | string directory = ""; | 
|  | assert(directory.ptr && !directory.length); | 
|  | spawnProcess([prog.path], null, Config.none, directory).wait(); | 
|  | } | 
|  |  | 
|  | // Reopening the standard streams (https://issues.dlang.org/show_bug.cgi?id=13258) | 
|  | @system unittest | 
|  | { | 
|  | import std.string; | 
|  | import std.file; | 
|  | void fun() | 
|  | { | 
|  | spawnShell("echo foo").wait(); | 
|  | spawnShell("echo bar").wait(); | 
|  | } | 
|  |  | 
|  | auto tmpFile = uniqueTempPath(); | 
|  | scope(exit) if (exists(tmpFile)) remove(tmpFile); | 
|  |  | 
|  | { | 
|  | auto oldOut = std.stdio.stdout; | 
|  | scope(exit) std.stdio.stdout = oldOut; | 
|  |  | 
|  | std.stdio.stdout = File(tmpFile, "w"); | 
|  | fun(); | 
|  | std.stdio.stdout.close(); | 
|  | } | 
|  |  | 
|  | auto lines = readText(tmpFile).splitLines(); | 
|  | assert(lines == ["foo", "bar"]); | 
|  | } | 
|  |  | 
|  | // MSVCRT workaround (https://issues.dlang.org/show_bug.cgi?id=14422) | 
|  | version (Windows) | 
|  | @system unittest | 
|  | { | 
|  | auto fn = uniqueTempPath(); | 
|  | scope(exit) if (exists(fn)) remove(fn); | 
|  | std.file.write(fn, "AAAAAAAAAA"); | 
|  |  | 
|  | auto f = File(fn, "a"); | 
|  | spawnProcess(["cmd", "/c", "echo BBBBB"], std.stdio.stdin, f).wait(); | 
|  |  | 
|  | auto data = readText(fn); | 
|  | assert(data == "AAAAAAAAAABBBBB\r\n", data); | 
|  | } | 
|  |  | 
|  | // https://issues.dlang.org/show_bug.cgi?id=20765 | 
|  | // Test that running processes with relative path works in conjunction | 
|  | // with indicating a workDir. | 
|  | version (Posix) @system unittest | 
|  | { | 
|  | import std.file : mkdir, write, setAttributes, rmdirRecurse; | 
|  | import std.conv : octal; | 
|  |  | 
|  | auto dir = uniqueTempPath(); | 
|  | mkdir(dir); | 
|  | scope(exit) rmdirRecurse(dir); | 
|  | write(dir ~ "/program", "#!/bin/sh\necho Hello"); | 
|  | setAttributes(dir ~ "/program", octal!700); | 
|  |  | 
|  | assert(execute(["./program"], null, Config.none, size_t.max, dir).output == "Hello\n"); | 
|  | } | 
|  |  | 
|  | /** | 
|  | A variation on $(LREF spawnProcess) that runs the given _command through | 
|  | the current user's preferred _command interpreter (aka. shell). | 
|  |  | 
|  | The string `command` is passed verbatim to the shell, and is therefore | 
|  | subject to its rules about _command structure, argument/filename quoting | 
|  | and escaping of special characters. | 
|  | The path to the shell executable defaults to $(LREF nativeShell). | 
|  |  | 
|  | In all other respects this function works just like `spawnProcess`. | 
|  | Please refer to the $(LREF spawnProcess) documentation for descriptions | 
|  | of the other function parameters, the return value and any exceptions | 
|  | that may be thrown. | 
|  | --- | 
|  | // Run the command/program "foo" on the file named "my file.txt", and | 
|  | // redirect its output into foo.log. | 
|  | auto pid = spawnShell(`foo "my file.txt" > foo.log`); | 
|  | wait(pid); | 
|  | --- | 
|  |  | 
|  | See_also: | 
|  | $(LREF escapeShellCommand), which may be helpful in constructing a | 
|  | properly quoted and escaped shell _command line for the current platform. | 
|  | */ | 
|  | Pid spawnShell(scope const(char)[] command, | 
|  | File stdin = std.stdio.stdin, | 
|  | File stdout = std.stdio.stdout, | 
|  | File stderr = std.stdio.stderr, | 
|  | scope const string[string] env = null, | 
|  | Config config = Config.none, | 
|  | scope const(char)[] workDir = null, | 
|  | scope string shellPath = nativeShell) | 
|  | @trusted // See reason below | 
|  | { | 
|  | version (Windows) | 
|  | { | 
|  | // CMD does not parse its arguments like other programs. | 
|  | // It does not use CommandLineToArgvW. | 
|  | // Instead, it treats the first and last quote specially. | 
|  | // See CMD.EXE /? for details. | 
|  | const commandLine = escapeShellFileName(shellPath) | 
|  | ~ ` ` ~ shellSwitch ~ ` "` ~ command ~ `"`; | 
|  | return spawnProcessWin(commandLine, shellPath, stdin, stdout, stderr, env, config, workDir); | 
|  | } | 
|  | else version (Posix) | 
|  | { | 
|  | const(char)[][3] args; | 
|  | args[0] = shellPath; | 
|  | args[1] = shellSwitch; | 
|  | args[2] = command; | 
|  | /* The passing of args converts the static array, which is initialized with `scope` pointers, | 
|  | * to a dynamic array, which is also a scope parameter. So, it is a scope pointer to a | 
|  | * scope pointer, which although is safely used here, D doesn't allow transitive scope. | 
|  | * See https://github.com/dlang/dmd/pull/10951 | 
|  | */ | 
|  | return spawnProcessPosix(args, stdin, stdout, stderr, env, config, workDir); | 
|  | } | 
|  | else | 
|  | static assert(0); | 
|  | } | 
|  |  | 
|  | /// ditto | 
|  | Pid spawnShell(scope const(char)[] command, | 
|  | scope const string[string] env, | 
|  | Config config = Config.none, | 
|  | scope const(char)[] workDir = null, | 
|  | scope string shellPath = nativeShell) | 
|  | @trusted // TODO: Should be @safe | 
|  | { | 
|  | return spawnShell(command, | 
|  | std.stdio.stdin, | 
|  | std.stdio.stdout, | 
|  | std.stdio.stderr, | 
|  | env, | 
|  | config, | 
|  | workDir, | 
|  | shellPath); | 
|  | } | 
|  |  | 
|  | @system unittest | 
|  | { | 
|  | version (Windows) | 
|  | auto cmd = "echo %FOO%"; | 
|  | else version (Posix) | 
|  | auto cmd = "echo $foo"; | 
|  | import std.file; | 
|  | auto tmpFile = uniqueTempPath(); | 
|  | scope(exit) if (exists(tmpFile)) remove(tmpFile); | 
|  | auto redir = "> \""~tmpFile~'"'; | 
|  | auto env = ["foo" : "bar"]; | 
|  | assert(wait(spawnShell(cmd~redir, env)) == 0); | 
|  | auto f = File(tmpFile, "a"); | 
|  | version (CRuntime_Microsoft) f.seek(0, SEEK_END); // MSVCRT probably seeks to the end when writing, not before | 
|  | assert(wait(spawnShell(cmd, std.stdio.stdin, f, std.stdio.stderr, env)) == 0); | 
|  | f.close(); | 
|  | auto output = std.file.readText(tmpFile); | 
|  | assert(output == "bar\nbar\n" || output == "bar\r\nbar\r\n"); | 
|  | } | 
|  |  | 
|  | version (Windows) | 
|  | @system unittest | 
|  | { | 
|  | import std.string; | 
|  | import std.conv : text; | 
|  | TestScript prog = "echo %0 %*"; | 
|  | auto outputFn = uniqueTempPath(); | 
|  | scope(exit) if (exists(outputFn)) remove(outputFn); | 
|  | auto args = [`a b c`, `a\b\c\`, `a"b"c"`]; | 
|  | auto result = executeShell( | 
|  | escapeShellCommand([prog.path] ~ args) | 
|  | ~ " > " ~ | 
|  | escapeShellFileName(outputFn)); | 
|  | assert(result.status == 0); | 
|  | auto args2 = outputFn.readText().strip().parseCommandLine()[1..$]; | 
|  | assert(args == args2, text(args2)); | 
|  | } | 
|  |  | 
|  |  | 
|  | /** | 
|  | Options that control the behaviour of process creation functions in this | 
|  | module. Most options only apply to $(LREF spawnProcess) and | 
|  | $(LREF spawnShell). | 
|  |  | 
|  | Example: | 
|  | --- | 
|  | auto logFile = File("myapp_error.log", "w"); | 
|  |  | 
|  | // Start program, suppressing the console window (Windows only), | 
|  | // redirect its error stream to logFile, and leave logFile open | 
|  | // in the parent process as well. | 
|  | auto pid = spawnProcess("myapp", stdin, stdout, logFile, | 
|  | Config.retainStderr | Config.suppressConsole); | 
|  | scope(exit) | 
|  | { | 
|  | auto exitCode = wait(pid); | 
|  | logFile.writeln("myapp exited with code ", exitCode); | 
|  | logFile.close(); | 
|  | } | 
|  | --- | 
|  | */ | 
|  | struct Config | 
|  | { | 
|  | /** | 
|  | Flag options. | 
|  | Use bitwise OR to combine flags. | 
|  | **/ | 
|  | enum Flags | 
|  | { | 
|  | none = 0, | 
|  |  | 
|  | /** | 
|  | By default, the child process inherits the parent's environment, | 
|  | and any environment variables passed to $(LREF spawnProcess) will | 
|  | be added to it.  If this flag is set, the only variables in the | 
|  | child process' environment will be those given to spawnProcess. | 
|  | */ | 
|  | newEnv = 1, | 
|  |  | 
|  | /** | 
|  | Unless the child process inherits the standard input/output/error | 
|  | streams of its parent, one almost always wants the streams closed | 
|  | in the parent when $(LREF spawnProcess) returns.  Therefore, by | 
|  | default, this is done.  If this is not desirable, pass any of these | 
|  | options to spawnProcess. | 
|  | */ | 
|  | retainStdin  = 2, | 
|  | retainStdout = 4,                                  /// ditto | 
|  | retainStderr = 8,                                  /// ditto | 
|  |  | 
|  | /** | 
|  | On Windows, if the child process is a console application, this | 
|  | flag will prevent the creation of a console window.  Otherwise, | 
|  | it will be ignored. On POSIX, `suppressConsole` has no effect. | 
|  | */ | 
|  | suppressConsole = 16, | 
|  |  | 
|  | /** | 
|  | On POSIX, open $(LINK2 http://en.wikipedia.org/wiki/File_descriptor,file descriptors) | 
|  | are by default inherited by the child process.  As this may lead | 
|  | to subtle bugs when pipes or multiple threads are involved, | 
|  | $(LREF spawnProcess) ensures that all file descriptors except the | 
|  | ones that correspond to standard input/output/error are closed | 
|  | in the child process when it starts.  Use `inheritFDs` to prevent | 
|  | this. | 
|  |  | 
|  | On Windows, this option has no effect, and any handles which have been | 
|  | explicitly marked as inheritable will always be inherited by the child | 
|  | process. | 
|  | */ | 
|  | inheritFDs = 32, | 
|  |  | 
|  | /** | 
|  | Spawn process in detached state. This removes the need in calling | 
|  | $(LREF wait) to clean up the process resources. | 
|  |  | 
|  | Note: | 
|  | Calling $(LREF wait) or $(LREF kill) with the resulting `Pid` is invalid. | 
|  | */ | 
|  | detached = 64, | 
|  |  | 
|  | /** | 
|  | By default, the $(LREF execute) and $(LREF executeShell) functions | 
|  | will capture child processes' both stdout and stderr. This can be | 
|  | undesirable if the standard output is to be processed or otherwise | 
|  | used by the invoking program, as `execute`'s result would then | 
|  | contain a mix of output and warning/error messages. | 
|  |  | 
|  | Specify this flag when calling `execute` or `executeShell` to | 
|  | cause invoked processes' stderr stream to be sent to $(REF stderr, | 
|  | std,stdio), and only capture and return standard output. | 
|  |  | 
|  | This flag has no effect on $(LREF spawnProcess) or $(LREF spawnShell). | 
|  | */ | 
|  | stderrPassThrough = 128, | 
|  | } | 
|  | Flags flags; /// ditto | 
|  |  | 
|  | /** | 
|  | For backwards compatibility, and cases when only flags need to | 
|  | be specified in the `Config`, these allow building `Config` | 
|  | instances using flag names only. | 
|  | */ | 
|  | enum Config none = Config.init; | 
|  | enum Config newEnv = Config(Flags.newEnv); /// ditto | 
|  | enum Config retainStdin = Config(Flags.retainStdin); /// ditto | 
|  | enum Config retainStdout = Config(Flags.retainStdout); /// ditto | 
|  | enum Config retainStderr = Config(Flags.retainStderr); /// ditto | 
|  | enum Config suppressConsole = Config(Flags.suppressConsole); /// ditto | 
|  | enum Config inheritFDs = Config(Flags.inheritFDs); /// ditto | 
|  | enum Config detached = Config(Flags.detached); /// ditto | 
|  | enum Config stderrPassThrough = Config(Flags.stderrPassThrough); /// ditto | 
|  | Config opUnary(string op)() | 
|  | if (is(typeof(mixin(op ~ q{flags})))) | 
|  | { | 
|  | return Config(mixin(op ~ q{flags})); | 
|  | } /// ditto | 
|  | Config opBinary(string op)(Config other) | 
|  | if (is(typeof(mixin(q{flags} ~ op ~ q{other.flags})))) | 
|  | { | 
|  | return Config(mixin(q{flags} ~ op ~ q{other.flags})); | 
|  | } /// ditto | 
|  | Config opOpAssign(string op)(Config other) | 
|  | if (is(typeof(mixin(q{flags} ~ op ~ q{=other.flags})))) | 
|  | { | 
|  | return Config(mixin(q{flags} ~ op ~ q{=other.flags})); | 
|  | } /// ditto | 
|  |  | 
|  | version (StdDdoc) | 
|  | { | 
|  | /** | 
|  | A function that is called before `exec` in $(LREF spawnProcess). | 
|  | It returns `true` if succeeded and otherwise returns `false`. | 
|  |  | 
|  | $(RED Warning: | 
|  | Please note that the code in this function must only use | 
|  | async-signal-safe functions.) | 
|  |  | 
|  | If $(LREF preExecDelegate) is also set, it is called last. | 
|  |  | 
|  | On Windows, this member is not available. | 
|  | */ | 
|  | bool function() nothrow @nogc @safe preExecFunction; | 
|  |  | 
|  | /** | 
|  | A delegate that is called before `exec` in $(LREF spawnProcess). | 
|  | It returns `true` if succeeded and otherwise returns `false`. | 
|  |  | 
|  | $(RED Warning: | 
|  | Please note that the code in this function must only use | 
|  | async-signal-safe functions.) | 
|  |  | 
|  | If $(LREF preExecFunction) is also set, it is called first. | 
|  |  | 
|  | On Windows, this member is not available. | 
|  | */ | 
|  | bool delegate() nothrow @nogc @safe preExecDelegate; | 
|  | } | 
|  | else version (Posix) | 
|  | { | 
|  | bool function() nothrow @nogc @safe preExecFunction; | 
|  | bool delegate() nothrow @nogc @safe preExecDelegate; | 
|  | } | 
|  | } | 
|  |  | 
|  | // https://issues.dlang.org/show_bug.cgi?id=22125 | 
|  | @safe unittest | 
|  | { | 
|  | Config c = Config.retainStdin; | 
|  | c |= Config.retainStdout; | 
|  | c |= Config.retainStderr; | 
|  | c &= ~Config.retainStderr; | 
|  | assert(c == (Config.retainStdin | Config.retainStdout)); | 
|  | } | 
|  |  | 
|  | /// A handle that corresponds to a spawned process. | 
|  | final class Pid | 
|  | { | 
|  | /** | 
|  | The process ID number. | 
|  |  | 
|  | This is a number that uniquely identifies the process on the operating | 
|  | system, for at least as long as the process is running.  Once $(LREF wait) | 
|  | has been called on the $(LREF Pid), this method will return an | 
|  | invalid (negative) process ID. | 
|  | */ | 
|  | @property int processID() const @safe pure nothrow | 
|  | { | 
|  | return _processID; | 
|  | } | 
|  |  | 
|  | /** | 
|  | An operating system handle to the process. | 
|  |  | 
|  | This handle is used to specify the process in OS-specific APIs. | 
|  | On POSIX, this function returns a `core.sys.posix.sys.types.pid_t` | 
|  | with the same value as $(LREF Pid.processID), while on Windows it returns | 
|  | a `core.sys.windows.windows.HANDLE`. | 
|  |  | 
|  | Once $(LREF wait) has been called on the $(LREF Pid), this method | 
|  | will return an invalid handle. | 
|  | */ | 
|  | // Note: Since HANDLE is a reference, this function cannot be const. | 
|  | version (Windows) | 
|  | @property HANDLE osHandle() @nogc @safe pure nothrow | 
|  | { | 
|  | return _handle; | 
|  | } | 
|  | else version (Posix) | 
|  | @property pid_t osHandle() @nogc @safe pure nothrow | 
|  | { | 
|  | return _processID; | 
|  | } | 
|  |  | 
|  | private: | 
|  | /* | 
|  | Pid.performWait() does the dirty work for wait() and nonBlockingWait(). | 
|  |  | 
|  | If block == true, this function blocks until the process terminates, | 
|  | sets _processID to terminated, and returns the exit code or terminating | 
|  | signal as described in the wait() documentation. | 
|  |  | 
|  | If block == false, this function returns immediately, regardless | 
|  | of the status of the process.  If the process has terminated, the | 
|  | function has the exact same effect as the blocking version.  If not, | 
|  | it returns 0 and does not modify _processID. | 
|  | */ | 
|  | version (Posix) | 
|  | int performWait(bool block) @trusted | 
|  | { | 
|  | import std.exception : enforce; | 
|  | enforce!ProcessException(owned, "Can't wait on a detached process"); | 
|  | if (_processID == terminated) return _exitCode; | 
|  | int exitCode; | 
|  | while (true) | 
|  | { | 
|  | int status; | 
|  | auto check = waitpid(_processID, &status, block ? 0 : WNOHANG); | 
|  | if (check == -1) | 
|  | { | 
|  | if (errno == ECHILD) | 
|  | { | 
|  | throw new ProcessException( | 
|  | "Process does not exist or is not a child process."); | 
|  | } | 
|  | else | 
|  | { | 
|  | // waitpid() was interrupted by a signal.  We simply | 
|  | // restart it. | 
|  | assert(errno == EINTR); | 
|  | continue; | 
|  | } | 
|  | } | 
|  | if (!block && check == 0) return 0; | 
|  | if (WIFEXITED(status)) | 
|  | { | 
|  | exitCode = WEXITSTATUS(status); | 
|  | break; | 
|  | } | 
|  | else if (WIFSIGNALED(status)) | 
|  | { | 
|  | exitCode = -WTERMSIG(status); | 
|  | break; | 
|  | } | 
|  | // We check again whether the call should be blocking, | 
|  | // since we don't care about other status changes besides | 
|  | // "exited" and "terminated by signal". | 
|  | if (!block) return 0; | 
|  |  | 
|  | // Process has stopped, but not terminated, so we continue waiting. | 
|  | } | 
|  | // Mark Pid as terminated, and cache and return exit code. | 
|  | _processID = terminated; | 
|  | _exitCode = exitCode; | 
|  | return exitCode; | 
|  | } | 
|  | else version (Windows) | 
|  | { | 
|  | int performWait(const bool block, const DWORD timeout = INFINITE) @trusted | 
|  | { | 
|  | import std.exception : enforce; | 
|  | enforce!ProcessException(owned, "Can't wait on a detached process"); | 
|  | if (_processID == terminated) return _exitCode; | 
|  | assert(_handle != INVALID_HANDLE_VALUE); | 
|  | if (block) | 
|  | { | 
|  | auto result = WaitForSingleObject(_handle, timeout); | 
|  | if (result != WAIT_OBJECT_0) | 
|  | { | 
|  | // Wait time exceeded `timeout` milliseconds? | 
|  | if (result == WAIT_TIMEOUT && timeout != INFINITE) | 
|  | return 0; | 
|  |  | 
|  | throw ProcessException.newFromLastError("Wait failed."); | 
|  | } | 
|  | } | 
|  | if (!GetExitCodeProcess(_handle, cast(LPDWORD)&_exitCode)) | 
|  | throw ProcessException.newFromLastError(); | 
|  | if (!block && _exitCode == STILL_ACTIVE) return 0; | 
|  | CloseHandle(_handle); | 
|  | _handle = INVALID_HANDLE_VALUE; | 
|  | _processID = terminated; | 
|  | return _exitCode; | 
|  | } | 
|  |  | 
|  | int performWait(Duration timeout) @safe | 
|  | { | 
|  | import std.exception : enforce; | 
|  | const msecs = timeout.total!"msecs"; | 
|  |  | 
|  | // Limit this implementation the maximum wait time offered by | 
|  | // WaitForSingleObject. One could theoretically break up larger | 
|  | // durations into multiple waits but (DWORD.max - 1).msecs | 
|  | // (> 7 weeks, 17 hours) should be enough for the usual case. | 
|  | // DWORD.max is reserved for INFINITE | 
|  | enforce!ProcessException(msecs < DWORD.max, "Timeout exceeds maximum wait time!"); | 
|  | return performWait(true, cast(DWORD) msecs); | 
|  | } | 
|  |  | 
|  | ~this() | 
|  | { | 
|  | if (_handle != INVALID_HANDLE_VALUE) | 
|  | { | 
|  | CloseHandle(_handle); | 
|  | _handle = INVALID_HANDLE_VALUE; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // Special values for _processID. | 
|  | enum invalid = -1, terminated = -2; | 
|  |  | 
|  | // OS process ID number.  Only nonnegative IDs correspond to | 
|  | // running processes. | 
|  | int _processID = invalid; | 
|  |  | 
|  | // Exit code cached by wait().  This is only expected to hold a | 
|  | // sensible value if _processID == terminated. | 
|  | int _exitCode; | 
|  |  | 
|  | // Whether the process can be waited for by wait() for or killed by kill(). | 
|  | // False if process was started as detached. True otherwise. | 
|  | bool owned; | 
|  |  | 
|  | // Pids are only meant to be constructed inside this module, so | 
|  | // we make the constructor private. | 
|  | version (Windows) | 
|  | { | 
|  | HANDLE _handle = INVALID_HANDLE_VALUE; | 
|  | this(int pid, HANDLE handle) @safe pure nothrow | 
|  | { | 
|  | _processID = pid; | 
|  | _handle = handle; | 
|  | this.owned = true; | 
|  | } | 
|  | this(int pid) @safe pure nothrow | 
|  | { | 
|  | _processID = pid; | 
|  | this.owned = false; | 
|  | } | 
|  | } | 
|  | else | 
|  | { | 
|  | this(int id, bool owned) @safe pure nothrow | 
|  | { | 
|  | _processID = id; | 
|  | this.owned = owned; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | /** | 
|  | Waits for the process associated with `pid` to terminate, and returns | 
|  | its exit status. | 
|  |  | 
|  | In general one should always _wait for child processes to terminate | 
|  | before exiting the parent process unless the process was spawned as detached | 
|  | (that was spawned with `Config.detached` flag). | 
|  | Otherwise, they may become "$(HTTP en.wikipedia.org/wiki/Zombie_process,zombies)" | 
|  | – processes that are defunct, yet still occupy a slot in the OS process table. | 
|  | You should not and must not wait for detached processes, since you don't own them. | 
|  |  | 
|  | If the process has already terminated, this function returns directly. | 
|  | The exit code is cached, so that if wait() is called multiple times on | 
|  | the same $(LREF Pid) it will always return the same value. | 
|  |  | 
|  | POSIX_specific: | 
|  | If the process is terminated by a signal, this function returns a | 
|  | negative number whose absolute value is the signal number. | 
|  | Since POSIX restricts normal exit codes to the range 0-255, a | 
|  | negative return value will always indicate termination by signal. | 
|  | Signal codes are defined in the `core.sys.posix.signal` module | 
|  | (which corresponds to the `signal.h` POSIX header). | 
|  |  | 
|  | Throws: | 
|  | $(LREF ProcessException) on failure or on attempt to wait for detached process. | 
|  |  | 
|  | Example: | 
|  | See the $(LREF spawnProcess) documentation. | 
|  |  | 
|  | See_also: | 
|  | $(LREF tryWait), for a non-blocking function. | 
|  | */ | 
|  | int wait(Pid pid) @safe | 
|  | { | 
|  | assert(pid !is null, "Called wait on a null Pid."); | 
|  | return pid.performWait(true); | 
|  | } | 
|  |  | 
|  |  | 
|  | @system unittest // Pid and wait() | 
|  | { | 
|  | version (Windows)    TestScript prog = "exit %~1"; | 
|  | else version (Posix) TestScript prog = "exit $1"; | 
|  | assert(wait(spawnProcess([prog.path, "0"])) == 0); | 
|  | assert(wait(spawnProcess([prog.path, "123"])) == 123); | 
|  | auto pid = spawnProcess([prog.path, "10"]); | 
|  | assert(pid.processID > 0); | 
|  | version (Windows)    assert(pid.osHandle != INVALID_HANDLE_VALUE); | 
|  | else version (Posix) assert(pid.osHandle == pid.processID); | 
|  | assert(wait(pid) == 10); | 
|  | assert(wait(pid) == 10); // cached exit code | 
|  | assert(pid.processID < 0); | 
|  | version (Windows)    assert(pid.osHandle == INVALID_HANDLE_VALUE); | 
|  | else version (Posix) assert(pid.osHandle < 0); | 
|  | } | 
|  |  | 
|  | private import std.typecons : Tuple; | 
|  |  | 
|  | /** | 
|  | Waits until either the process associated with `pid` terminates or the | 
|  | elapsed time exceeds the given timeout. | 
|  |  | 
|  | If the process terminates within the given duration it behaves exactly like | 
|  | `wait`, except that it returns a tuple `(true, exit code)`. | 
|  |  | 
|  | If the process does not terminate within the given duration it will stop | 
|  | waiting and return `(false, 0).` | 
|  |  | 
|  | The timeout may not exceed `(uint.max - 1).msecs` (~ 7 weeks, 17 hours). | 
|  |  | 
|  | $(BLUE This function is Windows-Only.) | 
|  |  | 
|  | Returns: | 
|  | An $(D std.typecons.Tuple!(bool, "terminated", int, "status")). | 
|  |  | 
|  | Throws: | 
|  | $(LREF ProcessException) on failure or on attempt to wait for detached process. | 
|  |  | 
|  | Example: | 
|  | See the $(LREF spawnProcess) documentation. | 
|  |  | 
|  | See_also: | 
|  | $(LREF wait), for a blocking function without timeout. | 
|  | $(LREF tryWait), for a non-blocking function without timeout. | 
|  | */ | 
|  | version (StdDdoc) | 
|  | Tuple!(bool, "terminated", int, "status") waitTimeout(Pid pid, Duration timeout) @safe; | 
|  |  | 
|  | else version (Windows) | 
|  | Tuple!(bool, "terminated", int, "status") waitTimeout(Pid pid, Duration timeout) @safe | 
|  | { | 
|  | assert(pid !is null, "Called wait on a null Pid."); | 
|  | auto code = pid.performWait(timeout); | 
|  | return typeof(return)(pid._processID == Pid.terminated, code); | 
|  | } | 
|  |  | 
|  | version (Windows) | 
|  | @system unittest // Pid and waitTimeout() | 
|  | { | 
|  | import std.exception : collectException; | 
|  | import std.typecons : tuple; | 
|  |  | 
|  | TestScript prog = ":Loop\r\n" ~ "goto Loop"; | 
|  | auto pid = spawnProcess(prog.path); | 
|  |  | 
|  | // Doesn't block longer than one second | 
|  | assert(waitTimeout(pid, 1.seconds) == tuple(false, 0)); | 
|  |  | 
|  | kill(pid); | 
|  | assert(waitTimeout(pid, 1.seconds) == tuple(true, 1)); // exit 1 because the process is killed | 
|  |  | 
|  | // Rejects timeouts exceeding the Windows API capabilities | 
|  | const dur = DWORD.max.msecs; | 
|  | const ex = collectException!ProcessException(waitTimeout(pid, dur)); | 
|  | assert(ex); | 
|  | assert(ex.msg == "Timeout exceeds maximum wait time!"); | 
|  | } | 
|  |  | 
|  | /** | 
|  | A non-blocking version of $(LREF wait). | 
|  |  | 
|  | If the process associated with `pid` has already terminated, | 
|  | `tryWait` has the exact same effect as `wait`. | 
|  | In this case, it returns a tuple where the `terminated` field | 
|  | is set to `true` and the `status` field has the same | 
|  | interpretation as the return value of `wait`. | 
|  |  | 
|  | If the process has $(I not) yet terminated, this function differs | 
|  | from `wait` in that does not wait for this to happen, but instead | 
|  | returns immediately.  The `terminated` field of the returned | 
|  | tuple will then be set to `false`, while the `status` field | 
|  | will always be 0 (zero).  `wait` or `tryWait` should then be | 
|  | called again on the same `Pid` at some later time; not only to | 
|  | get the exit code, but also to avoid the process becoming a "zombie" | 
|  | when it finally terminates.  (See $(LREF wait) for details). | 
|  |  | 
|  | Returns: | 
|  | An $(D std.typecons.Tuple!(bool, "terminated", int, "status")). | 
|  |  | 
|  | Throws: | 
|  | $(LREF ProcessException) on failure or on attempt to wait for detached process. | 
|  |  | 
|  | Example: | 
|  | --- | 
|  | auto pid = spawnProcess("dmd myapp.d"); | 
|  | scope(exit) wait(pid); | 
|  | ... | 
|  | auto dmd = tryWait(pid); | 
|  | if (dmd.terminated) | 
|  | { | 
|  | if (dmd.status == 0) writeln("Compilation succeeded!"); | 
|  | else writeln("Compilation failed"); | 
|  | } | 
|  | else writeln("Still compiling..."); | 
|  | ... | 
|  | --- | 
|  | Note that in this example, the first `wait` call will have no | 
|  | effect if the process has already terminated by the time `tryWait` | 
|  | is called.  In the opposite case, however, the `scope` statement | 
|  | ensures that we always wait for the process if it hasn't terminated | 
|  | by the time we reach the end of the scope. | 
|  | */ | 
|  | auto tryWait(Pid pid) @safe | 
|  | { | 
|  | import std.typecons : Tuple; | 
|  | assert(pid !is null, "Called tryWait on a null Pid."); | 
|  | auto code = pid.performWait(false); | 
|  | return Tuple!(bool, "terminated", int, "status")(pid._processID == Pid.terminated, code); | 
|  | } | 
|  | // unittest: This function is tested together with kill() below. | 
|  |  | 
|  |  | 
|  | /** | 
|  | Attempts to terminate the process associated with `pid`. | 
|  |  | 
|  | The effect of this function, as well as the meaning of `codeOrSignal`, | 
|  | is highly platform dependent.  Details are given below.  Common to all | 
|  | platforms is that this function only $(I initiates) termination of the process, | 
|  | and returns immediately.  It does not wait for the process to end, | 
|  | nor does it guarantee that the process does in fact get terminated. | 
|  |  | 
|  | Always call $(LREF wait) to wait for a process to complete, even if `kill` | 
|  | has been called on it. | 
|  |  | 
|  | Windows_specific: | 
|  | The process will be | 
|  | $(LINK2 http://msdn.microsoft.com/en-us/library/windows/desktop/ms686714%28v=vs.100%29.aspx, | 
|  | forcefully and abruptly terminated).  If `codeOrSignal` is specified, it | 
|  | must be a nonnegative number which will be used as the exit code of the process. | 
|  | If not, the process wil exit with code 1.  Do not use $(D codeOrSignal = 259), | 
|  | as this is a special value (aka. $(LINK2 http://msdn.microsoft.com/en-us/library/windows/desktop/ms683189.aspx,STILL_ACTIVE)) | 
|  | used by Windows to signal that a process has in fact $(I not) terminated yet. | 
|  | --- | 
|  | auto pid = spawnProcess("some_app"); | 
|  | kill(pid, 10); | 
|  | assert(wait(pid) == 10); | 
|  | --- | 
|  |  | 
|  | POSIX_specific: | 
|  | A $(LINK2 http://en.wikipedia.org/wiki/Unix_signal,signal) will be sent to | 
|  | the process, whose value is given by `codeOrSignal`.  Depending on the | 
|  | signal sent, this may or may not terminate the process.  Symbolic constants | 
|  | for various $(LINK2 http://en.wikipedia.org/wiki/Unix_signal#POSIX_signals, | 
|  | POSIX signals) are defined in `core.sys.posix.signal`, which corresponds to the | 
|  | $(LINK2 http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/signal.h.html, | 
|  | `signal.h` POSIX header).  If `codeOrSignal` is omitted, the | 
|  | `SIGTERM` signal will be sent.  (This matches the behaviour of the | 
|  | $(LINK2 http://pubs.opengroup.org/onlinepubs/9699919799/utilities/kill.html, | 
|  | `_kill`) shell command.) | 
|  | --- | 
|  | import core.sys.posix.signal : SIGKILL; | 
|  | auto pid = spawnProcess("some_app"); | 
|  | kill(pid, SIGKILL); | 
|  | assert(wait(pid) == -SIGKILL); // Negative return value on POSIX! | 
|  | --- | 
|  |  | 
|  | Throws: | 
|  | $(LREF ProcessException) on error (e.g. if codeOrSignal is invalid). | 
|  | or on attempt to kill detached process. | 
|  | Note that failure to terminate the process is considered a "normal" | 
|  | outcome, not an error.$(BR) | 
|  | */ | 
|  | void kill(Pid pid) | 
|  | { | 
|  | version (Windows) kill(pid, 1); | 
|  | else version (Posix) | 
|  | { | 
|  | import core.sys.posix.signal : SIGTERM; | 
|  | kill(pid, SIGTERM); | 
|  | } | 
|  | } | 
|  |  | 
|  | /// ditto | 
|  | void kill(Pid pid, int codeOrSignal) | 
|  | { | 
|  | import std.exception : enforce; | 
|  | enforce!ProcessException(pid.owned, "Can't kill detached process"); | 
|  | version (Windows) | 
|  | { | 
|  | if (codeOrSignal < 0) throw new ProcessException("Invalid exit code"); | 
|  | // On Windows, TerminateProcess() appears to terminate the | 
|  | // *current* process if it is passed an invalid handle... | 
|  | if (pid.osHandle == INVALID_HANDLE_VALUE) | 
|  | throw new ProcessException("Invalid process handle"); | 
|  | if (!TerminateProcess(pid.osHandle, codeOrSignal)) | 
|  | throw ProcessException.newFromLastError(); | 
|  | } | 
|  | else version (Posix) | 
|  | { | 
|  | import core.sys.posix.signal : kill; | 
|  | if (pid.osHandle == Pid.invalid) | 
|  | throw new ProcessException("Pid is invalid"); | 
|  | if (pid.osHandle == Pid.terminated) | 
|  | throw new ProcessException("Pid is already terminated"); | 
|  | if (kill(pid.osHandle, codeOrSignal) == -1) | 
|  | throw ProcessException.newFromErrno(); | 
|  | } | 
|  | } | 
|  |  | 
|  | @system unittest // tryWait() and kill() | 
|  | { | 
|  | import core.thread; | 
|  | import std.exception : assertThrown; | 
|  | // The test script goes into an infinite loop. | 
|  | version (Windows) | 
|  | { | 
|  | TestScript prog = ":loop | 
|  | goto loop"; | 
|  | } | 
|  | else version (Posix) | 
|  | { | 
|  | import core.sys.posix.signal : SIGTERM, SIGKILL; | 
|  | TestScript prog = "while true; do sleep 1; done"; | 
|  | } | 
|  | auto pid = spawnProcess(prog.path); | 
|  | // Android appears to automatically kill sleeping processes very quickly, | 
|  | // so shorten the wait before killing here. | 
|  | version (Android) | 
|  | Thread.sleep(dur!"msecs"(5)); | 
|  | else | 
|  | Thread.sleep(dur!"msecs"(500)); | 
|  | kill(pid); | 
|  | version (Windows)    assert(wait(pid) == 1); | 
|  | else version (Posix) assert(wait(pid) == -SIGTERM); | 
|  |  | 
|  | pid = spawnProcess(prog.path); | 
|  | Thread.sleep(dur!"msecs"(500)); | 
|  | auto s = tryWait(pid); | 
|  | assert(!s.terminated && s.status == 0); | 
|  | assertThrown!ProcessException(kill(pid, -123)); // Negative code not allowed. | 
|  | version (Windows)    kill(pid, 123); | 
|  | else version (Posix) kill(pid, SIGKILL); | 
|  | do { s = tryWait(pid); } while (!s.terminated); | 
|  | version (Windows)    assert(s.status == 123); | 
|  | else version (Posix) assert(s.status == -SIGKILL); | 
|  | assertThrown!ProcessException(kill(pid)); // Already terminated | 
|  | } | 
|  |  | 
|  | @system unittest // wait() and kill() detached process | 
|  | { | 
|  | import core.thread; | 
|  | import std.exception : assertThrown; | 
|  | TestScript prog = "exit 0"; | 
|  | auto pid = spawnProcess([prog.path], null, Config.detached); | 
|  | /* | 
|  | This sleep is needed because we can't wait() for detached process to end | 
|  | and therefore TestScript destructor may run at the same time as /bin/sh tries to start the script. | 
|  | This leads to the annoying message like "/bin/sh: 0: Can't open /tmp/std.process temporary file" to appear when running tests. | 
|  | It does not happen in unittests with non-detached processes because we always wait() for them to finish. | 
|  | */ | 
|  | Thread.sleep(500.msecs); | 
|  | assert(!pid.owned); | 
|  | version (Windows) assert(pid.osHandle == INVALID_HANDLE_VALUE); | 
|  | assertThrown!ProcessException(wait(pid)); | 
|  | assertThrown!ProcessException(kill(pid)); | 
|  | } | 
|  |  | 
|  |  | 
|  | /** | 
|  | Creates a unidirectional _pipe. | 
|  |  | 
|  | Data is written to one end of the _pipe and read from the other. | 
|  | --- | 
|  | auto p = pipe(); | 
|  | p.writeEnd.writeln("Hello World"); | 
|  | p.writeEnd.flush(); | 
|  | assert(p.readEnd.readln().chomp() == "Hello World"); | 
|  | --- | 
|  | Pipes can, for example, be used for interprocess communication | 
|  | by spawning a new process and passing one end of the _pipe to | 
|  | the child, while the parent uses the other end. | 
|  | (See also $(LREF pipeProcess) and $(LREF pipeShell) for an easier | 
|  | way of doing this.) | 
|  | --- | 
|  | // Use cURL to download the dlang.org front page, pipe its | 
|  | // output to grep to extract a list of links to ZIP files, | 
|  | // and write the list to the file "D downloads.txt": | 
|  | auto p = pipe(); | 
|  | auto outFile = File("D downloads.txt", "w"); | 
|  | auto cpid = spawnProcess(["curl", "http://dlang.org/download.html"], | 
|  | std.stdio.stdin, p.writeEnd); | 
|  | scope(exit) wait(cpid); | 
|  | auto gpid = spawnProcess(["grep", "-o", `http://\S*\.zip`], | 
|  | p.readEnd, outFile); | 
|  | scope(exit) wait(gpid); | 
|  | --- | 
|  |  | 
|  | Returns: | 
|  | A $(LREF Pipe) object that corresponds to the created _pipe. | 
|  |  | 
|  | Throws: | 
|  | $(REF StdioException, std,stdio) on failure. | 
|  | */ | 
|  | version (Posix) | 
|  | Pipe pipe() @trusted //TODO: @safe | 
|  | { | 
|  | import core.sys.posix.stdio : fdopen; | 
|  | int[2] fds; | 
|  | if (core.sys.posix.unistd.pipe(fds) != 0) | 
|  | throw new StdioException("Unable to create pipe"); | 
|  | Pipe p; | 
|  | auto readFP = fdopen(fds[0], "r"); | 
|  | if (readFP == null) | 
|  | throw new StdioException("Cannot open read end of pipe"); | 
|  | p._read = File(readFP, null); | 
|  | auto writeFP = fdopen(fds[1], "w"); | 
|  | if (writeFP == null) | 
|  | throw new StdioException("Cannot open write end of pipe"); | 
|  | p._write = File(writeFP, null); | 
|  | return p; | 
|  | } | 
|  | else version (Windows) | 
|  | Pipe pipe() @trusted //TODO: @safe | 
|  | { | 
|  | // use CreatePipe to create an anonymous pipe | 
|  | HANDLE readHandle; | 
|  | HANDLE writeHandle; | 
|  | if (!CreatePipe(&readHandle, &writeHandle, null, 0)) | 
|  | { | 
|  | throw new StdioException( | 
|  | "Error creating pipe (" ~ generateSysErrorMsg() ~ ')', | 
|  | 0); | 
|  | } | 
|  |  | 
|  | scope(failure) | 
|  | { | 
|  | CloseHandle(readHandle); | 
|  | CloseHandle(writeHandle); | 
|  | } | 
|  |  | 
|  | try | 
|  | { | 
|  | Pipe p; | 
|  | p._read .windowsHandleOpen(readHandle , "r"); | 
|  | p._write.windowsHandleOpen(writeHandle, "a"); | 
|  | return p; | 
|  | } | 
|  | catch (Exception e) | 
|  | { | 
|  | throw new StdioException("Error attaching pipe (" ~ e.msg ~ ")", | 
|  | 0); | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | /// An interface to a pipe created by the $(LREF pipe) function. | 
|  | struct Pipe | 
|  | { | 
|  | /// The read end of the pipe. | 
|  | @property File readEnd() @safe nothrow { return _read; } | 
|  |  | 
|  |  | 
|  | /// The write end of the pipe. | 
|  | @property File writeEnd() @safe nothrow { return _write; } | 
|  |  | 
|  |  | 
|  | /** | 
|  | Closes both ends of the pipe. | 
|  |  | 
|  | Normally it is not necessary to do this manually, as $(REF File, std,stdio) | 
|  | objects are automatically closed when there are no more references | 
|  | to them. | 
|  |  | 
|  | Note that if either end of the pipe has been passed to a child process, | 
|  | it will only be closed in the parent process.  (What happens in the | 
|  | child process is platform dependent.) | 
|  |  | 
|  | Throws: | 
|  | $(REF ErrnoException, std,exception) if an error occurs. | 
|  | */ | 
|  | void close() @safe | 
|  | { | 
|  | _read.close(); | 
|  | _write.close(); | 
|  | } | 
|  |  | 
|  | private: | 
|  | File _read, _write; | 
|  | } | 
|  |  | 
|  | @system unittest | 
|  | { | 
|  | import std.string; | 
|  | auto p = pipe(); | 
|  | p.writeEnd.writeln("Hello World"); | 
|  | p.writeEnd.flush(); | 
|  | assert(p.readEnd.readln().chomp() == "Hello World"); | 
|  | p.close(); | 
|  | assert(!p.readEnd.isOpen); | 
|  | assert(!p.writeEnd.isOpen); | 
|  | } | 
|  |  | 
|  |  | 
|  | /** | 
|  | Starts a new process, creating pipes to redirect its standard | 
|  | input, output and/or error streams. | 
|  |  | 
|  | `pipeProcess` and `pipeShell` are convenient wrappers around | 
|  | $(LREF spawnProcess) and $(LREF spawnShell), respectively, and | 
|  | automate the task of redirecting one or more of the child process' | 
|  | standard streams through pipes.  Like the functions they wrap, | 
|  | these functions return immediately, leaving the child process to | 
|  | execute in parallel with the invoking process.  It is recommended | 
|  | to always call $(LREF wait) on the returned $(LREF ProcessPipes.pid), | 
|  | as detailed in the documentation for `wait`. | 
|  |  | 
|  | The `args`/`program`/`command`, `env` and `config` | 
|  | parameters are forwarded straight to the underlying spawn functions, | 
|  | and we refer to their documentation for details. | 
|  |  | 
|  | Params: | 
|  | args      = An array which contains the program name as the zeroth element | 
|  | and any command-line arguments in the following elements. | 
|  | (See $(LREF spawnProcess) for details.) | 
|  | program   = The program name, $(I without) command-line arguments. | 
|  | (See $(LREF spawnProcess) for details.) | 
|  | command   = A shell command which is passed verbatim to the command | 
|  | interpreter.  (See $(LREF spawnShell) for details.) | 
|  | redirect  = Flags that determine which streams are redirected, and | 
|  | how.  See $(LREF Redirect) for an overview of available | 
|  | flags. | 
|  | env       = Additional environment variables for the child process. | 
|  | (See $(LREF spawnProcess) for details.) | 
|  | config    = Flags that control process creation. See $(LREF Config) | 
|  | for an overview of available flags, and note that the | 
|  | `retainStd...` flags have no effect in this function. | 
|  | workDir   = The working directory for the new process. | 
|  | By default the child process inherits the parent's working | 
|  | directory. | 
|  | shellPath = The path to the shell to use to run the specified program. | 
|  | By default this is $(LREF nativeShell). | 
|  |  | 
|  | Returns: | 
|  | A $(LREF ProcessPipes) object which contains $(REF File, std,stdio) | 
|  | handles that communicate with the redirected streams of the child | 
|  | process, along with a $(LREF Pid) object that corresponds to the | 
|  | spawned process. | 
|  |  | 
|  | Throws: | 
|  | $(LREF ProcessException) on failure to start the process.$(BR) | 
|  | $(REF StdioException, std,stdio) on failure to redirect any of the streams.$(BR) | 
|  |  | 
|  | Example: | 
|  | --- | 
|  | // my_application writes to stdout and might write to stderr | 
|  | auto pipes = pipeProcess("my_application", Redirect.stdout | Redirect.stderr); | 
|  | scope(exit) wait(pipes.pid); | 
|  |  | 
|  | // Store lines of output. | 
|  | string[] output; | 
|  | foreach (line; pipes.stdout.byLine) output ~= line.idup; | 
|  |  | 
|  | // Store lines of errors. | 
|  | string[] errors; | 
|  | foreach (line; pipes.stderr.byLine) errors ~= line.idup; | 
|  |  | 
|  |  | 
|  | // sendmail expects to read from stdin | 
|  | pipes = pipeProcess(["/usr/bin/sendmail", "-t"], Redirect.stdin); | 
|  | pipes.stdin.writeln("To: you"); | 
|  | pipes.stdin.writeln("From: me"); | 
|  | pipes.stdin.writeln("Subject: dlang"); | 
|  | pipes.stdin.writeln(""); | 
|  | pipes.stdin.writeln(message); | 
|  |  | 
|  | // a single period tells sendmail we are finished | 
|  | pipes.stdin.writeln("."); | 
|  |  | 
|  | // but at this point sendmail might not see it, we need to flush | 
|  | pipes.stdin.flush(); | 
|  |  | 
|  | // sendmail happens to exit on ".", but some you have to close the file: | 
|  | pipes.stdin.close(); | 
|  |  | 
|  | // otherwise this wait will wait forever | 
|  | wait(pipes.pid); | 
|  |  | 
|  | --- | 
|  | */ | 
|  | ProcessPipes pipeProcess(scope const(char[])[] args, | 
|  | Redirect redirect = Redirect.all, | 
|  | const string[string] env = null, | 
|  | Config config = Config.none, | 
|  | scope const(char)[] workDir = null) | 
|  | @safe | 
|  | { | 
|  | return pipeProcessImpl!spawnProcess(args, redirect, env, config, workDir); | 
|  | } | 
|  |  | 
|  | /// ditto | 
|  | ProcessPipes pipeProcess(scope const(char)[] program, | 
|  | Redirect redirect = Redirect.all, | 
|  | const string[string] env = null, | 
|  | Config config = Config.none, | 
|  | scope const(char)[] workDir = null) | 
|  | @safe | 
|  | { | 
|  | return pipeProcessImpl!spawnProcess(program, redirect, env, config, workDir); | 
|  | } | 
|  |  | 
|  | /// ditto | 
|  | ProcessPipes pipeShell(scope const(char)[] command, | 
|  | Redirect redirect = Redirect.all, | 
|  | const string[string] env = null, | 
|  | Config config = Config.none, | 
|  | scope const(char)[] workDir = null, | 
|  | string shellPath = nativeShell) | 
|  | @safe | 
|  | { | 
|  | return pipeProcessImpl!spawnShell(command, | 
|  | redirect, | 
|  | env, | 
|  | config, | 
|  | workDir, | 
|  | shellPath); | 
|  | } | 
|  |  | 
|  | // Implementation of the pipeProcess() family of functions. | 
|  | private ProcessPipes pipeProcessImpl(alias spawnFunc, Cmd, ExtraSpawnFuncArgs...) | 
|  | (scope Cmd command, | 
|  | Redirect redirectFlags, | 
|  | const string[string] env = null, | 
|  | Config config = Config.none, | 
|  | scope const(char)[] workDir = null, | 
|  | ExtraSpawnFuncArgs extraArgs = ExtraSpawnFuncArgs.init) | 
|  | @trusted //TODO: @safe | 
|  | { | 
|  | File childStdin, childStdout, childStderr; | 
|  | ProcessPipes pipes; | 
|  | pipes._redirectFlags = redirectFlags; | 
|  |  | 
|  | if (redirectFlags & Redirect.stdin) | 
|  | { | 
|  | auto p = pipe(); | 
|  | childStdin = p.readEnd; | 
|  | pipes._stdin = p.writeEnd; | 
|  | } | 
|  | else | 
|  | { | 
|  | childStdin = std.stdio.stdin; | 
|  | } | 
|  |  | 
|  | if (redirectFlags & Redirect.stdout) | 
|  | { | 
|  | if ((redirectFlags & Redirect.stdoutToStderr) != 0) | 
|  | throw new StdioException("Cannot create pipe for stdout AND " | 
|  | ~"redirect it to stderr", 0); | 
|  | auto p = pipe(); | 
|  | childStdout = p.writeEnd; | 
|  | pipes._stdout = p.readEnd; | 
|  | } | 
|  | else | 
|  | { | 
|  | childStdout = std.stdio.stdout; | 
|  | } | 
|  |  | 
|  | if (redirectFlags & Redirect.stderr) | 
|  | { | 
|  | if ((redirectFlags & Redirect.stderrToStdout) != 0) | 
|  | throw new StdioException("Cannot create pipe for stderr AND " | 
|  | ~"redirect it to stdout", 0); | 
|  | auto p = pipe(); | 
|  | childStderr = p.writeEnd; | 
|  | pipes._stderr = p.readEnd; | 
|  | } | 
|  | else | 
|  | { | 
|  | childStderr = std.stdio.stderr; | 
|  | } | 
|  |  | 
|  | if (redirectFlags & Redirect.stdoutToStderr) | 
|  | { | 
|  | if (redirectFlags & Redirect.stderrToStdout) | 
|  | { | 
|  | // We know that neither of the other options have been | 
|  | // set, so we assign the std.stdio.std* streams directly. | 
|  | childStdout = std.stdio.stderr; | 
|  | childStderr = std.stdio.stdout; | 
|  | } | 
|  | else | 
|  | { | 
|  | childStdout = childStderr; | 
|  | } | 
|  | } | 
|  | else if (redirectFlags & Redirect.stderrToStdout) | 
|  | { | 
|  | childStderr = childStdout; | 
|  | } | 
|  |  | 
|  | config.flags &= ~(Config.Flags.retainStdin | Config.Flags.retainStdout | Config.Flags.retainStderr); | 
|  | pipes._pid = spawnFunc(command, childStdin, childStdout, childStderr, | 
|  | env, config, workDir, extraArgs); | 
|  | return pipes; | 
|  | } | 
|  |  | 
|  |  | 
|  | /** | 
|  | Flags that can be passed to $(LREF pipeProcess) and $(LREF pipeShell) | 
|  | to specify which of the child process' standard streams are redirected. | 
|  | Use bitwise OR to combine flags. | 
|  | */ | 
|  | enum Redirect | 
|  | { | 
|  | /// Redirect the standard input, output or error streams, respectively. | 
|  | stdin = 1, | 
|  | stdout = 2,                             /// ditto | 
|  | stderr = 4,                             /// ditto | 
|  |  | 
|  | /** | 
|  | Redirect _all three streams.  This is equivalent to | 
|  | $(D Redirect.stdin | Redirect.stdout | Redirect.stderr). | 
|  | */ | 
|  | all = stdin | stdout | stderr, | 
|  |  | 
|  | /** | 
|  | Redirect the standard error stream into the standard output stream. | 
|  | This can not be combined with `Redirect.stderr`. | 
|  | */ | 
|  | stderrToStdout = 8, | 
|  |  | 
|  | /** | 
|  | Redirect the standard output stream into the standard error stream. | 
|  | This can not be combined with `Redirect.stdout`. | 
|  | */ | 
|  | stdoutToStderr = 16, | 
|  | } | 
|  |  | 
|  | @system unittest | 
|  | { | 
|  | import std.string; | 
|  | version (Windows) TestScript prog = | 
|  | "call :sub %~1 %~2 0 | 
|  | call :sub %~1 %~2 1 | 
|  | call :sub %~1 %~2 2 | 
|  | call :sub %~1 %~2 3 | 
|  | exit 3 | 
|  |  | 
|  | :sub | 
|  | set /p INPUT= | 
|  | if -%INPUT%-==-stop- ( exit %~3 ) | 
|  | echo %INPUT% %~1 | 
|  | echo %INPUT% %~2 1>&2"; | 
|  | else version (Posix) TestScript prog = | 
|  | `for EXITCODE in 0 1 2 3; do | 
|  | read INPUT | 
|  | if test "$INPUT" = stop; then break; fi | 
|  | echo "$INPUT $1" | 
|  | echo "$INPUT $2" >&2 | 
|  | done | 
|  | exit $EXITCODE`; | 
|  | auto pp = pipeProcess([prog.path, "bar", "baz"]); | 
|  | pp.stdin.writeln("foo"); | 
|  | pp.stdin.flush(); | 
|  | assert(pp.stdout.readln().chomp() == "foo bar"); | 
|  | assert(pp.stderr.readln().chomp().stripRight() == "foo baz"); | 
|  | pp.stdin.writeln("1234567890"); | 
|  | pp.stdin.flush(); | 
|  | assert(pp.stdout.readln().chomp() == "1234567890 bar"); | 
|  | assert(pp.stderr.readln().chomp().stripRight() == "1234567890 baz"); | 
|  | pp.stdin.writeln("stop"); | 
|  | pp.stdin.flush(); | 
|  | assert(wait(pp.pid) == 2); | 
|  |  | 
|  | pp = pipeProcess([prog.path, "12345", "67890"], | 
|  | Redirect.stdin | Redirect.stdout | Redirect.stderrToStdout); | 
|  | pp.stdin.writeln("xyz"); | 
|  | pp.stdin.flush(); | 
|  | assert(pp.stdout.readln().chomp() == "xyz 12345"); | 
|  | assert(pp.stdout.readln().chomp().stripRight() == "xyz 67890"); | 
|  | pp.stdin.writeln("stop"); | 
|  | pp.stdin.flush(); | 
|  | assert(wait(pp.pid) == 1); | 
|  |  | 
|  | pp = pipeShell(escapeShellCommand(prog.path, "AAAAA", "BBB"), | 
|  | Redirect.stdin | Redirect.stdoutToStderr | Redirect.stderr); | 
|  | pp.stdin.writeln("ab"); | 
|  | pp.stdin.flush(); | 
|  | assert(pp.stderr.readln().chomp() == "ab AAAAA"); | 
|  | assert(pp.stderr.readln().chomp().stripRight() == "ab BBB"); | 
|  | pp.stdin.writeln("stop"); | 
|  | pp.stdin.flush(); | 
|  | assert(wait(pp.pid) == 1); | 
|  | } | 
|  |  | 
|  | @system unittest | 
|  | { | 
|  | import std.exception : assertThrown; | 
|  | TestScript prog = "exit 0"; | 
|  | assertThrown!StdioException(pipeProcess( | 
|  | prog.path, | 
|  | Redirect.stdout | Redirect.stdoutToStderr)); | 
|  | assertThrown!StdioException(pipeProcess( | 
|  | prog.path, | 
|  | Redirect.stderr | Redirect.stderrToStdout)); | 
|  | auto p = pipeProcess(prog.path, Redirect.stdin); | 
|  | assertThrown!Error(p.stdout); | 
|  | assertThrown!Error(p.stderr); | 
|  | wait(p.pid); | 
|  | p = pipeProcess(prog.path, Redirect.stderr); | 
|  | assertThrown!Error(p.stdin); | 
|  | assertThrown!Error(p.stdout); | 
|  | wait(p.pid); | 
|  | } | 
|  |  | 
|  | /** | 
|  | Object which contains $(REF File, std,stdio) handles that allow communication | 
|  | with a child process through its standard streams. | 
|  | */ | 
|  | struct ProcessPipes | 
|  | { | 
|  | /// The $(LREF Pid) of the child process. | 
|  | @property Pid pid() @safe nothrow | 
|  | { | 
|  | return _pid; | 
|  | } | 
|  |  | 
|  | /** | 
|  | An $(REF File, std,stdio) that allows writing to the child process' | 
|  | standard input stream. | 
|  |  | 
|  | Throws: | 
|  | $(OBJECTREF Error) if the child process' standard input stream hasn't | 
|  | been redirected. | 
|  | */ | 
|  | @property File stdin() @safe nothrow | 
|  | { | 
|  | if ((_redirectFlags & Redirect.stdin) == 0) | 
|  | throw new Error("Child process' standard input stream hasn't " | 
|  | ~"been redirected."); | 
|  | return _stdin; | 
|  | } | 
|  |  | 
|  | /** | 
|  | An $(REF File, std,stdio) that allows reading from the child process' | 
|  | standard output stream. | 
|  |  | 
|  | Throws: | 
|  | $(OBJECTREF Error) if the child process' standard output stream hasn't | 
|  | been redirected. | 
|  | */ | 
|  | @property File stdout() @safe nothrow | 
|  | { | 
|  | if ((_redirectFlags & Redirect.stdout) == 0) | 
|  | throw new Error("Child process' standard output stream hasn't " | 
|  | ~"been redirected."); | 
|  | return _stdout; | 
|  | } | 
|  |  | 
|  | /** | 
|  | An $(REF File, std,stdio) that allows reading from the child process' | 
|  | standard error stream. | 
|  |  | 
|  | Throws: | 
|  | $(OBJECTREF Error) if the child process' standard error stream hasn't | 
|  | been redirected. | 
|  | */ | 
|  | @property File stderr() @safe nothrow | 
|  | { | 
|  | if ((_redirectFlags & Redirect.stderr) == 0) | 
|  | throw new Error("Child process' standard error stream hasn't " | 
|  | ~"been redirected."); | 
|  | return _stderr; | 
|  | } | 
|  |  | 
|  | private: | 
|  | Redirect _redirectFlags; | 
|  | Pid _pid; | 
|  | File _stdin, _stdout, _stderr; | 
|  | } | 
|  |  | 
|  |  | 
|  |  | 
|  | /** | 
|  | Executes the given program or shell command and returns its exit | 
|  | code and output. | 
|  |  | 
|  | `execute` and `executeShell` start a new process using | 
|  | $(LREF spawnProcess) and $(LREF spawnShell), respectively, and wait | 
|  | for the process to complete before returning.  The functions capture | 
|  | what the child process prints to both its standard output and | 
|  | standard error streams, and return this together with its exit code. | 
|  | --- | 
|  | auto dmd = execute(["dmd", "myapp.d"]); | 
|  | if (dmd.status != 0) writeln("Compilation failed:\n", dmd.output); | 
|  |  | 
|  | auto ls = executeShell("ls -l"); | 
|  | if (ls.status != 0) writeln("Failed to retrieve file listing"); | 
|  | else writeln(ls.output); | 
|  | --- | 
|  |  | 
|  | The `args`/`program`/`command`, `env` and `config` | 
|  | parameters are forwarded straight to the underlying spawn functions, | 
|  | and we refer to their documentation for details. | 
|  |  | 
|  | Params: | 
|  | args      = An array which contains the program name as the zeroth element | 
|  | and any command-line arguments in the following elements. | 
|  | (See $(LREF spawnProcess) for details.) | 
|  | program   = The program name, $(I without) command-line arguments. | 
|  | (See $(LREF spawnProcess) for details.) | 
|  | command   = A shell command which is passed verbatim to the command | 
|  | interpreter.  (See $(LREF spawnShell) for details.) | 
|  | env       = Additional environment variables for the child process. | 
|  | (See $(LREF spawnProcess) for details.) | 
|  | config    = Flags that control process creation. See $(LREF Config) | 
|  | for an overview of available flags, and note that the | 
|  | `retainStd...` flags have no effect in this function. | 
|  | maxOutput = The maximum number of bytes of output that should be | 
|  | captured. | 
|  | workDir   = The working directory for the new process. | 
|  | By default the child process inherits the parent's working | 
|  | directory. | 
|  | shellPath = The path to the shell to use to run the specified program. | 
|  | By default this is $(LREF nativeShell). | 
|  |  | 
|  |  | 
|  | Returns: | 
|  | An $(D std.typecons.Tuple!(int, "status", string, "output")). | 
|  |  | 
|  | POSIX_specific: | 
|  | If the process is terminated by a signal, the `status` field of | 
|  | the return value will contain a negative number whose absolute | 
|  | value is the signal number.  (See $(LREF wait) for details.) | 
|  |  | 
|  | Throws: | 
|  | $(LREF ProcessException) on failure to start the process.$(BR) | 
|  | $(REF StdioException, std,stdio) on failure to capture output. | 
|  | */ | 
|  | auto execute(scope const(char[])[] args, | 
|  | const string[string] env = null, | 
|  | Config config = Config.none, | 
|  | size_t maxOutput = size_t.max, | 
|  | scope const(char)[] workDir = null) | 
|  | @safe | 
|  | { | 
|  | return executeImpl!pipeProcess(args, env, config, maxOutput, workDir); | 
|  | } | 
|  |  | 
|  | /// ditto | 
|  | auto execute(scope const(char)[] program, | 
|  | const string[string] env = null, | 
|  | Config config = Config.none, | 
|  | size_t maxOutput = size_t.max, | 
|  | scope const(char)[] workDir = null) | 
|  | @safe | 
|  | { | 
|  | return executeImpl!pipeProcess(program, env, config, maxOutput, workDir); | 
|  | } | 
|  |  | 
|  | /// ditto | 
|  | auto executeShell(scope const(char)[] command, | 
|  | const string[string] env = null, | 
|  | Config config = Config.none, | 
|  | size_t maxOutput = size_t.max, | 
|  | scope const(char)[] workDir = null, | 
|  | string shellPath = nativeShell) | 
|  | @safe | 
|  | { | 
|  | return executeImpl!pipeShell(command, | 
|  | env, | 
|  | config, | 
|  | maxOutput, | 
|  | workDir, | 
|  | shellPath); | 
|  | } | 
|  |  | 
|  | // Does the actual work for execute() and executeShell(). | 
|  | private auto executeImpl(alias pipeFunc, Cmd, ExtraPipeFuncArgs...)( | 
|  | Cmd commandLine, | 
|  | const string[string] env = null, | 
|  | Config config = Config.none, | 
|  | 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; | 
|  | import std.typecons : Tuple; | 
|  |  | 
|  | auto redirect = (config.flags & Config.Flags.stderrPassThrough) | 
|  | ? Redirect.stdout | 
|  | : Redirect.stdout | Redirect.stderrToStdout; | 
|  |  | 
|  | auto p = pipeFunc(commandLine, redirect, | 
|  | env, config, workDir, extraArgs); | 
|  |  | 
|  | auto a = appender!string; | 
|  | enum size_t defaultChunkSize = 4096; | 
|  | immutable chunkSize = min(maxOutput, defaultChunkSize); | 
|  |  | 
|  | // Store up to maxOutput bytes in a. | 
|  | foreach (ubyte[] chunk; p.stdout.byChunk(chunkSize)) | 
|  | { | 
|  | immutable size_t remain = maxOutput - a.data.length; | 
|  |  | 
|  | if (chunk.length < remain) a.put(chunk); | 
|  | else | 
|  | { | 
|  | a.put(chunk[0 .. remain]); | 
|  | break; | 
|  | } | 
|  | } | 
|  | // Exhaust the stream, if necessary. | 
|  | foreach (ubyte[] chunk; p.stdout.byChunk(defaultChunkSize)) { } | 
|  |  | 
|  | return Tuple!(int, "status", string, "output")(wait(p.pid), a.data); | 
|  | } | 
|  |  | 
|  | @system unittest | 
|  | { | 
|  | import std.string; | 
|  | // To avoid printing the newline characters, we use the echo|set trick on | 
|  | // Windows, and printf on POSIX (neither echo -n nor echo \c are portable). | 
|  | version (Windows) TestScript prog = | 
|  | "echo|set /p=%~1 | 
|  | echo|set /p=%~2 1>&2 | 
|  | exit 123"; | 
|  | else version (Android) TestScript prog = | 
|  | `echo -n $1 | 
|  | echo -n $2 >&2 | 
|  | exit 123`; | 
|  | else version (Posix) TestScript prog = | 
|  | `printf '%s' $1 | 
|  | printf '%s' $2 >&2 | 
|  | exit 123`; | 
|  | auto r = execute([prog.path, "foo", "bar"]); | 
|  | assert(r.status == 123); | 
|  | assert(r.output.stripRight() == "foobar"); | 
|  | auto s = execute([prog.path, "Hello", "World"]); | 
|  | assert(s.status == 123); | 
|  | assert(s.output.stripRight() == "HelloWorld"); | 
|  | } | 
|  |  | 
|  | @safe unittest | 
|  | { | 
|  | import std.string; | 
|  | auto r1 = executeShell("echo foo"); | 
|  | assert(r1.status == 0); | 
|  | assert(r1.output.chomp() == "foo"); | 
|  | auto r2 = executeShell("echo bar 1>&2"); | 
|  | assert(r2.status == 0); | 
|  | assert(r2.output.chomp().stripRight() == "bar"); | 
|  | auto r3 = executeShell("exit 123"); | 
|  | assert(r3.status == 123); | 
|  | assert(r3.output.empty); | 
|  | } | 
|  |  | 
|  | @system unittest | 
|  | { | 
|  | // Temporarily disable output to stderr so as to not spam the build log. | 
|  | import std.stdio : stderr; | 
|  | import std.typecons : Tuple; | 
|  | import std.file : readText, exists, remove; | 
|  | import std.traits : ReturnType; | 
|  |  | 
|  | ReturnType!executeShell r; | 
|  | auto tmpname = uniqueTempPath; | 
|  | scope(exit) if (exists(tmpname)) remove(tmpname); | 
|  | auto t = stderr; | 
|  | // Open a new scope to minimize code ran with stderr redirected. | 
|  | { | 
|  | stderr.open(tmpname, "w"); | 
|  | scope(exit) stderr = t; | 
|  | r = executeShell("echo D rox>&2", null, Config.stderrPassThrough); | 
|  | } | 
|  | assert(r.status == 0); | 
|  | assert(r.output.empty); | 
|  | auto witness = readText(tmpname); | 
|  | import std.ascii : newline; | 
|  | assert(witness == "D rox" ~ newline, "'" ~ witness ~ "'"); | 
|  | } | 
|  |  | 
|  | @safe unittest | 
|  | { | 
|  | import std.typecons : Tuple; | 
|  | void foo() //Just test the compilation | 
|  | { | 
|  | auto ret1 = execute(["dummy", "arg"]); | 
|  | auto ret2 = executeShell("dummy arg"); | 
|  | static assert(is(typeof(ret1) == typeof(ret2))); | 
|  |  | 
|  | Tuple!(int, string) ret3 = execute(["dummy", "arg"]); | 
|  | } | 
|  | } | 
|  |  | 
|  | /// An exception that signals a problem with starting or waiting for a process. | 
|  | class ProcessException : Exception | 
|  | { | 
|  | import std.exception : basicExceptionCtors; | 
|  | mixin basicExceptionCtors; | 
|  |  | 
|  | // Creates a new ProcessException based on errno. | 
|  | static ProcessException newFromErrno(string customMsg = null, | 
|  | string file = __FILE__, | 
|  | size_t line = __LINE__) | 
|  | { | 
|  | import core.stdc.errno : errno; | 
|  | return newFromErrno(errno, customMsg, file, line); | 
|  | } | 
|  |  | 
|  | // ditto, but error number is provided by caller | 
|  | static ProcessException newFromErrno(int error, | 
|  | string customMsg = null, | 
|  | string file = __FILE__, | 
|  | size_t line = __LINE__) | 
|  | { | 
|  | import std.exception : errnoString; | 
|  | auto errnoMsg = errnoString(error); | 
|  | auto msg = customMsg.empty ? errnoMsg | 
|  | : customMsg ~ " (" ~ errnoMsg ~ ')'; | 
|  | return new ProcessException(msg, file, line); | 
|  | } | 
|  |  | 
|  | // Creates a new ProcessException based on GetLastError() (Windows only). | 
|  | version (Windows) | 
|  | static ProcessException newFromLastError(string customMsg = null, | 
|  | string file = __FILE__, | 
|  | size_t line = __LINE__) | 
|  | { | 
|  | auto lastMsg = generateSysErrorMsg(); | 
|  | auto msg = customMsg.empty ? lastMsg | 
|  | : customMsg ~ " (" ~ lastMsg ~ ')'; | 
|  | return new ProcessException(msg, file, line); | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | /** | 
|  | Determines the path to the current user's preferred command interpreter. | 
|  |  | 
|  | On Windows, this function returns the contents of the COMSPEC environment | 
|  | variable, if it exists.  Otherwise, it returns the result of $(LREF nativeShell). | 
|  |  | 
|  | On POSIX, `userShell` returns the contents of the SHELL environment | 
|  | variable, if it exists and is non-empty.  Otherwise, it returns the result of | 
|  | $(LREF nativeShell). | 
|  | */ | 
|  | @property string userShell() @safe | 
|  | { | 
|  | version (Windows)      return environment.get("COMSPEC", nativeShell); | 
|  | else version (Posix)   return environment.get("SHELL", nativeShell); | 
|  | } | 
|  |  | 
|  | /** | 
|  | The platform-specific native shell path. | 
|  |  | 
|  | This function returns `"cmd.exe"` on Windows, `"/bin/sh"` on POSIX, and | 
|  | `"/system/bin/sh"` on Android. | 
|  | */ | 
|  | @property string nativeShell() @safe @nogc pure nothrow | 
|  | { | 
|  | version (Windows)      return "cmd.exe"; | 
|  | else version (Android) return "/system/bin/sh"; | 
|  | else version (Posix)   return "/bin/sh"; | 
|  | } | 
|  |  | 
|  | // A command-line switch that indicates to the shell that it should | 
|  | // interpret the following argument as a command to be executed. | 
|  | version (Posix)   private immutable string shellSwitch = "-c"; | 
|  | version (Windows) private immutable string shellSwitch = "/C"; | 
|  |  | 
|  | // Unittest support code:  TestScript takes a string that contains a | 
|  | // shell script for the current platform, and writes it to a temporary | 
|  | // file. On Windows the file name gets a .cmd extension, while on | 
|  | // POSIX its executable permission bit is set.  The file is | 
|  | // automatically deleted when the object goes out of scope. | 
|  | version (StdUnittest) | 
|  | private struct TestScript | 
|  | { | 
|  | this(string code) @system | 
|  | { | 
|  | // @system due to chmod | 
|  | import std.ascii : newline; | 
|  | import std.file : write; | 
|  | version (Windows) | 
|  | { | 
|  | auto ext = ".cmd"; | 
|  | auto firstLine = "@echo off"; | 
|  | } | 
|  | else version (Posix) | 
|  | { | 
|  | auto ext = ""; | 
|  | auto firstLine = "#!" ~ nativeShell; | 
|  | } | 
|  | path = uniqueTempPath()~ext; | 
|  | write(path, firstLine ~ newline ~ code ~ newline); | 
|  | version (Posix) | 
|  | { | 
|  | import core.sys.posix.sys.stat : chmod; | 
|  | import std.conv : octal; | 
|  | chmod(path.tempCString(), octal!777); | 
|  | } | 
|  | } | 
|  |  | 
|  | ~this() | 
|  | { | 
|  | import std.file : remove, exists; | 
|  | if (!path.empty && exists(path)) | 
|  | { | 
|  | try { remove(path); } | 
|  | catch (Exception e) | 
|  | { | 
|  | debug std.stdio.stderr.writeln(e.msg); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | string path; | 
|  | } | 
|  |  | 
|  |  | 
|  | // ============================================================================= | 
|  | // Functions for shell command quoting/escaping. | 
|  | // ============================================================================= | 
|  |  | 
|  |  | 
|  | /* | 
|  | Command line arguments exist in three forms: | 
|  | 1) string or char* array, as received by main. | 
|  | Also used internally on POSIX systems. | 
|  | 2) Command line string, as used in Windows' | 
|  | CreateProcess and CommandLineToArgvW functions. | 
|  | A specific quoting and escaping algorithm is used | 
|  | to distinguish individual arguments. | 
|  | 3) Shell command string, as written at a shell prompt | 
|  | or passed to cmd /C - this one may contain shell | 
|  | control characters, e.g. > or | for redirection / | 
|  | piping - thus, yet another layer of escaping is | 
|  | used to distinguish them from program arguments. | 
|  |  | 
|  | Except for escapeWindowsArgument, the intermediary | 
|  | format (2) is hidden away from the user in this module. | 
|  | */ | 
|  |  | 
|  | /** | 
|  | Escapes an argv-style argument array to be used with $(LREF spawnShell), | 
|  | $(LREF pipeShell) or $(LREF executeShell). | 
|  | --- | 
|  | string url = "http://dlang.org/"; | 
|  | executeShell(escapeShellCommand("wget", url, "-O", "dlang-index.html")); | 
|  | --- | 
|  |  | 
|  | Concatenate multiple `escapeShellCommand` and | 
|  | $(LREF escapeShellFileName) results to use shell redirection or | 
|  | piping operators. | 
|  | --- | 
|  | executeShell( | 
|  | escapeShellCommand("curl", "http://dlang.org/download.html") ~ | 
|  | "|" ~ | 
|  | escapeShellCommand("grep", "-o", `http://\S*\.zip`) ~ | 
|  | ">" ~ | 
|  | escapeShellFileName("D download links.txt")); | 
|  | --- | 
|  |  | 
|  | Throws: | 
|  | $(OBJECTREF Exception) if any part of the command line contains unescapable | 
|  | characters (NUL on all platforms, as well as CR and LF on Windows). | 
|  | */ | 
|  | string escapeShellCommand(scope const(char[])[] args...) @safe pure | 
|  | { | 
|  | if (args.empty) | 
|  | return null; | 
|  | version (Windows) | 
|  | { | 
|  | // Do not ^-escape the first argument (the program path), | 
|  | // as the shell parses it differently from parameters. | 
|  | // ^-escaping a program path that contains spaces will fail. | 
|  | string result = escapeShellFileName(args[0]); | 
|  | if (args.length > 1) | 
|  | { | 
|  | result ~= " " ~ escapeShellCommandString( | 
|  | escapeShellArguments(args[1..$])); | 
|  | } | 
|  | return result; | 
|  | } | 
|  | version (Posix) | 
|  | { | 
|  | return escapeShellCommandString(escapeShellArguments(args)); | 
|  | } | 
|  | } | 
|  |  | 
|  | @safe unittest | 
|  | { | 
|  | // This is a simple unit test without any special requirements, | 
|  | // in addition to the unittest_burnin one below which requires | 
|  | // special preparation. | 
|  |  | 
|  | struct TestVector { string[] args; string windows, posix; } | 
|  | TestVector[] tests = | 
|  | [ | 
|  | { | 
|  | args    : ["foo bar"], | 
|  | windows : `"foo bar"`, | 
|  | posix   : `'foo bar'` | 
|  | }, | 
|  | { | 
|  | args    : ["foo bar", "hello"], | 
|  | windows : `"foo bar" hello`, | 
|  | posix   : `'foo bar' hello` | 
|  | }, | 
|  | { | 
|  | args    : ["foo bar", "hello world"], | 
|  | windows : `"foo bar" ^"hello world^"`, | 
|  | posix   : `'foo bar' 'hello world'` | 
|  | }, | 
|  | { | 
|  | args    : ["foo bar", "hello", "world"], | 
|  | windows : `"foo bar" hello world`, | 
|  | posix   : `'foo bar' hello world` | 
|  | }, | 
|  | { | 
|  | args    : ["foo bar", `'"^\`], | 
|  | windows : `"foo bar" ^"'\^"^^\\^"`, | 
|  | posix   : `'foo bar' ''\''"^\'` | 
|  | }, | 
|  | { | 
|  | args    : ["foo bar", ""], | 
|  | windows : `"foo bar" ^"^"`, | 
|  | posix   : `'foo bar' ''` | 
|  | }, | 
|  | { | 
|  | args    : ["foo bar", "2"], | 
|  | windows : `"foo bar" ^"2^"`, | 
|  | posix   : `'foo bar' '2'` | 
|  | }, | 
|  | ]; | 
|  |  | 
|  | foreach (test; tests) | 
|  | { | 
|  | auto actual = escapeShellCommand(test.args); | 
|  | version (Windows) | 
|  | string expected = test.windows; | 
|  | else | 
|  | string expected = test.posix; | 
|  | assert(actual == expected, "\nExpected: " ~ expected ~ "\nGot: " ~ actual); | 
|  | } | 
|  | } | 
|  |  | 
|  | private string escapeShellCommandString(return scope string command) @safe pure | 
|  | { | 
|  | version (Windows) | 
|  | return escapeWindowsShellCommand(command); | 
|  | else | 
|  | return command; | 
|  | } | 
|  |  | 
|  | private string escapeWindowsShellCommand(scope const(char)[] command) @safe pure | 
|  | { | 
|  | import std.array : appender; | 
|  | auto result = appender!string(); | 
|  | result.reserve(command.length); | 
|  |  | 
|  | foreach (c; command) | 
|  | switch (c) | 
|  | { | 
|  | case '\0': | 
|  | throw new Exception("Cannot put NUL in command line"); | 
|  | case '\r': | 
|  | case '\n': | 
|  | throw new Exception("CR/LF are not escapable"); | 
|  | case '\x01': .. case '\x09': | 
|  | case '\x0B': .. case '\x0C': | 
|  | case '\x0E': .. case '\x1F': | 
|  | case '"': | 
|  | case '^': | 
|  | case '&': | 
|  | case '<': | 
|  | case '>': | 
|  | case '|': | 
|  | result.put('^'); | 
|  | goto default; | 
|  | default: | 
|  | result.put(c); | 
|  | } | 
|  | return result.data; | 
|  | } | 
|  |  | 
|  | private string escapeShellArguments(scope const(char[])[] args...) | 
|  | @trusted pure nothrow | 
|  | { | 
|  | import std.exception : assumeUnique; | 
|  | char[] buf; | 
|  |  | 
|  | @safe nothrow | 
|  | char[] allocator(size_t size) | 
|  | { | 
|  | if (buf.length == 0) | 
|  | return buf = new char[size]; | 
|  | else | 
|  | { | 
|  | auto p = buf.length; | 
|  | buf.length = buf.length + 1 + size; | 
|  | buf[p++] = ' '; | 
|  | return buf[p .. p+size]; | 
|  | } | 
|  | } | 
|  |  | 
|  | foreach (arg; args) | 
|  | escapeShellArgument!allocator(arg); | 
|  | return assumeUnique(buf); | 
|  | } | 
|  |  | 
|  | private auto escapeShellArgument(alias allocator)(scope const(char)[] arg) @safe nothrow | 
|  | { | 
|  | // The unittest for this function requires special | 
|  | // preparation - see below. | 
|  |  | 
|  | version (Windows) | 
|  | return escapeWindowsArgumentImpl!allocator(arg); | 
|  | else | 
|  | return escapePosixArgumentImpl!allocator(arg); | 
|  | } | 
|  |  | 
|  | /** | 
|  | Quotes a command-line argument in a manner conforming to the behavior of | 
|  | $(LINK2 http://msdn.microsoft.com/en-us/library/windows/desktop/bb776391(v=vs.85).aspx, | 
|  | CommandLineToArgvW). | 
|  | */ | 
|  | string escapeWindowsArgument(scope const(char)[] arg) @trusted pure nothrow | 
|  | { | 
|  | // Rationale for leaving this function as public: | 
|  | // this algorithm of escaping paths is also used in other software, | 
|  | // e.g. DMD's response files. | 
|  | import std.exception : assumeUnique; | 
|  | auto buf = escapeWindowsArgumentImpl!charAllocator(arg); | 
|  | return assumeUnique(buf); | 
|  | } | 
|  |  | 
|  |  | 
|  | private char[] charAllocator(size_t size) @safe pure nothrow | 
|  | { | 
|  | return new char[size]; | 
|  | } | 
|  |  | 
|  |  | 
|  | private char[] escapeWindowsArgumentImpl(alias allocator)(scope const(char)[] arg) | 
|  | @safe nothrow | 
|  | if (is(typeof(allocator(size_t.init)[0] = char.init))) | 
|  | { | 
|  | // References: | 
|  | // * http://msdn.microsoft.com/en-us/library/windows/desktop/bb776391(v=vs.85).aspx | 
|  | // * http://blogs.msdn.com/b/oldnewthing/archive/2010/09/17/10063629.aspx | 
|  |  | 
|  | // Check if the string needs to be escaped, | 
|  | // and calculate the total string size. | 
|  |  | 
|  | // Trailing backslashes must be escaped | 
|  | bool escaping = true; | 
|  | bool needEscape = false; | 
|  | // Result size = input size + 2 for surrounding quotes + 1 for the | 
|  | // backslash for each escaped character. | 
|  | size_t size = 1 + arg.length + 1; | 
|  |  | 
|  | foreach_reverse (char c; arg) | 
|  | { | 
|  | if (c == '"') | 
|  | { | 
|  | needEscape = true; | 
|  | escaping = true; | 
|  | size++; | 
|  | } | 
|  | else | 
|  | if (c == '\\') | 
|  | { | 
|  | if (escaping) | 
|  | size++; | 
|  | } | 
|  | else | 
|  | { | 
|  | if (c == ' ' || c == '\t') | 
|  | needEscape = true; | 
|  | escaping = false; | 
|  | } | 
|  | } | 
|  |  | 
|  | import std.ascii : isDigit; | 
|  | // Empty arguments need to be specified as "" | 
|  | if (!arg.length) | 
|  | needEscape = true; | 
|  | else | 
|  | // Arguments ending with digits need to be escaped, | 
|  | // to disambiguate with 1>file redirection syntax | 
|  | if (isDigit(arg[$-1])) | 
|  | needEscape = true; | 
|  |  | 
|  | if (!needEscape) | 
|  | return allocator(arg.length)[] = arg; | 
|  |  | 
|  | // Construct result string. | 
|  |  | 
|  | auto buf = allocator(size); | 
|  | size_t p = size; | 
|  | buf[--p] = '"'; | 
|  | escaping = true; | 
|  | foreach_reverse (char c; arg) | 
|  | { | 
|  | if (c == '"') | 
|  | escaping = true; | 
|  | else | 
|  | if (c != '\\') | 
|  | escaping = false; | 
|  |  | 
|  | buf[--p] = c; | 
|  | if (escaping) | 
|  | buf[--p] = '\\'; | 
|  | } | 
|  | buf[--p] = '"'; | 
|  | assert(p == 0); | 
|  |  | 
|  | return buf; | 
|  | } | 
|  |  | 
|  | version (Windows) version (StdUnittest) | 
|  | { | 
|  | private: | 
|  | import core.stdc.stddef; | 
|  | import core.stdc.wchar_ : wcslen; | 
|  | import core.sys.windows.shellapi : CommandLineToArgvW; | 
|  | import core.sys.windows.winbase; | 
|  | import core.sys.windows.winnt; | 
|  | import std.array; | 
|  |  | 
|  | string[] parseCommandLine(string line) | 
|  | { | 
|  | import std.algorithm.iteration : map; | 
|  | import std.array : array; | 
|  | import std.conv : to; | 
|  | auto lpCommandLine = (to!(WCHAR[])(line) ~ '\0').ptr; | 
|  | int numArgs; | 
|  | auto args = CommandLineToArgvW(lpCommandLine, &numArgs); | 
|  | scope(exit) LocalFree(args); | 
|  | return args[0 .. numArgs] | 
|  | .map!(arg => to!string(arg[0 .. wcslen(arg)])) | 
|  | .array(); | 
|  | } | 
|  |  | 
|  | @system unittest | 
|  | { | 
|  | import std.conv : text; | 
|  | string[] testStrings = [ | 
|  | `Hello`, | 
|  | `Hello, world`, | 
|  | `Hello, "world"`, | 
|  | `C:\`, | 
|  | `C:\dmd`, | 
|  | `C:\Program Files\`, | 
|  | ]; | 
|  |  | 
|  | enum CHARS = `_x\" *&^` ~ "\t"; // _ is placeholder for nothing | 
|  | foreach (c1; CHARS) | 
|  | foreach (c2; CHARS) | 
|  | foreach (c3; CHARS) | 
|  | foreach (c4; CHARS) | 
|  | testStrings ~= [c1, c2, c3, c4].replace("_", ""); | 
|  |  | 
|  | foreach (s; testStrings) | 
|  | { | 
|  | auto q = escapeWindowsArgument(s); | 
|  | auto args = parseCommandLine("Dummy.exe " ~ q); | 
|  | assert(args.length == 2, s ~ " => " ~ q ~ " #" ~ text(args.length-1)); | 
|  | assert(args[1] == s, s ~ " => " ~ q ~ " => " ~ args[1]); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | private string escapePosixArgument(scope const(char)[] arg) @trusted pure nothrow | 
|  | { | 
|  | import std.exception : assumeUnique; | 
|  | auto buf = escapePosixArgumentImpl!charAllocator(arg); | 
|  | return assumeUnique(buf); | 
|  | } | 
|  |  | 
|  | private char[] escapePosixArgumentImpl(alias allocator)(scope const(char)[] arg) | 
|  | @safe nothrow | 
|  | if (is(typeof(allocator(size_t.init)[0] = char.init))) | 
|  | { | 
|  | bool needQuoting = { | 
|  | import std.ascii : isAlphaNum, isDigit; | 
|  | import std.algorithm.comparison : among; | 
|  |  | 
|  | // Empty arguments need to be specified as '' | 
|  | if (arg.length == 0) | 
|  | return true; | 
|  | // Arguments ending with digits need to be escaped, | 
|  | // to disambiguate with 1>file redirection syntax | 
|  | if (isDigit(arg[$-1])) | 
|  | return true; | 
|  |  | 
|  | // Obtained using: | 
|  | // for n in $(seq 1 255) ; do | 
|  | //     c=$(printf \\$(printf "%o" $n)) | 
|  | //     q=$(/bin/printf '%q' "$c") | 
|  | //     if [[ "$q" == "$c" ]] ; then printf "%s, " "'$c'" ; fi | 
|  | // done | 
|  | // printf '\n' | 
|  | foreach (char c; arg) | 
|  | if (!isAlphaNum(c) && !c.among('%', '+', ',', '-', '.', '/', ':', '@', ']', '_')) | 
|  | return true; | 
|  | return false; | 
|  | }(); | 
|  | if (!needQuoting) | 
|  | { | 
|  | auto buf = allocator(arg.length); | 
|  | buf[] = arg; | 
|  | return buf; | 
|  | } | 
|  |  | 
|  | // '\'' means: close quoted part of argument, append an escaped | 
|  | // single quote, and reopen quotes | 
|  |  | 
|  | // Below code is equivalent to: | 
|  | // return `'` ~ std.array.replace(arg, `'`, `'\''`) ~ `'`; | 
|  |  | 
|  | size_t size = 1 + arg.length + 1; | 
|  | foreach (char c; arg) | 
|  | if (c == '\'') | 
|  | size += 3; | 
|  |  | 
|  | auto buf = allocator(size); | 
|  | size_t p = 0; | 
|  | buf[p++] = '\''; | 
|  | foreach (char c; arg) | 
|  | if (c == '\'') | 
|  | { | 
|  | buf[p .. p+4] = `'\''`; | 
|  | p += 4; | 
|  | } | 
|  | else | 
|  | buf[p++] = c; | 
|  | buf[p++] = '\''; | 
|  | assert(p == size); | 
|  |  | 
|  | return buf; | 
|  | } | 
|  |  | 
|  | /** | 
|  | Escapes a filename to be used for shell redirection with $(LREF spawnShell), | 
|  | $(LREF pipeShell) or $(LREF executeShell). | 
|  | */ | 
|  | string escapeShellFileName(scope const(char)[] fileName) @trusted pure nothrow | 
|  | { | 
|  | // The unittest for this function requires special | 
|  | // preparation - see below. | 
|  |  | 
|  | version (Windows) | 
|  | { | 
|  | // If a file starts with &, it can cause cmd.exe to misinterpret | 
|  | // the file name as the stream redirection syntax: | 
|  | //     command > "&foo.txt" | 
|  | // gets interpreted as | 
|  | //     command >&foo.txt | 
|  | // Prepend .\ to disambiguate. | 
|  |  | 
|  | if (fileName.length && fileName[0] == '&') | 
|  | return cast(string)(`".\` ~ fileName ~ '"'); | 
|  |  | 
|  | return cast(string)('"' ~ fileName ~ '"'); | 
|  | } | 
|  | else | 
|  | return escapePosixArgument(fileName); | 
|  | } | 
|  |  | 
|  | // Loop generating strings with random characters | 
|  | //version = unittest_burnin; | 
|  |  | 
|  | version (unittest_burnin) | 
|  | @system unittest | 
|  | { | 
|  | // There are no readily-available commands on all platforms suitable | 
|  | // for properly testing command escaping. The behavior of CMD's "echo" | 
|  | // built-in differs from the POSIX program, and Windows ports of POSIX | 
|  | // environments (Cygwin, msys, gnuwin32) may interfere with their own | 
|  | // "echo" ports. | 
|  |  | 
|  | // To run this unit test, create std_process_unittest_helper.d with the | 
|  | // following content and compile it: | 
|  | // import std.stdio, std.array; void main(string[] args) { write(args.join("\0")); } | 
|  | // Then, test this module with: | 
|  | // rdmd --main -unittest -version=unittest_burnin process.d | 
|  |  | 
|  | import std.file : readText, remove; | 
|  | import std.format : format; | 
|  | import std.path : absolutePath; | 
|  | import std.random : uniform; | 
|  |  | 
|  | auto helper = absolutePath("std_process_unittest_helper"); | 
|  | assert(executeShell(helper ~ " hello").output.split("\0")[1..$] == ["hello"], "Helper malfunction"); | 
|  |  | 
|  | void test(string[] s, string fn) | 
|  | { | 
|  | string e; | 
|  | string[] g; | 
|  |  | 
|  | e = escapeShellCommand(helper ~ s); | 
|  | { | 
|  | scope(failure) writefln("executeShell() failed.\nExpected:\t%s\nEncoded:\t%s", s, [e]); | 
|  | auto result = executeShell(e); | 
|  | assert(result.status == 0, "std_process_unittest_helper failed"); | 
|  | g = result.output.split("\0")[1..$]; | 
|  | } | 
|  | assert(s == g, format("executeShell() test failed.\nExpected:\t%s\nGot:\t\t%s\nEncoded:\t%s", s, g, [e])); | 
|  |  | 
|  | e = escapeShellCommand(helper ~ s) ~ ">" ~ escapeShellFileName(fn); | 
|  | { | 
|  | scope(failure) writefln( | 
|  | "executeShell() with redirect failed.\nExpected:\t%s\nFilename:\t%s\nEncoded:\t%s", s, [fn], [e]); | 
|  | auto result = executeShell(e); | 
|  | assert(result.status == 0, "std_process_unittest_helper failed"); | 
|  | assert(!result.output.length, "No output expected, got:\n" ~ result.output); | 
|  | g = readText(fn).split("\0")[1..$]; | 
|  | } | 
|  | remove(fn); | 
|  | assert(s == g, | 
|  | format("executeShell() with redirect test failed.\nExpected:\t%s\nGot:\t\t%s\nEncoded:\t%s", s, g, [e])); | 
|  | } | 
|  |  | 
|  | while (true) | 
|  | { | 
|  | string[] args; | 
|  | foreach (n; 0 .. uniform(1, 4)) | 
|  | { | 
|  | string arg; | 
|  | foreach (l; 0 .. uniform(0, 10)) | 
|  | { | 
|  | dchar c; | 
|  | while (true) | 
|  | { | 
|  | version (Windows) | 
|  | { | 
|  | // As long as DMD's system() uses CreateProcessA, | 
|  | // we can't reliably pass Unicode | 
|  | c = uniform(0, 128); | 
|  | } | 
|  | else | 
|  | c = uniform!ubyte(); | 
|  |  | 
|  | if (c == 0) | 
|  | continue; // argv-strings are zero-terminated | 
|  | version (Windows) | 
|  | if (c == '\r' || c == '\n') | 
|  | continue; // newlines are unescapable on Windows | 
|  | break; | 
|  | } | 
|  | arg ~= c; | 
|  | } | 
|  | args ~= arg; | 
|  | } | 
|  |  | 
|  | // generate filename | 
|  | string fn; | 
|  | foreach (l; 0 .. uniform(1, 10)) | 
|  | { | 
|  | dchar c; | 
|  | while (true) | 
|  | { | 
|  | version (Windows) | 
|  | c = uniform(0, 128); // as above | 
|  | else | 
|  | c = uniform!ubyte(); | 
|  |  | 
|  | if (c == 0 || c == '/') | 
|  | continue; // NUL and / are the only characters | 
|  | // forbidden in POSIX filenames | 
|  | version (Windows) | 
|  | if (c < '\x20' || c == '<' || c == '>' || c == ':' || | 
|  | c == '"' || c == '\\' || c == '|' || c == '?' || c == '*') | 
|  | continue; // http://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx | 
|  | break; | 
|  | } | 
|  |  | 
|  | fn ~= c; | 
|  | } | 
|  | fn = fn[0..$/2] ~ "_testfile_" ~ fn[$/2..$]; | 
|  |  | 
|  | test(args, fn); | 
|  | } | 
|  | } | 
|  |  | 
|  | // ============================================================================= | 
|  | // Everything below this line was part of the old std.process, and most of | 
|  | // it will be deprecated and removed. | 
|  | // ============================================================================= | 
|  |  | 
|  |  | 
|  | /* | 
|  | Copyright: Copyright The D Language Foundation 2007 - 2009. | 
|  | License:   $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). | 
|  | Authors:   $(HTTP digitalmars.com, Walter Bright), | 
|  | $(HTTP erdani.org, Andrei Alexandrescu), | 
|  | $(HTTP thecybershadow.net, Vladimir Panteleev) | 
|  | Source:    $(PHOBOSSRC std/_process.d) | 
|  | */ | 
|  | /* | 
|  | Copyright The D Language Foundation 2007 - 2009. | 
|  | Distributed under the Boost Software License, Version 1.0. | 
|  | (See accompanying file LICENSE_1_0.txt or copy at | 
|  | http://www.boost.org/LICENSE_1_0.txt) | 
|  | */ | 
|  |  | 
|  |  | 
|  | import core.stdc.errno; | 
|  | import core.stdc.stdlib; | 
|  | import core.stdc.string; | 
|  | import core.thread; | 
|  |  | 
|  | version (Windows) | 
|  | { | 
|  | import std.file, std.format, std.random; | 
|  | } | 
|  | version (Posix) | 
|  | { | 
|  | import core.sys.posix.stdlib; | 
|  | } | 
|  |  | 
|  | private const(char)** toAStringz(in string[] a) | 
|  | { | 
|  | import std.string : toStringz; | 
|  | auto p = (new const(char)*[1 + a.length]).ptr; | 
|  | foreach (i, string s; a) | 
|  | p[i] = toStringz(s); | 
|  | p[a.length] = null; | 
|  | return p; | 
|  | } | 
|  |  | 
|  |  | 
|  | /* ========================================================== */ | 
|  |  | 
|  | //version (Windows) | 
|  | //{ | 
|  | //    int spawnvp(int mode, string pathname, string[] argv) | 
|  | //    { | 
|  | //      char** argv_ = cast(char**) core.stdc.stdlib.malloc((char*).sizeof * (1 + argv.length)); | 
|  | //      scope(exit) core.stdc.stdlib.free(argv_); | 
|  | // | 
|  | //      toAStringz(argv, argv_); | 
|  | // | 
|  | //      return spawnvp(mode, pathname.tempCString(), argv_); | 
|  | //    } | 
|  | //} | 
|  |  | 
|  | // Incorporating idea (for spawnvp() on Posix) from Dave Fladebo | 
|  |  | 
|  | enum { _P_WAIT, _P_NOWAIT, _P_OVERLAY } | 
|  | version (Windows) extern(C) int spawnvp(int, scope const(char) *, scope const(char*)*); | 
|  | alias P_WAIT = _P_WAIT; | 
|  | alias P_NOWAIT = _P_NOWAIT; | 
|  |  | 
|  | /* ========================================================== */ | 
|  |  | 
|  | version (StdDdoc) | 
|  | { | 
|  | /** | 
|  | Replaces the current process by executing a command, `pathname`, with | 
|  | the arguments in `argv`. | 
|  |  | 
|  | $(BLUE This function is Posix-Only.) | 
|  |  | 
|  | Typically, the first element of `argv` is | 
|  | the command being executed, i.e. $(D argv[0] == pathname). The 'p' | 
|  | versions of `exec` search the PATH environment variable for $(D | 
|  | pathname). The 'e' versions additionally take the new process' | 
|  | environment variables as an array of strings of the form key=value. | 
|  |  | 
|  | Does not return on success (the current process will have been | 
|  | replaced). Returns -1 on failure with no indication of the | 
|  | underlying error. | 
|  |  | 
|  | Windows_specific: | 
|  | These functions are only supported on POSIX platforms, as the Windows | 
|  | operating systems do not provide the ability to overwrite the current | 
|  | process image with another. In single-threaded programs it is possible | 
|  | to approximate the effect of `execv*` by using $(LREF spawnProcess) | 
|  | and terminating the current process once the child process has returned. | 
|  | For example: | 
|  | --- | 
|  | auto commandLine = [ "program", "arg1", "arg2" ]; | 
|  | version (Posix) | 
|  | { | 
|  | execv(commandLine[0], commandLine); | 
|  | throw new Exception("Failed to execute program"); | 
|  | } | 
|  | else version (Windows) | 
|  | { | 
|  | import core.stdc.stdlib : _Exit; | 
|  | _Exit(wait(spawnProcess(commandLine))); | 
|  | } | 
|  | --- | 
|  | This is, however, NOT equivalent to POSIX' `execv*`.  For one thing, the | 
|  | executed program is started as a separate process, with all this entails. | 
|  | Secondly, in a multithreaded program, other threads will continue to do | 
|  | work while the current thread is waiting for the child process to complete. | 
|  |  | 
|  | A better option may sometimes be to terminate the current program immediately | 
|  | after spawning the child process.  This is the behaviour exhibited by the | 
|  | $(LINK2 http://msdn.microsoft.com/en-us/library/431x4c1w.aspx,`__exec`) | 
|  | functions in Microsoft's C runtime library, and it is how D's now-deprecated | 
|  | Windows `execv*` functions work. Example: | 
|  | --- | 
|  | auto commandLine = [ "program", "arg1", "arg2" ]; | 
|  | version (Posix) | 
|  | { | 
|  | execv(commandLine[0], commandLine); | 
|  | throw new Exception("Failed to execute program"); | 
|  | } | 
|  | else version (Windows) | 
|  | { | 
|  | spawnProcess(commandLine); | 
|  | import core.stdc.stdlib : _exit; | 
|  | _exit(0); | 
|  | } | 
|  | --- | 
|  | */ | 
|  | int execv(in string pathname, in string[] argv); | 
|  | ///ditto | 
|  | int execve(in string pathname, in string[] argv, in string[] envp); | 
|  | /// ditto | 
|  | int execvp(in string pathname, in string[] argv); | 
|  | /// ditto | 
|  | int execvpe(in string pathname, in string[] argv, in string[] envp); | 
|  | } | 
|  | else version (Posix) | 
|  | { | 
|  | int execv(in string pathname, in string[] argv) | 
|  | { | 
|  | return execv_(pathname, argv); | 
|  | } | 
|  | int execve(in string pathname, in string[] argv, in string[] envp) | 
|  | { | 
|  | return execve_(pathname, argv, envp); | 
|  | } | 
|  | int execvp(in string pathname, in string[] argv) | 
|  | { | 
|  | return execvp_(pathname, argv); | 
|  | } | 
|  | int execvpe(in string pathname, in string[] argv, in string[] envp) | 
|  | { | 
|  | return execvpe_(pathname, argv, envp); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Move these C declarations to druntime if we decide to keep the D wrappers | 
|  | extern(C) | 
|  | { | 
|  | int execv(scope const(char) *, scope const(char *)*); | 
|  | int execve(scope const(char)*, scope const(char*)*, scope const(char*)*); | 
|  | int execvp(scope const(char)*, scope const(char*)*); | 
|  | version (Windows) int execvpe(scope const(char)*, scope const(char*)*, scope const(char*)*); | 
|  | } | 
|  |  | 
|  | private int execv_(in string pathname, in string[] argv) | 
|  | { | 
|  | return execv(pathname.tempCString(), toAStringz(argv)); | 
|  | } | 
|  |  | 
|  | private int execve_(in string pathname, in string[] argv, in string[] envp) | 
|  | { | 
|  | return execve(pathname.tempCString(), toAStringz(argv), toAStringz(envp)); | 
|  | } | 
|  |  | 
|  | private int execvp_(in string pathname, in string[] argv) | 
|  | { | 
|  | return execvp(pathname.tempCString(), toAStringz(argv)); | 
|  | } | 
|  |  | 
|  | private int execvpe_(in string pathname, in string[] argv, in string[] envp) | 
|  | { | 
|  | version (Posix) | 
|  | { | 
|  | import std.array : split; | 
|  | import std.conv : to; | 
|  | // Is pathname rooted? | 
|  | if (pathname[0] == '/') | 
|  | { | 
|  | // Yes, so just call execve() | 
|  | return execve(pathname, argv, envp); | 
|  | } | 
|  | else | 
|  | { | 
|  | // No, so must traverse PATHs, looking for first match | 
|  | string[]    envPaths    =   split( | 
|  | to!string(core.stdc.stdlib.getenv("PATH")), ":"); | 
|  | int         iRet        =   0; | 
|  |  | 
|  | // Note: if any call to execve() succeeds, this process will cease | 
|  | // execution, so there's no need to check the execve() result through | 
|  | // the loop. | 
|  |  | 
|  | foreach (string pathDir; envPaths) | 
|  | { | 
|  | string  composite   =  cast(string) (pathDir ~ "/" ~ pathname); | 
|  |  | 
|  | iRet = execve(composite, argv, envp); | 
|  | } | 
|  | if (0 != iRet) | 
|  | { | 
|  | iRet = execve(pathname, argv, envp); | 
|  | } | 
|  |  | 
|  | return iRet; | 
|  | } | 
|  | } | 
|  | else version (Windows) | 
|  | { | 
|  | return execvpe(pathname.tempCString(), toAStringz(argv), toAStringz(envp)); | 
|  | } | 
|  | else | 
|  | { | 
|  | static assert(0); | 
|  | } // version | 
|  | } | 
|  |  | 
|  | version (StdDdoc) | 
|  | { | 
|  | /**************************************** | 
|  | * Start up the browser and set it to viewing the page at url. | 
|  | */ | 
|  | void browse(scope const(char)[] url); | 
|  | } | 
|  | else | 
|  | version (Windows) | 
|  | { | 
|  | import core.sys.windows.shellapi, core.sys.windows.winuser; | 
|  |  | 
|  | pragma(lib,"shell32.lib"); | 
|  |  | 
|  | void browse(scope const(char)[] url) nothrow @nogc @trusted | 
|  | { | 
|  | ShellExecuteW(null, "open", url.tempCStringW(), null, null, SW_SHOWNORMAL); | 
|  | } | 
|  | } | 
|  | else version (Posix) | 
|  | { | 
|  | import core.stdc.stdio; | 
|  | import core.stdc.string; | 
|  | import core.sys.posix.unistd; | 
|  |  | 
|  | void browse(scope const(char)[] url) nothrow @nogc @safe | 
|  | { | 
|  | const buffer = url.tempCString(); // Retain buffer until end of scope | 
|  | const(char)*[3] args; | 
|  |  | 
|  | // Trusted because it's called with a zero-terminated literal | 
|  | const(char)* browser = (() @trusted => core.stdc.stdlib.getenv("BROWSER"))(); | 
|  | if (browser) | 
|  | { | 
|  | // String already zero-terminated | 
|  | browser = (() @trusted => strdup(browser))(); | 
|  | args[0] = browser; | 
|  | } | 
|  | else | 
|  | { | 
|  | version (OSX) | 
|  | { | 
|  | args[0] = "open"; | 
|  | } | 
|  | else | 
|  | { | 
|  | //args[0] = "x-www-browser";  // doesn't work on some systems | 
|  | args[0] = "xdg-open"; | 
|  | } | 
|  | } | 
|  |  | 
|  | args[1] = buffer; | 
|  | args[2] = null; | 
|  |  | 
|  | auto childpid = core.sys.posix.unistd.fork(); | 
|  | if (childpid == 0) | 
|  | { | 
|  | // Trusted because args and all entries are always zero-terminated | 
|  | (() @trusted { | 
|  | core.sys.posix.unistd.execvp(args[0], &args[0]); | 
|  | perror(args[0]); | 
|  | core.sys.posix.unistd._exit(1); | 
|  | })(); | 
|  | assert(0, "Child failed to exec"); | 
|  | } | 
|  | if (browser) | 
|  | // Trusted because it's allocated via strdup above | 
|  | (() @trusted => free(cast(void*) browser))(); | 
|  |  | 
|  | version (StdUnittest) | 
|  | { | 
|  | // Verify that the test script actually suceeds | 
|  | int status; | 
|  | const check = (() @trusted => waitpid(childpid, &status, 0))(); | 
|  | assert(check != -1); | 
|  | assert(status == 0); | 
|  | } | 
|  | } | 
|  | } | 
|  | else | 
|  | static assert(0, "os not supported"); | 
|  |  | 
|  | // Verify attributes are consistent between all implementations | 
|  | @safe @nogc nothrow unittest | 
|  | { | 
|  | if (false) | 
|  | browse(""); | 
|  | } | 
|  |  | 
|  | version (Windows) { /* Doesn't use BROWSER */ } | 
|  | else | 
|  | @system unittest | 
|  | { | 
|  | import std.conv : text; | 
|  | import std.range : repeat; | 
|  | immutable string url = text("http://", repeat('x', 249)); | 
|  |  | 
|  | TestScript prog = `if [ "$1" != "` ~ url ~ `" ]; then exit 1; fi`; | 
|  | environment["BROWSER"] = prog.path; | 
|  | browse(url); | 
|  | } |