view tango/tango/sys/Process.d @ 132:1700239cab2e trunk

[svn r136] MAJOR UNSTABLE UPDATE!!! Initial commit after moving to Tango instead of Phobos. Lots of bugfixes... This build is not suitable for most things.
author lindquist
date Fri, 11 Jan 2008 17:57:40 +0100
parents
children
line wrap: on
line source

/*******************************************************************************
  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.FileConst;
private import tango.io.Console;
private import tango.io.Buffer;
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;
}

debug (Process)
{
    private import tango.io.Stdout;
}


/**
 * 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 of 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.
 *
 * 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;

    private char[][]        _args;
    private char[][char[]]  _env;
    private char[]          _workDir;
    private PipeConduit     _stdin;
    private PipeConduit     _stdout;
    private PipeConduit     _stderr;
    private bool            _running = false;

    version (Windows)
    {
        private PROCESS_INFORMATION *_info = null;
    }
    else
    {
        private pid_t _pid = cast(pid_t) -1;
    }

    /**
     * Constructor (variadic version).
     *
     * Params:
     * args     = array of strings with the process' arguments; the first
     *            argument must be the process' name; the arguments can be
     *            empty.
     *
     * Examples:
     * ---
     * auto p = new Process("myprogram", "first argument", "second", "third")
     * ---
     */
    public this(char[][] args ...)
    {
        _args = args;
    }

    /**
     * Constructor.
     *
     * 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.
     *
     * 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.
     * 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 void programName(char[] name)
    {
        if (_args.length == 0)
        {
            _args.length = 1;
        }
        _args[0] = name;
    }

    /**
     * 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.
     *
     * Examples:
     * ---
     * p.args("myprogram", "first", "second argument", "third");
     * ---
     */
    public void args(char[][] args ...)
    {
        _args = args;
    }

    /**
     * Return an associative array with the process' environment variables.
     */
    public char[][char[]] env()
    {
        return _env;
    }

    /**
     * Set the process' environment variables from the associative array
     * received by the method.
     *
     * Params:
     * env  = associative array of strings containing the environment
     *        variables for the process. The variable name should be the key
     *        used for each entry.
     *
     * Examples:
     * ---
     * char[][char[]] env;
     *
     * env["MYVAR1"] = "first";
     * env["MYVAR2"] = "second";
     *
     * p.env = env;
     * ---
     */
    public void env(char[][char[]] env)
    {
        _env = env;
    }

    /**
     * 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];
                command ~= '"';
            }
            else
            {
                command ~= _args[i];
            }
        }
        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.
     */
    public void workDir(char[] dir)
    {
        _workDir = dir;
    }

    /**
     * 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.
     */
    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.
     */
    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.
     */
    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.
     */
    public void execute(char[][] args ...)
    in
    {
        assert(!_running);
    }
    body
    {
        if (args.length > 0 && args[0] !is null)
        {
            _args = args;
        }
        executeInternal();
    }

    /**
     * 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.
     *
     * 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.
     */
    public void execute(char[] command, char[][char[]] env)
    in
    {
        assert(!_running);
        assert(command.length > 0);
    }
    body
    {
        _args = splitArgs(command);
        _env = env;

        executeInternal();
    }

    /**
     * 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.
     *
     * 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.
     *
     * Examples:
     * ---
     * auto p = new Process();
     * char[][] args;
     *
     * args ~= "ls";
     * args ~= "-l";
     *
     * p.execute(args, null);
     * ---
     */
    public void execute(char[][] args, char[][char[]] env)
    in
    {
        assert(!_running);
        assert(args.length > 0);
    }
    body
    {
        _args = args;
        _env = env;

        executeInternal();
    }

    /**
     * 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.
     *
     * 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.
     */
    protected void executeInternal()
    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;
            startup.dwFlags |= STARTF_USESTDHANDLES;

            // Create the pipes used to communicate with the child process.
            Pipe 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);
            scope(exit)
                pin.source.close();

            Pipe 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);
            scope(exit)
                pout.sink.close();

            Pipe 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);
            scope(exit)
                perr.sink.close();

            _info = new PROCESS_INFORMATION;
            // Set up members of the PROCESS_INFORMATION structure.
            memset(_info, '\0', PROCESS_INFORMATION.sizeof);

            char[] command = toString();
            command ~= '\0';

            // Convert the working directory to a null-ended string if
            // necessary.
            if (CreateProcessA(null, command.ptr, null, null, true,
                               DETACHED_PROCESS,
                               (_env.length > 0 ? toNullEndedBuffer(_env).ptr : null),
                               toStringz(_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();

            Pipe pin = new Pipe(DefaultStdinBufferSize);
            Pipe pout = new Pipe(DefaultStdoutBufferSize);
            Pipe 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
                    _stdin = pin.sink;
                    pin.source.close();

                    _stdout = pout.source;
                    pout.sink.close();

                    _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;

                    // Replace stdin with the "read" pipe
                    dup2(pin.source.fileHandle(), STDIN_FILENO);
                    pin.sink().close();
                    scope(exit)
                        pin.source.close();

                    // Replace stdout with the "write" pipe
                    dup2(pout.sink.fileHandle(), STDOUT_FILENO);
                    pout.source.close();
                    scope(exit)
                        pout.sink.close();

                    // Replace stderr with the "write" pipe
                    dup2(perr.sink.fileHandle(), STDERR_FILENO);
                    perr.source.close();
                    scope(exit)
                        perr.sink.close();

                    // We close the unneeded part of the execv*() notification pipe
                    pexec.source.close();
                    scope(exit)
                        pexec.sink.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 = (_env.length > 0 ? toNullEndedArray(_env) : null);

                        // 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);
                        }
                    }
                    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");
        }
    }


    /**
     * 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.
     */
    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.
     */
    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(inout 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()
        {
            uint 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;
    }

    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';
            }

            if (dest.length > 0)
            {
                dest ~= '\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];
                int     i = src.length;

                // Add terminating null pointer to the array
                dest[i] = null;

                while (--i >= 0)
                {
                    // 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;
            }

            if (dest.length > 0)
            {
                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)], ":");

                foreach (path; pathList)
                {
                    if (path[path.length - 1] != FileConst.PathSeparatorChar)
                    {
                        path ~= FileConst.PathSeparatorChar;
                    }

                    debug (Process)
                        Stdout.formatln("Trying execution of '{0}' in directory '{1}'",
                                        filename, path);

                    path ~= filename;
                    path ~= '\0';

                    rc = execve(path.ptr, argv.ptr, (envp.length > 0 ? envp.ptr : null));
                    // If the process execution failed because of an error
                    // other than ENOENT (No such file or directory) we
                    // abort the loop.
                    if (rc == -1 && errno != 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 ? envp.ptr : null));
            }
            return rc;
        }
    }
}


/**
 * Exception thrown when the process cannot be created.
 */
class ProcessCreateException: ProcessException
{
    public this(char[] command, char[] file, uint line)
    {
        super("Could not create process for " ~ command ~ " : " ~ SysError.lastMsg);
    }
}

/**
 * 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);
}


debug (UnitTest)
{
    private import tango.text.stream.LineIterator;

    unittest
    {
        char[][] params;
        char[] command = "echo ";

        params ~= "one";
        params ~= "two";
        params ~= "three";

        command ~= '"';
        foreach (i, param; params)
        {
            command ~= param;
            if (i != params.length - 1)
            {
                command ~= '\n';
            }
        }
        command ~= '"';

        try
        {
            auto p = new Process(command, null);

            p.execute();

            foreach (i, line; new LineIterator!(char)(p.stdout))
            {
                if (i == params.length) // echo can add ending new line confusing this test
                    break;
                assert(line == params[i]);
            }

            auto result = p.wait();

            assert(result.reason == Process.Result.Exit && result.status == 0);
        }
        catch (ProcessException e)
        {
            Cerr("Program execution failed: ")(e.toString()).newline();
        }
    }
}