123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139 |
|
/*******************************************************************************
copyright: Copyright (c) 2006 Juan Jose Comellas. All rights reserved
license: BSD style: $(LICENSE)
author: Juan Jose Comellas <juanjo@comellas.com.ar>
*******************************************************************************/
module tango.sys.Process;
private import tango.io.model.IFile;
private import tango.io.Console;
private import tango.sys.Common;
private import tango.sys.Pipe;
private import tango.core.Exception;
private import tango.text.Util;
private import Integer = tango.text.convert.Integer;
private import tango.stdc.stdlib;
private import tango.stdc.string;
private import tango.stdc.stringz;
version (Posix)
{
private import tango.stdc.errno;
private import tango.stdc.posix.fcntl;
private import tango.stdc.posix.unistd;
private import tango.stdc.posix.sys.wait;
version (darwin)
{
extern (C) char*** _NSGetEnviron();
private __gshared char** environ;
shared static this ()
{
environ = *_NSGetEnviron();
}
}
else
private extern (C) extern __gshared char** environ;
}
version (Windows)
{
version (Win32SansUnicode)
{
}
else
{
private import tango.text.convert.Utf : toString16;
}
}
debug (Process)
{
private import tango.io.Stdout;
}
/**
* Redirect flags for processes. Defined outside process class to cut down on
* verbosity.
*/
enum Redirect
{
/**
* Redirect none of the standard handles
*/
None = 0,
/**
* Redirect the stdout handle to a pipe.
*/
Output = 1,
/**
* Redirect the stderr handle to a pipe.
*/
Error = 2,
/**
* Redirect the stdin handle to a pipe.
*/
Input = 4,
/**
* Redirect all three handles to pipes (default).
*/
All = Output | Error | Input,
/**
* Send stderr to stdout's handle. Note that the stderr PipeConduit will
* be null.
*/
ErrorToOutput = 0x10,
/**
* Send stdout to stderr's handle. Note that the stdout PipeConduit will
* be null.
*/
OutputToError = 0x20,
}
/**
* The Process class is used to start external programs and communicate with
* them via their standard input, output and error streams.
*
* You can pass either the command line or an array of arguments to execute,
* either in the constructor or to the args property. The environment
* variables can be set in a similar way using the env property and you can
* set the program's working directory via the workDir property.
*
* To actually start a process you need to use the execute() method. Once the
* program is running you will be able to write to its standard input via the
* stdin OutputStream and you will be able to read from its standard output and
* error through the stdout and stderr InputStream respectively.
*
* You can check whether the process is running or not with the isRunning()
* method and you can get its process ID via the pid property.
*
* After you are done with the process, or if you just want to wait for it to
* end, you need to call the wait() method which will return once the process
* is no longer running.
*
* To stop a running process you must use kill() method. If you do this you
* cannot call the wait() method. Once the kill() method returns the process
* will be already dead.
*
* After calling either wait() or kill(), and no more data is expected on the
* pipes, you should call close() as this will clean the pipes. Not doing this
* may lead to a depletion of the available file descriptors for the main
* process if many processes are created.
*
* Examples:
* ---
* try
* {
* auto p = new Process ("ls -al", null);
* p.execute;
*
* Stdout.formatln ("Output from {}:", p.programName);
* Stdout.copy (p.stdout).flush;
* auto result = p.wait;
*
* Stdout.formatln ("Process '{}' ({}) exited with reason {}, status {}",
* p.programName, p.pid, cast(int) result.reason, result.status);
* }
* catch (ProcessException e)
* Stdout.formatln ("Process execution failed: {}", e);
* ---
*/
class Process
{
/**
* Result returned by wait().
*/
public struct Result
{
/**
* Reasons returned by wait() indicating why the process is no
* longer running.
*/
public enum
{
Exit,
Signal,
Stop,
Continue,
Error
}
public int reason;
public int status;
/**
* Returns a string with a description of the process execution result.
*/
public immutable(char)[] toString()
{
const(char)[] str;
switch (reason)
{
case Exit:
str = format("Process exited normally with return code ", status);
break;
case Signal:
str = format("Process was killed with signal ", status);
break;
case Stop:
str = format("Process was stopped with signal ", status);
break;
case Continue:
str = format("Process was resumed with signal ", status);
break;
case Error:
str = format("Process failed with error code ", reason) ~
" : " ~ SysError.lookup(status);
break;
default:
str = format("Unknown process result ", reason);
break;
}
return cast(immutable(char)[])str;
}
}
enum uint DefaultStdinBufferSize = 512;
enum uint DefaultStdoutBufferSize = 8192;
enum uint DefaultStderrBufferSize = 512;
enum Redirect DefaultRedirectFlags = Redirect.All;
private const(char[])[] _args;
private const(char)[] _program;
private const(char[])[char[]] _env;
private const(char)[] _workDir;
private PipeConduit _stdin;
private PipeConduit _stdout;
private PipeConduit _stderr;
private bool _running = false;
private bool _copyEnv = false;
private Redirect _redirect = DefaultRedirectFlags;
version (Windows)
{
private PROCESS_INFORMATION *_info = null;
private bool _gui = false;
}
else
{
private pid_t _pid = cast(pid_t) -1;
}
/**
* Constructor (variadic version). Note that by default, the environment
* will not be copied.
*
* Params:
* args = array of strings with the process' arguments. If there is
* exactly one argument, it is considered to contain the entire
* command line including parameters. If you pass only one
* argument, spaces that are not intended to separate
* parameters should be embedded in quotes. The arguments can
* also be empty.
* Note: The class will use only slices, .dup when necessary.
*
* Examples:
* ---
* auto p = new Process("myprogram", "first argument", "second", "third");
* auto p = new Process("myprogram \"first argument\" second third");
* ---
*/
public this(const(char[])[] args ...)
{
if(args.length == 1)
_args = splitArgs(args[0]);
else
_args = args;
if(_args.length > 0)
{
_program = _args[0];
_args = _args[1..$];
}
}
/**
* Constructor (variadic version, with environment copy).
*
* Params:
* copyEnv = if true, the environment is copied from the current process.
* args = array of strings with the process' arguments. If there is
* exactly one argument, it is considered to contain the entire
* command line including parameters. If you pass only one
* argument, spaces that are not intended to separate
* parameters should be embedded in quotes. The arguments can
* also be empty.
* Note: The class will use only slices, .dup when necessary.
*
* Examples:
* ---
* auto p = new Process(true, "myprogram", "first argument", "second", "third");
* auto p = new Process(true, "myprogram \"first argument\" second third");
* ---
*/
public this(bool copyEnv, const(char[])[] args ...)
{
_copyEnv = copyEnv;
this(args);
}
/**
* Constructor.
*
* Params:
* command = string with the process' command line; arguments that have
* embedded whitespace must be enclosed in inside double-quotes (").
* Note: The class will use only slices, .dup when necessary.
* env = associative array of strings with the process' environment
* variables; the variable name must be the key of each entry.
*
* Examples:
* ---
* char[] command = "myprogram \"first argument\" second third";
* char[][char[]] env;
*
* // Environment variables
* env["MYVAR1"] = "first";
* env["MYVAR2"] = "second";
*
* auto p = new Process(command, env)
* ---
*/
public this(const(char)[] command, const(char[])[char[]] env)
in
{
assert(command.length > 0);
}
body
{
_args = splitArgs(command);
_env = env;
if(_args.length > 0)
{
_program = _args[0];
_args = _args[1..$];
}
}
/**
* Constructor.
*
* Params:
* args = array of strings with the process' arguments; the first
* argument must be the process' name; the arguments can be
* empty.
* Note: The class will use only slices, .dup when necessary.
* env = associative array of strings with the process' environment
* variables; the variable name must be the key of each entry.
*
* Examples:
* ---
* char[][] args;
* char[][char[]] env;
*
* // Process name
* args ~= "myprogram";
* // Process arguments
* args ~= "first argument";
* args ~= "second";
* args ~= "third";
*
* // Environment variables
* env["MYVAR1"] = "first";
* env["MYVAR2"] = "second";
*
* auto p = new Process(args, env)
* ---
*/
public this(const(char[])[] args, const(char[])[char[]] env)
in
{
assert(args.length > 0);
assert(args[0].length > 0);
}
body
{
_args = args;
_env = env;
if(_args.length > 0)
{
_program = _args[0];
_args = _args[1..$];
}
}
/**
* Indicate whether the process is running or not.
*/
public const bool isRunning()
{
return _running;
}
/**
* Return the running process' ID.
*
* Returns: an int with the process ID if the process is running;
* -1 if not.
*/
@property
public const int pid()
{
version (Windows)
{
return (_info !is null ? cast(int) _info.dwProcessId : -1);
}
else // version (Posix)
{
return cast(int) _pid;
}
}
/**
* Return the process' executable filename.
*/
@property
public const const(char)[] programName()
{
return _program;
}
/**
* Set the process' executable filename.
*/
@property
public const(char)[] programName(const(char)[] name)
{
return _program = name;
}
/**
* Set the process' executable filename, return 'this' for chaining
*/
public Process setProgramName(const(char)[] name)
{
programName = name;
return this;
}
/**
* Return an array with the process' arguments. This does not include the actual program name.
*/
public
const(char[])[] args() const
{
return _args;
}
/**
* Set the process' arguments from the arguments received by the method.
*
* Remarks:
* The first element of the array must be the name of the process'
* executable.
*
* Returns: the arguments that were set. This doesn't include the progname.
*
* Examples:
* ---
* p.args("myprogram", "first", "second argument", "third");
* ---
*/
public const(char[])[] args(const(char)[] progname, const(char[])[] args ...)
{
_program = progname;
return _args = args;
}
/**
* Set the process' arguments from the arguments received by the method.
*
* Remarks:
* The first element of the array must be the name of the process'
* executable.
*
* Returns: a reference to this for chaining
*
* Examples:
* ---
* p.setArgs("myprogram", "first", "second argument", "third").execute();
* ---
*/
public Process setArgs(const(char)[] progname, const(char[])[] args ...)
{
this.args(progname, args);
return this;
}
/**
* If true, the environment from the current process will be copied to the
* child process.
*/
@property
public const bool copyEnv()
{
return _copyEnv;
}
/**
* Set the copyEnv flag. If set to true, then the environment will be
* copied from the current process. If set to false, then the environment
* is set from the env field.
*/
@property
public bool copyEnv(bool b)
{
return _copyEnv = b;
}
/**
* Set the copyEnv flag. If set to true, then the environment will be
* copied from the current process. If set to false, then the environment
* is set from the env field.
*
* Returns:
* A reference to this for chaining
*/
public Process setCopyEnv(bool b)
{
_copyEnv = b;
return this;
}
/**
* Return an associative array with the process' environment variables.
*
* Note that if copyEnv is set to true, this value is ignored.
*/
@property
public const(const(char[])[char[]]) env() const
{
return _env;
}
/**
* Set the process' environment variables from the associative array
* received by the method.
*
* This also clears the copyEnv flag.
*
* Params:
* env = associative array of strings containing the environment
* variables for the process. The variable name should be the key
* used for each entry.
*
* Returns: the env set.
* Examples:
* ---
* char[][char[]] env;
*
* env["MYVAR1"] = "first";
* env["MYVAR2"] = "second";
*
* p.env = env;
* ---
*/
@property
public const(char[])[char[]] env(const(char[])[char[]] env)
{
_copyEnv = false;
return _env = env;
}
/**
* Set the process' environment variables from the associative array
* received by the method. Returns a 'this' reference for chaining.
*
* This also clears the copyEnv flag.
*
* Params:
* env = associative array of strings containing the environment
* variables for the process. The variable name should be the key
* used for each entry.
*
* Returns: A reference to this process object
* Examples:
* ---
* char[][char[]] env;
*
* env["MYVAR1"] = "first";
* env["MYVAR2"] = "second";
*
* p.setEnv(env).execute();
* ---
*/
public Process setEnv(const(char[])[char[]] env)
{
_copyEnv = false;
_env = env;
return this;
}
/**
* Return an UTF-8 string with the process' command line.
*/
public override
string toString()
{
immutable(char)[] command;
command ~= _program.substitute("\\", "\\\\").substitute(`"`, `\"`);
for (size_t i = 0; i < _args.length; ++i)
{
if (i > 0)
{
command ~= ' ';
}
if (contains(_args[i], ' ') || _args[i].length == 0)
{
command ~= '"';
command ~= _args[i].substitute("\\", "\\\\").substitute(`"`, `\"`);
command ~= '"';
}
else
{
command ~= _args[i].substitute("\\", "\\\\").substitute(`"`, `\"`);
}
}
return command;
}
/**
* Return the working directory for the process.
*
* Returns: a string with the working directory; null if the working
* directory is the current directory.
*/
@property
public const(char)[] workDir() const
{
return _workDir;
}
/**
* Set the working directory for the process.
*
* Params:
* dir = a string with the working directory; null if the working
* directory is the current directory.
*
* Returns: the directory set.
*/
@property
public const(char)[] workDir(const(char)[] dir)
{
return _workDir = dir;
}
/**
* Set the working directory for the process. Returns a 'this' reference
* for chaining
*
* Params:
* dir = a string with the working directory; null if the working
* directory is the current directory.
*
* Returns: a reference to this process.
*/
public Process setWorkDir(const(char)[] dir)
{
_workDir = dir;
return this;
}
/**
* Get the redirect flags for the process.
*
* The redirect flags are used to determine whether stdout, stderr, or
* stdin are redirected. The flags are an or'd combination of which
* standard handles to redirect. A redirected handle creates a pipe,
* whereas a non-redirected handle simply points to the same handle this
* process is pointing to.
*
* You can also redirect stdout or stderr to each other. The flags to
* redirect a handle to a pipe and to redirect it to another handle are
* mutually exclusive. In the case both are specified, the redirect to
* the other handle takes precedent. It is illegal to specify both
* redirection from stdout to stderr and from stderr to stdout. If both
* of these are specified, an exception is thrown.
*
* If redirected to a pipe, once the process is executed successfully, its
* input and output can be manipulated through the stdin, stdout and
* stderr member PipeConduit's. Note that if you redirect for example
* stderr to stdout, and you redirect stdout to a pipe, only stdout will
* be non-null.
*/
@property public const Redirect redirect()
{
return _redirect;
}
/**
* Set the redirect flags for the process.
*/
@property public Redirect redirect(Redirect flags)
{
return _redirect = flags;
}
/**
* Set the redirect flags for the process. Return a reference to this
* process for chaining.
*/
public Process setRedirect(Redirect flags)
{
_redirect = flags;
return this;
}
/**
* Get the GUI flag.
*
* This flag indicates on Windows systems that the CREATE_NO_WINDOW flag
* should be set on CreateProcess. Although this is a specific windows
* flag, it is present on posix systems as a noop for compatibility.
*
* Without this flag, a console window will be allocated if it doesn't
* already exist.
*/
@property
public bool gui() const
{
version(Windows)
return _gui;
else
return false;
}
/**
* Set the GUI flag.
*
* This flag indicates on Windows systems that the CREATE_NO_WINDOW flag
* should be set on CreateProcess. Although this is a specific windows
* flag, it is present on posix systems as a noop for compatibility.
*
* Without this flag, a console window will be allocated if it doesn't
* already exist.
*/
@property
public bool gui(bool value)
{
version(Windows)
return _gui = value;
else
return false;
}
/**
* Set the GUI flag. Returns a reference to this process for chaining.
*
* This flag indicates on Windows systems that the CREATE_NO_WINDOW flag
* should be set on CreateProcess. Although this is a specific windows
* flag, it is present on posix systems as a noop for compatibility.
*
* Without this flag, a console window will be allocated if it doesn't
* already exist.
*/
public Process setGui(bool value)
{
version(Windows)
{
_gui = value;
}
return this;
}
/**
* Return the running process' standard input pipe.
*
* Returns: a write-only PipeConduit connected to the child
* process' stdin.
*
* Remarks:
* The stream will be null if no child process has been executed, or the
* standard input stream was not redirected.
*/
@property public PipeConduit stdin()
{
return _stdin;
}
/**
* Return the running process' standard output pipe.
*
* Returns: a read-only PipeConduit connected to the child
* process' stdout.
*
* Remarks:
* The stream will be null if no child process has been executed, or the
* standard output stream was not redirected.
*/
@property public PipeConduit stdout()
{
return _stdout;
}
/**
* Return the running process' standard error pipe.
*
* Returns: a read-only PipeConduit connected to the child
* process' stderr.
*
* Remarks:
* The stream will be null if no child process has been executed, or the
* standard error stream was not redirected.
*/
@property public PipeConduit stderr()
{
return _stderr;
}
/**
* Execute a process using the arguments as parameters to this method.
*
* Once the process is executed successfully, its input and output can be
* manipulated through the stdin, stdout and
* stderr member PipeConduit's.
*
* Throws:
* ProcessCreateException if the process could not be created
* successfully; ProcessForkException if the call to the fork()
* system call failed (on POSIX-compatible platforms).
*
* Remarks:
* The process must not be running and the provided list of arguments must
* not be empty. If there was any argument already present in the args
* member, they will be replaced by the arguments supplied to the method.
*
* Deprecated: Use constructor or properties to set up process for
* execution.
*/
deprecated public void execute(const(char)[] arg1, const(char[])[] args ...)
in
{
assert(!_running);
}
body
{
this._program = arg1;
this._args = args;
execute();
}
/**
* Execute a process using the command line arguments as parameters to
* this method.
*
* Once the process is executed successfully, its input and output can be
* manipulated through the stdin, stdout and
* stderr member PipeConduit's.
*
* This also clears the copyEnv flag
*
* Params:
* command = string with the process' command line; arguments that have
* embedded whitespace must be enclosed in inside double-quotes (").
* env = associative array of strings with the process' environment
* variables; the variable name must be the key of each entry.
*
* Throws:
* ProcessCreateException if the process could not be created
* successfully; ProcessForkException if the call to the fork()
* system call failed (on POSIX-compatible platforms).
*
* Remarks:
* The process must not be running and the provided list of arguments must
* not be empty. If there was any argument already present in the args
* member, they will be replaced by the arguments supplied to the method.
*
* Deprecated: use properties or the constructor to set these parameters
* instead.
*/
deprecated public void execute(const(char)[] command, const(char[])[const(char)[]] env)
in
{
assert(!_running);
assert(command.length > 0);
}
body
{
_args = splitArgs(command);
if (_args.length > 0)
{
_program = _args[0];
_args = _args[1..$];
}
_copyEnv = false;
_env = env;
execute();
}
/**
* Execute a process using the command line arguments as parameters to
* this method.
*
* Once the process is executed successfully, its input and output can be
* manipulated through the stdin, stdout and
* stderr member PipeConduit's.
*
* This also clears the copyEnv flag
*
* Params:
* args = array of strings with the process' arguments; the first
* argument must be the process' name; the arguments can be
* empty.
* env = associative array of strings with the process' environment
* variables; the variable name must be the key of each entry.
*
* Throws:
* ProcessCreateException if the process could not be created
* successfully; ProcessForkException if the call to the fork()
* system call failed (on POSIX-compatible platforms).
*
* Remarks:
* The process must not be running and the provided list of arguments must
* not be empty. If there was any argument already present in the args
* member, they will be replaced by the arguments supplied to the method.
*
* Deprecated:
* Use properties or the constructor to set these parameters instead.
*
* Examples:
* ---
* auto p = new Process();
* char[][] args;
*
* args ~= "ls";
* args ~= "-l";
*
* p.execute(args, null);
* ---
*/
deprecated public void execute(const(char[])[] args, const(char[])[char[]] env)
in
{
assert(!_running);
assert(args.length > 0);
}
body
{
_args = args;
if (_args.length > 0)
{
_program = _args[0];
_args = _args[1..$];
}
_env = env;
_copyEnv = false;
execute();
}
/**
* Execute a process using the arguments that were supplied to the
* constructor or to the args property.
*
* Once the process is executed successfully, its input and output can be
* manipulated through the stdin, stdout and
* stderr member PipeConduit's.
*
* Returns:
* A reference to this process object for chaining.
*
* Throws:
* ProcessCreateException if the process could not be created
* successfully; ProcessForkException if the call to the fork()
* system call failed (on POSIX-compatible platforms).
*
* Remarks:
* The process must not be running and the list of arguments must
* not be empty before calling this method.
*/
public Process execute()
in
{
assert(!_running);
assert(_program !is null);
}
body
{
version (Windows)
{
SECURITY_ATTRIBUTES sa;
STARTUPINFO startup;
// We close and delete the pipes that could have been left open
// from a previous execution.
cleanPipes();
// Set up the security attributes struct.
sa.nLength = SECURITY_ATTRIBUTES.sizeof;
sa.lpSecurityDescriptor = null;
sa.bInheritHandle = true;
// Set up members of the STARTUPINFO structure.
memset(&startup, '\0', STARTUPINFO.sizeof);
startup.cb = STARTUPINFO.sizeof;
Pipe pin, pout, perr;
if(_redirect != Redirect.None)
{
if((_redirect & (Redirect.OutputToError | Redirect.ErrorToOutput)) == (Redirect.OutputToError | Redirect.ErrorToOutput))
throw new ProcessCreateException(_program, "Illegal redirection flags", __FILE__, __LINE__);
//
// some redirection is specified, set the flag that indicates
startup.dwFlags |= STARTF_USESTDHANDLES;
// Create the pipes used to communicate with the child process.
if(_redirect & Redirect.Input)
{
pin = new Pipe(DefaultStdinBufferSize, &sa);
// Replace stdin with the "read" pipe
_stdin = pin.sink;
startup.hStdInput = cast(HANDLE) pin.source.fileHandle();
// Ensure the write handle to the pipe for STDIN is not inherited.
SetHandleInformation(cast(HANDLE) pin.sink.fileHandle(), HANDLE_FLAG_INHERIT, 0);
}
else
{
// need to get the local process stdin handle
startup.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
}
if((_redirect & (Redirect.Output | Redirect.OutputToError)) == Redirect.Output)
{
pout = new Pipe(DefaultStdoutBufferSize, &sa);
// Replace stdout with the "write" pipe
_stdout = pout.source;
startup.hStdOutput = cast(HANDLE) pout.sink.fileHandle();
// Ensure the read handle to the pipe for STDOUT is not inherited.
SetHandleInformation(cast(HANDLE) pout.source.fileHandle(), HANDLE_FLAG_INHERIT, 0);
}
else
{
// need to get the local process stdout handle
startup.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
}
if((_redirect & (Redirect.Error | Redirect.ErrorToOutput)) == Redirect.Error)
{
perr = new Pipe(DefaultStderrBufferSize, &sa);
// Replace stderr with the "write" pipe
_stderr = perr.source;
startup.hStdError = cast(HANDLE) perr.sink.fileHandle();
// Ensure the read handle to the pipe for STDOUT is not inherited.
SetHandleInformation(cast(HANDLE) perr.source.fileHandle(), HANDLE_FLAG_INHERIT, 0);
}
else
{
// need to get the local process stderr handle
startup.hStdError = GetStdHandle(STD_ERROR_HANDLE);
}
// do redirection from one handle to another
if(_redirect & Redirect.ErrorToOutput)
{
startup.hStdError = startup.hStdOutput;
}
if(_redirect & Redirect.OutputToError)
{
startup.hStdOutput = startup.hStdError;
}
}
// close the unused end of the pipes on scope exit
scope(exit)
{
if(pin !is null)
pin.source.close();
if(pout !is null)
pout.sink.close();
if(perr !is null)
perr.sink.close();
}
_info = new PROCESS_INFORMATION;
// Set up members of the PROCESS_INFORMATION structure.
memset(_info, '\0', PROCESS_INFORMATION.sizeof);
/*
* quotes and backslashes in the command line are handled very
* strangely by Windows. Through trial and error, I believe that
* these are the rules:
*
* inside or outside quote mode:
* 1. if 2 or more backslashes are followed by a quote, the first
* 2 backslashes are reduced to 1 backslash which does not
* affect anything after it.
* 2. one backslash followed by a quote is interpreted as a
* literal quote, which cannot be used to close quote mode, and
* does not affect anything after it.
*
* outside quote mode:
* 3. a quote enters quote mode
* 4. whitespace delineates an argument
*
* inside quote mode:
* 5. 2 quotes sequentially are interpreted as a literal quote and
* an exit from quote mode.
* 6. a quote at the end of the string, or one that is followed by
* anything other than a quote exits quote mode, but does not
* affect the character after the quote.
* 7. end of line exits quote mode
*
* In our 'reverse' routine, we will only utilize the first 2 rules
* for escapes.
*/
char[] command;
void append_arg(const(char)[] arg)
{
const(char)[] nextarg = arg.substitute(`"`, `\"`);
//
// find all instances where \\" occurs, and double all the
// backslashes. Otherwise, it will fall under rule 1, and those
// backslashes will be halved.
//
uint pos = 0;
while((pos = nextarg.locatePattern(`\\"`, pos)) < nextarg.length)
{
//
// move back until we have all the backslashes
//
uint afterback = pos+1;
while(pos > 0 && nextarg[pos - 1] == '\\')
pos--;
//
// double the number of backslashes that do not escape the
// quote
//
nextarg = nextarg[0..afterback] ~ nextarg[pos..$];
pos = afterback + afterback - pos + 2;
}
//
// check to see if we need to surround the arg with quotes.
//
if(nextarg.length == 0)
{
nextarg = `""`;
}
else if(nextarg.contains(' '))
{
//
// surround with quotes, but if the arg ends in backslashes,
// we must double all the backslashes, or they will fall under
// rule 1 and be halved.
//
if(nextarg[$-1] == '\\')
{
//
// ends in a backslash. count all the \'s at the end of the
// string, and repeat them
//
pos = nextarg.length - 1;
while(pos > 0 && nextarg[pos-1] == '\\')
pos--;
nextarg ~= nextarg[pos..$];
}
// surround the argument with quotes
nextarg = '"' ~ nextarg ~ '"';
}
command ~= ' ';
command ~= nextarg;
}
append_arg(_program);
foreach(a; _args)
append_arg(a);
command ~= '\0';
command = command[1..$];
// old way
//char[] command = toString();
//command ~= '\0';
version(Win32SansUnicode)
{
//
// ASCII version of CreateProcess
//
// Convert the working directory to a null-ended string if
// necessary.
//
// Note, this used to contain DETACHED_PROCESS, but
// this causes problems with redirection if the program being
// started decides to allocate a console (i.e. if you run a batch
// file)
if (CreateProcessA(null, command.ptr, null, null, true,
_gui ? CREATE_NO_WINDOW : 0,
(_copyEnv ? null : toNullEndedBuffer(_env).ptr),
toStringz(_workDir), &startup, _info))
{
CloseHandle(_info.hThread);
_running = true;
}
else
{
throw new ProcessCreateException(_program, __FILE__, __LINE__);
}
}
else
{
// Convert the working directory to a null-ended string if
// necessary.
//
// Note, this used to contain DETACHED_PROCESS, but
// this causes problems with redirection if the program being
// started decides to allocate a console (i.e. if you run a batch
// file)
if (CreateProcessW(null, toString16(command).ptr, null, null, true,
_gui ? CREATE_NO_WINDOW : 0,
(_copyEnv ? null : toNullEndedBuffer(_env).ptr),
toString16z(toString16(_workDir)), &startup, _info))
{
CloseHandle(_info.hThread);
_running = true;
}
else
{
throw new ProcessCreateException(_program, __FILE__, __LINE__);
}
}
}
else version (Posix)
{
// We close and delete the pipes that could have been left open
// from a previous execution.
cleanPipes();
// validate the redirection flags
if((_redirect & (Redirect.OutputToError | Redirect.ErrorToOutput)) == (Redirect.OutputToError | Redirect.ErrorToOutput))
throw new ProcessCreateException(_program, "Illegal redirection flags", __FILE__, __LINE__);
Pipe pin, pout, perr;
if(_redirect & Redirect.Input)
pin = new Pipe(DefaultStdinBufferSize);
if((_redirect & (Redirect.Output | Redirect.OutputToError)) == Redirect.Output)
pout = new Pipe(DefaultStdoutBufferSize);
if((_redirect & (Redirect.Error | Redirect.ErrorToOutput)) == Redirect.Error)
perr = new Pipe(DefaultStderrBufferSize);
// This pipe is used to propagate the result of the call to
// execv*() from the child process to the parent process.
Pipe pexec = new Pipe(8);
int status = 0;
_pid = fork();
if (_pid >= 0)
{
if (_pid != 0)
{
// Parent process
if(pin !is null)
{
_stdin = pin.sink;
pin.source.close();
}
if(pout !is null)
{
_stdout = pout.source;
pout.sink.close();
}
if(perr !is null)
{
_stderr = perr.source;
perr.sink.close();
}
pexec.sink.close();
scope(exit)
pexec.source.close();
try
{
pexec.source.input.read((cast(byte*) &status)[0 .. status.sizeof]);
}
catch (Exception e)
{
// Everything's OK, the pipe was closed after the call to execv*()
}
if (status == 0)
{
_running = true;
}
else
{
// We set errno to the value that was sent through
// the pipe from the child process
errno = status;
_running = false;
throw new ProcessCreateException(_program, __FILE__, __LINE__);
}
}
else
{
// Child process
int rc;
const(char)*[] argptr;
const(char)*[] envptr;
// Note that for all the pipes, we can close both ends
// because dup2 opens a duplicate file descriptor to the
// same resource.
// Replace stdin with the "read" pipe
if(pin !is null)
{
if (dup2(pin.source.fileHandle(), STDIN_FILENO) < 0)
throw new Exception("dup2 < 0");
pin.sink().close();
pin.source.close();
}
// Replace stdout with the "write" pipe
if(pout !is null)
{
if (dup2(pout.sink.fileHandle(), STDOUT_FILENO) < 0)
throw new Exception("dup2 < 0");
pout.source.close();
pout.sink.close();
}
// Replace stderr with the "write" pipe
if(perr !is null)
{
if (dup2(perr.sink.fileHandle(), STDERR_FILENO) < 0)
throw new Exception("dup2 < 0");
perr.source.close();
perr.sink.close();
}
// Check for redirection from stdout to stderr or vice
// versa
if(_redirect & Redirect.OutputToError)
{
if(dup2(STDERR_FILENO, STDOUT_FILENO) < 0)
throw new Exception("dup2 < 0");
}
if(_redirect & Redirect.ErrorToOutput)
{
if(dup2(STDOUT_FILENO, STDERR_FILENO) < 0)
throw new Exception("dup2 < 0");
}
// We close the unneeded part of the execv*() notification pipe
pexec.source.close();
// Set the "write" pipe so that it closes upon a successful
// call to execv*()
if (fcntl(cast(int) pexec.sink.fileHandle(), F_SETFD, FD_CLOEXEC) == 0)
{
// Convert the arguments and the environment variables to
// the format expected by the execv() family of functions.
argptr = toNullEndedArray(_program, _args);
envptr = (_copyEnv ? null : toNullEndedArray(_env));
// Switch to the working directory if it has been set.
if (_workDir.length > 0)
{
chdir(toStringz(_workDir));
}
// Replace the child fork with a new process. We always use the
// system PATH to look for executables that don't specify
// directories in their names.
rc = execvpe(_program, argptr, envptr);
if (rc == -1)
{
Cerr("Failed to exec ")(_program)(": ")(SysError.lastMsg).newline;
try
{
status = errno;
// Propagate the child process' errno value to
// the parent process.
pexec.sink.output.write((cast(byte*) &status)[0 .. status.sizeof]);
}
catch (Exception e)
{
}
exit(errno);
}
exit(errno);
}
else
{
Cerr("Failed to set notification pipe to close-on-exec for ")
(_program)(": ")(SysError.lastMsg).newline;
exit(errno);
}
}
}
else
{
throw new ProcessForkException(_pid, __FILE__, __LINE__);
}
}
else
{
assert(false, "tango.sys.Process: Unsupported platform");
}
return this;
}
/**
* Unconditionally wait for a process to end and return the reason and
* status code why the process ended.
*
* Returns:
* The return value is a Result struct, which has two members:
* reason and status. The reason can take the
* following values:
*
* Process.Result.Exit: the child process exited normally;
* status has the process' return
* code.
*
* Process.Result.Signal: the child process was killed by a signal;
* status has the signal number
* that killed the process.
*
* Process.Result.Stop: the process was stopped; status
* has the signal number that was used to stop
* the process.
*
* Process.Result.Continue: the process had been previously stopped
* and has now been restarted;
* status has the signal number
* that was used to continue the process.
*
* Process.Result.Error: We could not properly wait on the child
* process; status has the
* errno value if the process was
* running and -1 if not.
*
* Remarks:
* You can only call wait() on a running process once. The Signal, Stop
* and Continue reasons will only be returned on POSIX-compatible
* platforms.
* Calling wait() will not clean the pipes as the parent process may still
* want the remaining output. It is however recommended to call close()
* when no more content is expected, as this will close the pipes.
*/
public Result wait()
{
version (Windows)
{
Result result;
if (_running)
{
DWORD rc;
DWORD exitCode;
assert(_info !is null);
// We clean up the process related data and set the _running
// flag to false once we're done waiting for the process to
// finish.
//
// IMPORTANT: we don't delete the open pipes so that the parent
// process can get whatever the child process left on
// these pipes before dying.
scope(exit)
{
CloseHandle(_info.hProcess);
_running = false;
}
rc = WaitForSingleObject(_info.hProcess, INFINITE);
if (rc == WAIT_OBJECT_0)
{
GetExitCodeProcess(_info.hProcess, &exitCode);
result.reason = Result.Exit;
result.status = cast(typeof(result.status)) exitCode;
debug (Process)
Stdout.formatln("Child process '{0}' ({1}) returned with code {2}\n",
_program, pid, result.status);
}
else if (rc == WAIT_FAILED)
{
result.reason = Result.Error;
result.status = cast(short) GetLastError();
debug (Process)
Stdout.formatln("Child process '{0}' ({1}) failed "
"with unknown exit status {2}\n",
_program, pid, result.status);
}
}
else
{
result.reason = Result.Error;
result.status = -1;
debug (Process)
Stdout.formatln("Child process '{0}' is not running", _program);
}
return result;
}
else version (Posix)
{
Result result;
if (_running)
{
int rc;
// We clean up the process related data and set the _running
// flag to false once we're done waiting for the process to
// finish.
//
// IMPORTANT: we don't delete the open pipes so that the parent
// process can get whatever the child process left on
// these pipes before dying.
scope(exit)
{
_running = false;
}
// Wait for child process to end.
if (waitpid(_pid, &rc, 0) != -1)
{
if (WIFEXITED(rc))
{
result.reason = Result.Exit;
result.status = WEXITSTATUS(rc);
if (result.status != 0)
{
debug (Process)
Stdout.formatln("Child process '{0}' ({1}) returned with code {2}\n",
_program, _pid, result.status);
}
}
else
{
if (WIFSIGNALED(rc))
{
result.reason = Result.Signal;
result.status = WTERMSIG(rc);
debug (Process)
Stdout.formatln("Child process '{0}' ({1}) was killed prematurely "
"with signal {2}",
_program, _pid, result.status);
}
else if (WIFSTOPPED(rc))
{
result.reason = Result.Stop;
result.status = WSTOPSIG(rc);
debug (Process)
Stdout.formatln("Child process '{0}' ({1}) was stopped "
"with signal {2}",
_program, _pid, result.status);
}
else if (WIFCONTINUED(rc))
{
result.reason = Result.Stop;
result.status = WSTOPSIG(rc);
debug (Process)
Stdout.formatln("Child process '{0}' ({1}) was continued "
"with signal {2}",
_program, _pid, result.status);
}
else
{
result.reason = Result.Error;
result.status = rc;
debug (Process)
Stdout.formatln("Child process '{0}' ({1}) failed "
"with unknown exit status {2}\n",
_program, _pid, result.status);
}
}
}
else
{
result.reason = Result.Error;
result.status = errno;
debug (Process)
Stdout.formatln("Could not wait on child process '{0}' ({1}): ({2}) {3}",
_program, _pid, result.status, SysError.lastMsg);
}
}
else
{
result.reason = Result.Error;
result.status = -1;
debug (Process)
Stdout.formatln("Child process '{0}' is not running", _program);
}
return result;
}
else
{
assert(false, "tango.sys.Process: Unsupported platform");
}
}
/**
* Kill a running process. This method will not return until the process
* has been killed.
*
* Throws:
* ProcessKillException if the process could not be killed;
* ProcessWaitException if we could not wait on the process after
* killing it.
*
* Remarks:
* After calling this method you will not be able to call wait() on the
* process.
* Killing the process does not clean the attached pipes as the parent
* process may still want/need the remaining content. However, it is
* recommended to call close() on the process when it is no longer needed
* as this will clean the pipes.
*/
public void kill()
{
version (Windows)
{
if (_running)
{
assert(_info !is null);
if (TerminateProcess(_info.hProcess, cast(UINT) -1))
{
assert(_info !is null);
// We clean up the process related data and set the _running
// flag to false once we're done waiting for the process to
// finish.
//
// IMPORTANT: we don't delete the open pipes so that the parent
// process can get whatever the child process left on
// these pipes before dying.
scope(exit)
{
CloseHandle(_info.hProcess);
_running = false;
}
// FIXME: We should probably use a timeout here
if (WaitForSingleObject(_info.hProcess, INFINITE) == WAIT_FAILED)
{
throw new ProcessWaitException(cast(int) _info.dwProcessId,
__FILE__, __LINE__);
}
}
else
{
throw new ProcessKillException(cast(int) _info.dwProcessId,
__FILE__, __LINE__);
}
}
else
{
debug (Process)
Stdout.print("Tried to kill an invalid process");
}
}
else version (Posix)
{
if (_running)
{
int rc;
assert(_pid > 0);
if (.kill(_pid, SIGTERM) != -1)
{
// We clean up the process related data and set the _running
// flag to false once we're done waiting for the process to
// finish.
//
// IMPORTANT: we don't delete the open pipes so that the parent
// process can get whatever the child process left on
// these pipes before dying.
scope(exit)
{
_running = false;
}
// FIXME: is this loop really needed?
for (uint i = 0; i < 100; i++)
{
rc = waitpid(pid, null, WNOHANG | WUNTRACED);
if (rc == _pid)
{
break;
}
else if (rc == -1)
{
throw new ProcessWaitException(cast(int) _pid, __FILE__, __LINE__);
}
usleep(50000);
}
}
else
{
throw new ProcessKillException(_pid, __FILE__, __LINE__);
}
}
else
{
debug (Process)
Stdout.print("Tried to kill an invalid process");
}
}
else
{
assert(false, "tango.sys.Process: Unsupported platform");
}
}
/**
* Split a string containing the command line used to invoke a program
* and return and array with the parsed arguments. The double-quotes (")
* character can be used to specify arguments with embedded spaces.
* e.g. first "second param" third
*/
protected static const(char)[][] splitArgs(const(char)[] command, const(char)[] delims = " \t\r\n")
in
{
assert(!contains(delims, '"'),
"The argument delimiter string cannot contain a double quotes ('\"') character");
}
body
{
enum State
{
Start,
FindDelimiter,
InsideQuotes
}
const(char)[][] args = null;
const(char)[][] chunks = null;
int start = -1;
char c;
int i;
State state = State.Start;
// Append an argument to the 'args' array using the 'chunks' array
// and the current position in the 'command' string as the source.
void appendChunksAsArg()
{
size_t argPos;
if (chunks.length > 0)
{
// Create the array element corresponding to the argument by
// appending the first chunk.
args ~= chunks[0];
argPos = args.length - 1;
for (uint chunkPos = 1; chunkPos < chunks.length; ++chunkPos)
{
args[argPos] ~= chunks[chunkPos];
}
if (start != -1)
{
args[argPos] ~= command[start .. i];
}
chunks.length = 0;
}
else
{
if (start != -1)
{
args ~= command[start .. i];
}
}
start = -1;
}
for (i = 0; i < command.length; i++)
{
c = command[i];
switch (state)
{
// Start looking for an argument.
case State.Start:
if (c == '"')
{
state = State.InsideQuotes;
}
else if (!contains(delims, c))
{
start = i;
state = State.FindDelimiter;
}
else
{
appendChunksAsArg();
}
break;
// Find the ending delimiter for an argument.
case State.FindDelimiter:
if (c == '"')
{
// If we find a quotes character this means that we've
// found a quoted section of an argument. (e.g.
// abc"def"ghi). The quoted section will be appended
// to the preceding part of the argument. This is also
// what Unix shells do (i.e. a"b"c becomes abc).
if (start != -1)
{
chunks ~= command[start .. i];
start = -1;
}
state = State.InsideQuotes;
}
else if (contains(delims, c))
{
appendChunksAsArg();
state = State.Start;
}
break;
// Inside a quoted argument or section of an argument.
case State.InsideQuotes:
if (start == -1)
{
start = i;
}
if (c == '"')
{
chunks ~= command[start .. i];
start = -1;
state = State.Start;
}
break;
default:
assert(false, "Invalid state in Process.splitArgs");
}
}
// Add the last argument (if there is one)
appendChunksAsArg();
return args;
}
/**
* Close and delete any pipe that may have been left open in a previous
* execution of a child process.
*/
protected void cleanPipes()
{
delete _stdin;
delete _stdout;
delete _stderr;
}
/**
* Explicitly close any resources held by this process object. It is recommended
* to always call this when you are done with the process.
*/
public void close()
{
this.cleanPipes();
}
version (Windows)
{
/**
* Convert an associative array of strings to a buffer containing a
* concatenation of "<name>=<value>" strings separated by a null
* character and with an additional null character at the end of it.
* This is the format expected by the CreateProcess() Windows API for
* the environment variables.
*/
protected static char[] toNullEndedBuffer(const(char[])[char[]] src)
{
char[] dest;
foreach (key, value; src)
{
dest ~= key ~ '=' ~ value ~ '\0';
}
dest ~= "\0\0";
return dest;
}
}
else version (Posix)
{
/**
* Convert an array of strings to an array of pointers to char with
* a terminating null character (C strings). The resulting array
* has a null pointer at the end. This is the format expected by
* the execv*() family of POSIX functions.
*/
protected static const(char)*[] toNullEndedArray(const(char)[] elem0, const(char[])[] src)
{
if (src !is null && elem0 !is null)
{
const(char)*[] dest = new const(char)*[src.length + 2];
auto i = src.length;
// Add terminating null pointer to the array
dest[i + 1] = null;
while (i > 0)
{
--i;
// Add a terminating null character to each string
dest[i + 1] = toStringz(src[i]);
}
dest[0] = toStringz(elem0);
return dest;
}
else
{
return null;
}
}
/**
* Convert an associative array of strings to an array of pointers to
* char with a terminating null character (C strings). The resulting
* array has a null pointer at the end. This is the format expected by
* the execv*() family of POSIX functions for environment variables.
*/
protected static const(char)*[] toNullEndedArray(const(char[])[char[]] src)
{
const(char)*[] dest;
foreach (key, value; src)
{
dest ~= (key ~ '=' ~ value ~ '\0').ptr;
}
dest ~= null;
return dest;
}
/**
* Execute a process by looking up a file in the system path, passing
* the array of arguments and the the environment variables. This
* method is a combination of the execve() and execvp() POSIX system
* calls.
*/
protected static int execvpe(const(char)[] filename, const(char)*[] argv, const(char)*[] envp)
in
{
assert(filename.length > 0);
}
body
{
int rc = -1;
char* str;
if (!contains(filename, FileConst.PathSeparatorChar) &&
(str = getenv("PATH")) !is null)
{
auto pathList = delimit(str[0 .. strlen(str)], ":");
char[] path_buf;
foreach (path; pathList)
{
if (path[$-1] != FileConst.PathSeparatorChar)
{
path_buf.length = path.length + 1 + filename.length + 1;
path_buf[] = path ~ FileConst.PathSeparatorChar ~ filename ~ '\0';
}
else
{
path_buf.length = path.length +filename.length + 1;
path_buf[] = path ~ filename ~ '\0';
}
rc = execve(path_buf.ptr, argv.ptr, (envp.length == 0 ? cast(const(char)**)environ : envp.ptr));
// If the process execution failed because of an error
// other than ENOENT (No such file or directory) we
// abort the loop.
if (rc == -1 && SysError.lastCode !is ENOENT)
{
break;
}
}
}
else
{
debug (Process)
Stdout.formatln("Calling execve('{0}', argv[{1}], {2})",
(argv[0])[0 .. strlen(argv[0])],
argv.length, (envp.length > 0 ? "envp" : "null"));
rc = execve(argv[0], argv.ptr, (envp.length == 0 ? cast(const(char)**)environ : envp.ptr));
}
return rc;
}
}
}
/**
* Exception thrown when the process cannot be created.
*/
class ProcessCreateException: ProcessException
{
public this(const(char)[] command, const(char)[] file, uint line)
{
this(command, SysError.lastMsg, file, line);
}
public this(const(char)[] command, const(char)[] message, const(char)[] file, uint line)
{
super("Could not create process for " ~ command.idup ~ " : " ~ message.idup);
}
}
/**
* Exception thrown when the parent process cannot be forked.
*
* This exception will only be thrown on POSIX-compatible platforms.
*/
class ProcessForkException: ProcessException
{
public this(int pid, const(char)[] file, uint line)
{
super(format("Could not fork process ", pid).idup ~ " : " ~ SysError.lastMsg.idup);
}
}
/**
* Exception thrown when the process cannot be killed.
*/
class ProcessKillException: ProcessException
{
public this(int pid, const(char)[] file, uint line)
{
super(format("Could not kill process ", pid).idup ~ " : " ~ SysError.lastMsg.idup);
}
}
/**
* Exception thrown when the parent process tries to wait on the child
* process and fails.
*/
class ProcessWaitException: ProcessException
{
public this(int pid, const(char)[] file, uint line)
{
super(format("Could not wait on process ", pid).idup ~ " : " ~ SysError.lastMsg.idup);
}
}
/**
* append an int argument to a message
*/
private char[] format (const(char)[] msg, int value)
{
char[10] tmp;
return msg ~ Integer.format (tmp, value);
}
extern (C) uint sleep (uint s);
debug (UnitTest)
{
import tango.io.Stdout;
import tango.stdc.stdio : printf, fflush, stdout;
unittest
{
const(char)[] message = "hello world";
version(Windows)
{
const(char)[] command = "cmd.exe /c echo " ~ message;
}
else
const(char)[] command = "echo " ~ message;
try
{
auto p = new Process(command, null);
Stdout.flush();
p.execute();
char[255] buffer;
auto nread = p.stdout.read(buffer);
assert(nread != p.stdout.Eof);
version(Windows)
assert(buffer[0..nread] == message ~ "\r\n");
else
assert(buffer[0..nread] == message ~ "\n");
nread = p.stdout.read(buffer);
assert(nread == p.stdout.Eof);
auto result = p.wait();
assert(result.reason == Process.Result.Exit && result.status == 0);
}
catch (ProcessException e)
{
Cerr("Program execution failed: ")(e.toString()).newline();
}
}
}
|