mirror of
https://github.com/dlang/phobos.git
synced 2025-04-27 13:40:20 +03:00
1242 lines
33 KiB
D
1242 lines
33 KiB
D
// Written in the D programming language.
|
|
|
|
/**
|
|
Macros:
|
|
|
|
WIKI=Phobos/StdProcess
|
|
|
|
Copyright: Copyright Digital Mars 2007 - 2009.
|
|
License: <a href="http://www.boost.org/LICENSE_1_0.txt">Boost License 1.0</a>.
|
|
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 ErrnoException.
|
|
|
|
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));
|
|
|
|
import std.exception; // Issue 9444
|
|
assertThrown!ErrnoException(shell("qwertyuiop09813478"));
|
|
}
|
|
|
|
/**
|
|
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);
|
|
}
|
|
}
|