phobos/std/process.d
jmdavis 213c064e70 Reverted change from toStringz to toStringZ.
There was no consenus in the newsgroup about what to do about renaming
toStringz to be properly camelcased. It was pretty much divided between
renaming it to toCString and leaving it exactly as-is. No one wanted it
to be toStringZ. So, given the lack of consensus, I'm just going to
leave it as toStringz.
2011-06-18 01:13:21 -07:00

726 lines
19 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)
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 core.stdc.errno;
import std.c.process;
import std.c.string;
import std.conv;
import std.exception;
import std.stdio;
import std.string;
import std.typecons;
version (Windows)
{
import std.array, 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;
}
// 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();
// need to declare environ = *_NSGetEnviron() in static this()
}
else
{
// Made available by the C runtime:
private extern(C) extern __gshared const char** environ;
}
}
version(Windows)
{
// TODO: This should be in core.sys.windows.windows.
alias WCHAR* LPWCH;
extern(Windows)
{
LPWCH GetEnvironmentStringsW();
BOOL FreeEnvironmentStringsW(LPWCH lpszEnvironmentBlock);
DWORD GetEnvironmentVariableW(LPCWSTR lpName, LPWSTR lpBuffer,
DWORD nSize);
BOOL SetEnvironmentVariableW(LPCWSTR lpName, LPCWSTR lpValue);
}
}
/**
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)
return (status & 0x0000ff00) >>> 8;
else
return status;
}
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
{
return std.c.process.spawnvp(mode, toStringz(pathname), argv_);
}
}
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
{
bool stopped(int status) { return cast(bool)((status & 0xff) == 0x7f); }
bool signaled(int status) { return cast(bool)((cast(char)((status & 0x7f) + 1) >> 1) > 0); }
int termsig(int status) { return status & 0x7f; }
bool exited(int status) { return cast(bool)((status & 0x7f) == 0); }
int exitstatus(int status) { return (status & 0xff00) >> 8; }
} // private
} // version (Posix)
/* ========================================================== */
/**
* Execute program specified by pathname, passing it the arguments (argv)
* and the environment (envp), returning the exit status.
* The 'p' versions of exec search the PATH environment variable
* setting for the program.
*/
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.string.split(
to!string(std.c.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());
* ---
*/
version(Posix)
{
alias core.sys.posix.unistd.getpid getpid;
}
else version (Windows)
{
alias std.c.windows.windows.GetCurrentProcessId 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("rm " ~ tempFilename);
}
... use f ...
----
*/
version (Windows) string shell(string cmd)
{
// 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);
errnoEnforce(system(cmd ~ "> " ~ filename) == 0);
return readText(filename);
}
version (Posix) string shell(string cmd)
{
File f;
f.popen(cmd, "r");
char[] line;
string result;
while (f.readln(line))
{
result ~= line;
}
f.close;
return result;
}
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 = std.c.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(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(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
{
// initiaizes the value of environ for OSX
version(OSX)
{
static private char** environ;
static this()
{
environ = * _NSGetEnviron();
}
}
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, 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;
assert (v == environment[n]);
}
}