// Written in the D programming language. /** Macros: WIKI=Phobos/StdProcess Copyright: Copyright Digital Mars 2007 - 2009. License: Boost License 1.0. Authors: $(WEB digitalmars.com, Walter Bright), $(WEB erdani.org, Andrei Alexandrescu), $(WEB thecybershadow.net, Vladimir Panteleev) Source: $(PHOBOSSRC std/_process.d) */ /* Copyright Digital Mars 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) */ module std.process; import core.stdc.stdlib; import std.c.stdlib; import core.stdc.errno; import core.thread; import std.c.process; import std.c.string; import std.array; import std.conv; import std.exception; import std.internal.processinit; import std.stdio; import std.string; import std.typecons; version (Windows) { import std.format, std.random, std.file; import core.sys.windows.windows; import std.utf; import std.windows.syserror; } version (Posix) { import core.sys.posix.stdlib; } version (unittest) { import std.file, std.conv, std.array, std.random; import std.path : absolutePath; } // The following is needed for reading/writing environment variables. version(Posix) { version(OSX) { // https://www.gnu.org/software/gnulib/manual/html_node/environ.html private extern(C) extern __gshared char*** _NSGetEnviron(); __gshared char** environ; // Run in std.__processinit to avoid cyclic construction errors. extern(C) void std_process_static_this() { environ = *_NSGetEnviron(); } } else { // Made available by the C runtime: private extern(C) extern __gshared const char** environ; } } /** Execute $(D command) in a _command shell. Returns: If $(D command) is null, returns nonzero if the _command interpreter is found, and zero otherwise. If $(D command) is not null, returns -1 on error, or the exit status of command (which may in turn signal an error in command's execution). Note: On Unix systems, the homonym C function (which is accessible to D programs as $(LINK2 std_c_process.html, std.c._system)) returns a code in the same format as $(LUCKY waitpid, waitpid), meaning that C programs must use the $(D WEXITSTATUS) macro to extract the actual exit code from the $(D system) call. D's $(D system) automatically extracts the exit status. */ int system(string command) { if (!command) return std.c.process.system(null); const commandz = toStringz(command); immutable status = std.c.process.system(commandz); if (status == -1) return status; version (Posix) { if (exited(status)) return exitstatus(status); // Abnormal termination, return -1. return -1; } else version (Windows) return status; else static assert(0, "system not implemented for this OS."); } private void toAStringz(in string[] a, const(char)**az) { foreach(string s; a) { *az++ = toStringz(s); } *az = null; } /* ========================================================== */ //version (Windows) //{ // int spawnvp(int mode, string pathname, string[] argv) // { // char** argv_ = cast(char**)alloca((char*).sizeof * (1 + argv.length)); // // toAStringz(argv, argv_); // // return std.c.process.spawnvp(mode, toStringz(pathname), argv_); // } //} // Incorporating idea (for spawnvp() on Posix) from Dave Fladebo alias std.c.process._P_WAIT P_WAIT; alias std.c.process._P_NOWAIT P_NOWAIT; int spawnvp(int mode, string pathname, string[] argv) { auto argv_ = cast(const(char)**)alloca((char*).sizeof * (1 + argv.length)); toAStringz(argv, argv_); version (Posix) { return _spawnvp(mode, toStringz(pathname), argv_); } else version (Windows) { return std.c.process.spawnvp(mode, toStringz(pathname), argv_); } else static assert(0, "spawnvp not implemented for this OS."); } version (Posix) { private import core.sys.posix.unistd; private import core.sys.posix.sys.wait; int _spawnvp(int mode, in char *pathname, in char **argv) { int retval = 0; pid_t pid = fork(); if(!pid) { // child std.c.process.execvp(pathname, argv); goto Lerror; } else if(pid > 0) { // parent if(mode == _P_NOWAIT) { retval = pid; // caller waits } else { while(1) { int status; pid_t wpid = waitpid(pid, &status, 0); if(exited(status)) { retval = exitstatus(status); break; } else if(signaled(status)) { retval = -termsig(status); break; } else if(stopped(status)) // ptrace support continue; else goto Lerror; } } return retval; } Lerror: retval = errno; char[80] buf = void; throw new Exception( "Cannot spawn " ~ to!string(pathname) ~ "; " ~ to!string(strerror_r(retval, buf.ptr, buf.length)) ~ " [errno " ~ to!string(retval) ~ "]"); } // _spawnvp private { alias WIFSTOPPED stopped; alias WIFSIGNALED signaled; alias WTERMSIG termsig; alias WIFEXITED exited; alias WEXITSTATUS exitstatus; } // private } // version (Posix) /* ========================================================== */ /** * Replace the current process by executing a command, $(D pathname), with * the arguments in $(D argv). Typically, the first element of $(D argv) is * the command being executed, i.e. $(D argv[0] == pathname). The 'p' * versions of $(D 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. */ int execv(in string pathname, in string[] argv) { auto argv_ = cast(const(char)**)alloca((char*).sizeof * (1 + argv.length)); toAStringz(argv, argv_); return std.c.process.execv(toStringz(pathname), argv_); } /** ditto */ int execve(in string pathname, in string[] argv, in string[] envp) { auto argv_ = cast(const(char)**)alloca((char*).sizeof * (1 + argv.length)); auto envp_ = cast(const(char)**)alloca((char*).sizeof * (1 + envp.length)); toAStringz(argv, argv_); toAStringz(envp, envp_); return std.c.process.execve(toStringz(pathname), argv_, envp_); } /** ditto */ int execvp(in string pathname, in string[] argv) { auto argv_ = cast(const(char)**)alloca((char*).sizeof * (1 + argv.length)); toAStringz(argv, argv_); return std.c.process.execvp(toStringz(pathname), argv_); } /** ditto */ int execvpe(in string pathname, in string[] argv, in string[] envp) { version(Posix) { // 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 = std.array.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) { auto argv_ = cast(const(char)**)alloca((char*).sizeof * (1 + argv.length)); auto envp_ = cast(const(char)**)alloca((char*).sizeof * (1 + envp.length)); toAStringz(argv, argv_); toAStringz(envp, envp_); return std.c.process.execvpe(toStringz(pathname), argv_, envp_); } else { static assert(0); } // version } /** * Returns the process ID of the calling process, which is guaranteed to be * unique on the system. This call is always successful. * * Example: * --- * writefln("Current process id: %s", getpid()); * --- */ alias core.thread.getpid getpid; /** Runs $(D_PARAM cmd) in a shell and returns its standard output. If the process could not be started or exits with an error code, throws an exception. Example: ---- auto tempFilename = chomp(shell("mcookie")); auto f = enforce(fopen(tempFilename), "w"); scope(exit) { fclose(f) == 0 || assert(false); system(escapeShellCommand("rm", tempFilename)); } ... use f ... ---- */ string shell(string cmd) { version(Windows) { // Generate a random filename auto a = appender!string(); foreach (ref e; 0 .. 8) { formattedWrite(a, "%x", rndGen.front); rndGen.popFront(); } auto filename = a.data; scope(exit) if (exists(filename)) remove(filename); // We can't use escapeShellCommands here because we don't know // if cmd is escaped (wrapped in quotes) or not, without relying // on shady heuristics. The current code shouldn't cause much // trouble unless filename contained spaces (it won't). errnoEnforce(system(cmd ~ "> " ~ filename) == 0); return readText(filename); } else version(Posix) { File f; f.popen(cmd, "r"); char[] line; string result; while (f.readln(line)) { result ~= line; } f.close(); return result; } else static assert(0, "shell not implemented for this OS."); } unittest { auto x = shell("echo wyda"); // @@@ This fails on wine //assert(x == "wyda" ~ newline, text(x.length)); } /** Gets the value of environment variable $(D name) as a string. Calls $(LINK2 std_c_stdlib.html#_getenv, std.c.stdlib._getenv) internally. */ string getenv(in char[] name) { // Cache the last call's result static string lastResult; auto p = core.stdc.stdlib.getenv(toStringz(name)); if (!p) return null; auto value = p[0 .. strlen(p)]; if (value == lastResult) return lastResult; return lastResult = value.idup; } /** Sets the value of environment variable $(D name) to $(D value). If the value was written, or the variable was already present and $(D overwrite) is false, returns normally. Otherwise, it throws an exception. Calls $(LINK2 std_c_stdlib.html#_setenv, std.c.stdlib._setenv) internally. */ version(StdDdoc) void setenv(in char[] name, in char[] value, bool overwrite); else version(Posix) void setenv(in char[] name, in char[] value, bool overwrite) { errnoEnforce( std.c.stdlib.setenv(toStringz(name), toStringz(value), overwrite) == 0); } /** Removes variable $(D name) from the environment. Calls $(LINK2 std_c_stdlib.html#_unsetenv, std.c.stdlib._unsetenv) internally. */ version(StdDdoc) void unsetenv(in char[] name); else version(Posix) void unsetenv(in char[] name) { errnoEnforce(std.c.stdlib.unsetenv(toStringz(name)) == 0); } version (Posix) unittest { setenv("wyda", "geeba", true); assert(getenv("wyda") == "geeba"); // Get again to make sure caching works assert(getenv("wyda") == "geeba"); unsetenv("wyda"); assert(getenv("wyda") is null); } /* ////////////////////////////////////////////////////////////////////////// */ version(MainTest) { int main(string[] args) { if(args.length < 2) { printf("Must supply executable (and optional arguments)\n"); return 1; } else { string[] dummy_env; dummy_env ~= "VAL0=value"; dummy_env ~= "VAL1=value"; /+ foreach(string arg; args) { printf("%.*s\n", arg); } +/ // int i = execv(args[1], args[1 .. args.length]); // int i = execvp(args[1], args[1 .. args.length]); int i = execvpe(args[1], args[1 .. args.length], dummy_env); printf("exec??() has returned! Error code: %d; errno: %d\n", i, /* errno */-1); return 0; } } } /* ////////////////////////////////////////////////////////////////////////// */ /** Manipulates environment variables using an associative-array-like interface. Examples: --- // Return variable, or throw an exception if it doesn't exist. auto path = environment["PATH"]; // Add/replace variable. environment["foo"] = "bar"; // Remove variable. environment.remove("foo"); // Return variable, or null if it doesn't exist. auto foo = environment.get("foo"); // Return variable, or a default value if it doesn't exist. auto foo = environment.get("foo", "default foo value"); // Return an associative array of type string[string] containing // all the environment variables. auto aa = environment.toAA(); --- */ alias Environment environment; abstract final class Environment { static: private: // Return the length of an environment variable (in number of // wchars, including the null terminator), 0 if it doesn't exist. version(Windows) int varLength(LPCWSTR namez) { return GetEnvironmentVariableW(namez, null, 0); } // Retrieve the environment variable, or return false on failure. bool getImpl(string name, out string value) { version(Posix) { const vz = core.sys.posix.stdlib.getenv(toStringz(name)); if (vz == null) return false; auto v = vz[0 .. strlen(vz)]; // Cache the last call's result. static string lastResult; if (v != lastResult) lastResult = v.idup; value = lastResult; return true; } else version(Windows) { const namez = toUTF16z(name); immutable len = varLength(namez); if (len == 0) return false; if (len == 1) return true; auto buf = new WCHAR[len]; GetEnvironmentVariableW(namez, buf.ptr, to!DWORD(buf.length)); value = toUTF8(buf[0 .. $-1]); return true; } else static assert(0); } public: // Retrieve an environment variable, throw on failure. string opIndex(string name) { string value; enforce(getImpl(name, value), "Environment variable not found: "~name); return value; } // Assign a value to an environment variable. If the variable // exists, it is overwritten. string opIndexAssign(string value, string name) { version(Posix) { if (core.sys.posix.stdlib.setenv(toStringz(name), toStringz(value), 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) { enforce( SetEnvironmentVariableW(toUTF16z(name), toUTF16z(value)), sysErrorString(GetLastError()) ); return value; } else static assert(0); } // Remove an environment variable. The function succeeds even // if the variable isn't in the environment. void remove(string name) { version(Posix) { core.sys.posix.stdlib.unsetenv(toStringz(name)); } else version(Windows) { SetEnvironmentVariableW(toUTF16z(name), null); } else static assert(0); } // Same as opIndex, except return a default value if // the variable doesn't exist. string get(string name, string defaultValue = null) { string value; auto found = getImpl(name, value); return found ? value : defaultValue; } // Return all environment variables in an associative array. string[string] toAA() { string[string] aa; version(Posix) { for (int i=0; environ[i] != null; ++i) { immutable varDef = to!string(environ[i]); immutable eq = varDef.indexOf('='); 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) { 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] != '=') { assert (envBlock[i] != '\0'); ++i; } immutable name = toUTF8(envBlock[start .. i]); start = i+1; while (envBlock[i] != '\0') ++i; aa[name] = toUTF8(envBlock[start .. i]); } } else static assert(0); return aa; } } unittest { // New variable environment["std_process"] = "foo"; assert (environment["std_process"] == "foo"); // Set variable again environment["std_process"] = "bar"; assert (environment["std_process"] == "bar"); // Remove variable environment.remove("std_process"); // Remove again, should succeed environment.remove("std_process"); // Throw on not found. try { environment["std_process"]; assert(0); } catch(Exception e) { } // get() without default value assert (environment.get("std.process") == null); // get() with default value assert (environment.get("std_process", "baz") == "baz"); // 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. // - If an env. variable has zero length, i.e. is "\0", // GetEnvironmentVariable should return 1. Instead it returns // 0, indicating the variable doesn't exist. version(Windows) if (n.length == 0 || v.length == 0) continue; // why does this happen? // n = "temp" || "tmp" // v = "C:\Users\ADMINI~1\AppData\Local\Temp\2" // e[n] = "C:\cygwin\tmp" // for n = "TEMP" or "TMP", v and en[v] are both "C:\cygwin\tmp" version(Windows) if (n == "temp" || n == "tmp") continue; //printf("%.*s, %.*s, %.*s\n", n.length, n.ptr, v.length, v.ptr, environment[n].length, environment[n].ptr); assert (v == environment[n]); } } version(StdDdoc) { /**************************************** * Start up the browser and set it to viewing the page at url. */ void browse(string url); } else version (Windows) { import core.sys.windows.windows; extern (Windows) HINSTANCE ShellExecuteA(HWND hwnd, LPCSTR lpOperation, LPCSTR lpFile, LPCSTR lpParameters, LPCSTR lpDirectory, INT nShowCmd); pragma(lib,"shell32.lib"); void browse(string url) { ShellExecuteA(null, "open", toStringz(url), null, null, SW_SHOWNORMAL); } } else version (OSX) { import core.stdc.stdio; import core.stdc.string; import core.sys.posix.unistd; void browse(string url) { const(char)*[5] args; const(char)* browser = core.stdc.stdlib.getenv("BROWSER"); if (browser) { browser = strdup(browser); args[0] = browser; args[1] = toStringz(url); args[2] = null; } else { args[0] = "open".ptr; args[1] = toStringz(url); args[2] = null; } auto childpid = fork(); if (childpid == 0) { core.sys.posix.unistd.execvp(args[0], cast(char**)args.ptr); perror(args[0]); // failed to execute return; } if (browser) free(cast(void*)browser); } } else version (Posix) { import core.stdc.stdio; import core.stdc.string; import core.sys.posix.unistd; void browse(string url) { const(char)*[3] args; const(char)* browser = core.stdc.stdlib.getenv("BROWSER"); if (browser) { browser = strdup(browser); args[0] = browser; } else //args[0] = "x-www-browser".ptr; // doesn't work on some systems args[0] = "xdg-open".ptr; args[1] = toStringz(url); args[2] = null; auto childpid = fork(); if (childpid == 0) { core.sys.posix.unistd.execvp(args[0], cast(char**)args.ptr); perror(args[0]); // failed to execute return; } if (browser) free(cast(void*)browser); } } else static assert(0, "os not supported"); /* ////////////////////////////////////////////////////////////////////////// */ /* 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. */ pure @safe nothrow private char[] charAllocator(size_t size) { return new char[size]; } /** Quote an 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). */ pure nothrow string escapeWindowsArgument(in char[] arg) { // Rationale for leaving this function as public: // this algorithm of escaping paths is also used in other software, // e.g. DMD's response files. auto buf = escapeWindowsArgumentImpl!charAllocator(arg); return assumeUnique(buf); } @safe nothrow private char[] escapeWindowsArgumentImpl(alias allocator)(in char[] arg) 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 // Calculate the total string size. // Trailing backslashes must be escaped bool escaping = true; // 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 (c; arg) { if (c == '"') { escaping = true; size++; } else if (c == '\\') { if (escaping) size++; } else escaping = false; } // Construct result string. auto buf = allocator(size); size_t p = size; buf[--p] = '"'; escaping = true; foreach_reverse (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(unittest) { import core.sys.windows.windows; import core.stdc.stddef; extern (Windows) wchar_t** CommandLineToArgvW(wchar_t*, int*); extern (C) size_t wcslen(in wchar *); unittest { string[] testStrings = [ `Hello`, `Hello, world`, `Hello, "world"`, `C:\`, `C:\dmd`, `C:\Program Files\`, ]; enum CHARS = `_x\" *&^`; // _ 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); LPWSTR lpCommandLine = (to!(wchar[])("Dummy.exe " ~ q) ~ "\0"w).ptr; int numArgs; LPWSTR* args = CommandLineToArgvW(lpCommandLine, &numArgs); scope(exit) LocalFree(args); assert(numArgs==2, s ~ " => " ~ q ~ " #" ~ text(numArgs-1)); auto arg = to!string(args[1][0..wcslen(args[1])]); assert(arg == s, s ~ " => " ~ q ~ " => " ~ arg); } } } pure nothrow private string escapePosixArgument(in char[] arg) { auto buf = escapePosixArgumentImpl!charAllocator(arg); return assumeUnique(buf); } @safe nothrow private char[] escapePosixArgumentImpl(alias allocator)(in char[] arg) if (is(typeof(allocator(size_t.init)[0] = char.init))) { // '\'' 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 (c; arg) if (c == '\'') size += 3; auto buf = allocator(size); size_t p = 0; buf[p++] = '\''; foreach (c; arg) if (c == '\'') { buf[p..p+4] = `'\''`; p += 4; } else buf[p++] = c; buf[p++] = '\''; assert(p == size); return buf; } @safe nothrow private auto escapeShellArgument(alias allocator)(in char[] arg) { // The unittest for this function requires special // preparation - see below. version (Windows) return escapeWindowsArgumentImpl!allocator(arg); else return escapePosixArgumentImpl!allocator(arg); } pure nothrow private string escapeShellArguments(in char[][] args) { 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); } string escapeWindowsShellCommand(in char[] command) { auto result = appender!string(); result.reserve(command.length); foreach (c; command) switch (c) { case '\0': assert(0, "Cannot put NUL in command line"); case '\r': case '\n': assert(0, "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 escapeShellCommandString(string command) { version (Windows) return escapeWindowsShellCommand(command); else return command; } /** Escape an argv-style argument array to be used with the $(D system) or $(D shell) functions. Example: --- string url = "http://dlang.org/"; system(escapeShellCommand("wget", url, "-O", "dlang-index.html")); --- Concatenate multiple $(D escapeShellCommand) and $(D escapeShellFileName) results to use shell redirection or piping operators. Example: --- system( escapeShellCommand("curl", "http://dlang.org/download.html") ~ "|" ~ escapeShellCommand("grep", "-o", `http://\S*\.zip`) ~ ">" ~ escapeShellFileName("D download links.txt")); --- */ string escapeShellCommand(in char[][] args...) { return escapeShellCommandString(escapeShellArguments(args)); } /** Escape a filename to be used for shell redirection with the $(D system) or $(D shell) functions. */ pure nothrow string escapeShellFileName(in char[] fn) { // The unittest for this function requires special // preparation - see below. version (Windows) return cast(string)('"' ~ fn ~ '"'); else return escapePosixArgument(fn); } // Loop generating strings with random characters //version = unittest_burnin; version(unittest_burnin) 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 auto helper = absolutePath("std_process_unittest_helper"); assert(shell(helper ~ " hello").split("\0")[1..$] == ["hello"], "Helper malfunction"); void test(string[] s, string fn) { string e; string[] g; e = escapeShellCommand(helper ~ s); { scope(failure) writefln("shell() failed.\nExpected:\t%s\nEncoded:\t%s", s, [e]); g = shell(e).split("\0")[1..$]; } assert(s == g, format("shell() test failed.\nExpected:\t%s\nGot:\t\t%s\nEncoded:\t%s", s, g, [e])); e = escapeShellCommand(helper ~ s) ~ ">" ~ escapeShellFileName(fn); { scope(failure) writefln("system() failed.\nExpected:\t%s\nFilename:\t%s\nEncoded:\t%s", s, [fn], [e]); system(e); g = readText(fn).split("\0")[1..$]; } remove(fn); assert(s == g, format("system() 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 = "test_"; 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; } test(args, fn); } }