| 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(); } } } |