| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092 | /******************************************************************************* 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 char** environ; static this () { environ = *_NSGetEnviron(); } } else private extern (C) extern 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 char[] toString() { 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 str; } } static const uint DefaultStdinBufferSize = 512; static const uint DefaultStdoutBufferSize = 8192; static const uint DefaultStderrBufferSize = 512; static const Redirect DefaultRedirectFlags = Redirect.All; private char[][] _args; private char[][char[]] _env; private 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(char[][] args ...) { if(args.length == 1) _args = splitArgs(args[0]); else _args = args; } /** * 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, 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(char[] command, char[][char[]] env) in { assert(command.length > 0); } body { _args = splitArgs(command); _env = env; } /** * 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(char[][] args, char[][char[]] env) in { assert(args.length > 0); assert(args[0].length > 0); } body { _args = args; _env = env; } /** * Indicate whether the process is running or not. */ public bool isRunning() { return _running; } /** * Return the running process' ID. * * Returns: an int with the process ID if the process is running; * -1 if not. */ public 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. */ public char[] programName() { return (_args !is null ? _args[0] : null); } /** * Set the process' executable filename. */ public char[] programName(char[] name) { if (_args.length == 0) { _args.length = 1; } return _args[0] = name; } /** * Set the process' executable filename, return 'this' for chaining */ public Process setProgramName(char[] name) { programName = name; return this; } /** * Return an array with the process' arguments. */ public char[][] args() { 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 arugments that were set. * * Examples: * --- * p.args("myprogram", "first", "second argument", "third"); * --- */ public char[][] args(char[] progname, char[][] args ...) { return _args = progname ~ 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(char[] progname, char[][] args ...) { this.args(progname, args); return this; } /** * If true, the environment from the current process will be copied to the * child process. */ public 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. */ 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. */ public char[][char[]] env() { 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; * --- */ public char[][char[]] env(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(char[][char[]] env) { _copyEnv = false; _env = env; return this; } /** * Return an UTF-8 string with the process' command line. */ public char[] toString() { char[] command; for (uint 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. */ public char[] workDir() { 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. */ public char[] workDir(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(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. */ public Redirect redirect() { return _redirect; } /** * Set the redirect flags for the process. */ 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. */ public bool gui() { 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. */ 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. */ 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. */ 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. */ 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(char[] arg1, char[][] args ...) in { assert(!_running); } body { this._args = arg1 ~ 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(char[] command, char[][char[]] env) in { assert(!_running); assert(command.length > 0); } body { _args = splitArgs(command); _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(char[][] args, char[][char[]] env) in { assert(!_running); assert(args.length > 0); } body { _args = args; _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(_args.length > 0 && _args[0] !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(_args[0], "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; foreach(a; _args) { char[] nextarg = a.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; } 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(_args[0], __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(_args[0], __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(_args[0], "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(_args[0], __FILE__, __LINE__); } } else { // Child process int rc; char*[] argptr; 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(_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(_args[0], argptr, envptr); if (rc == -1) { Cerr("Failed to exec ")(_args[0])(": ")(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 ") (_args[0])(": ")(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", _args[0], 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", _args[0], pid, result.status); } } else { result.reason = Result.Error; result.status = -1; debug (Process) Stdout.formatln("Child process '{0}' is not running", _args[0]); } 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", _args[0], _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}", _args[0], _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}", _args[0], _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}", _args[0], _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", _args[0], _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}", _args[0], _pid, result.status, SysError.lastMsg); } } else { result.reason = Result.Error; result.status = -1; debug (Process) Stdout.formatln("Child process '{0}' is not running", _args[0]); } 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 char[][] splitArgs(ref char[] command, 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 } char[][] args = null; 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(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 char*[] toNullEndedArray(char[][] src) { if (src !is null) { char*[] dest = new char*[src.length + 1]; auto i = src.length; // Add terminating null pointer to the array dest[i] = null; while (i > 0) { --i; // Add a terminating null character to each string dest[i] = toStringz(src[i]); } 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 char*[] toNullEndedArray(char[][char[]] src) { 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(char[] filename, char*[] argv, char*[] envp) in { assert(filename.length > 0); } body { int rc = -1; char* str; if (!contains(filename, FileConst.PathSeparatorChar) && (str = getenv("PATH")) !is null) { char[][] 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 ? 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 ? environ : envp.ptr)); } return rc; } } } /** * Exception thrown when the process cannot be created. */ class ProcessCreateException: ProcessException { public this(char[] command, char[] file, uint line) { this(command, SysError.lastMsg, file, line); } public this(char[] command, char[] message, char[] file, uint line) { super("Could not create process for " ~ command ~ " : " ~ message); } } /** * 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, char[] file, uint line) { super(format("Could not fork process ", pid) ~ " : " ~ SysError.lastMsg); } } /** * Exception thrown when the process cannot be killed. */ class ProcessKillException: ProcessException { public this(int pid, char[] file, uint line) { super(format("Could not kill process ", pid) ~ " : " ~ SysError.lastMsg); } } /** * Exception thrown when the parent process tries to wait on the child * process and fails. */ class ProcessWaitException: ProcessException { public this(int pid, char[] file, uint line) { super(format("Could not wait on process ", pid) ~ " : " ~ SysError.lastMsg); } } /** * append an int argument to a message */ private char[] format (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 { char[] message = "hello world"; version(Windows) { char[] command = "cmd.exe /c echo " ~ message; } else 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(); } } } |