def run_process_with_timeout(
        args,
        filename_in=None,
        filename_out=None,
        filename_err=None,
        cwd=None,
        timeout=None,
        sudo=None):
    """Execute the specified process but within a certain timeout.

    @param args: the actuall process. This should be a list of string as in:
         ['/usr/bin/foo', '--bar', 'baz']
    @type args: list of string

    @param filename_in: the path to a file that should be provided as standard
        input to the process. If None this will default to /dev/null
    @type filename_in: string

    @param filename_out: Desired filename for stdout output
        (optional; see below).
    @type filename_out: string

    @param filename_err: Desired filename for stderr output
        (optional; see below).
    @type filename_err: string

    @param cwd: the path from where to execute the process
    @type cwd: string

    @param timeout: the timeout in seconds after which to consider the
        process execution as failed. a Timeout exception will be raised
    @type timeout: int

    @param sudo: the optional name of the user under which to execute the
        process (by using sudo, without prompting for a password)
    @type sudo: string

    @return: Tuple (exit code, string containing stdout output buffer,
        string containing stderr output buffer).

        However, if either filename_out or filename_err are defined,
        then the output buffers are not passed back but rather written
        into filename_out/filename_err pathnames.  This is useful for
        commands that produce big files, for which it is not practical
        to pass results back to the callers in a Python text buffer.
        Note that it is the client's responsibility to name these
        files in the proper fashion (e.g. to be unique) and to close
        these files after use.
    @rtype: (number, string, string)

    @raise Timeout: if the process does not terminate within the timeout
    """
    if timeout is None:
        timeout = current_app.config['CFG_MISCUTIL_DEFAULT_PROCESS_TIMEOUT']
    stdout = stderr = None
    if filename_in is not None:
        stdin = open(filename_in)
    else:
        # FIXME: should use NUL on Windows
        stdin = open('/dev/null', 'r')
    if filename_out:
        stdout = open(filename_out, 'w')
    if filename_err:
        stderr = open(filename_err, 'w')
    tmp_stdout = StringIO()
    tmp_stderr = StringIO()
    if sudo is not None:
        args = ['sudo', '-u', sudo, '-S'] + list(args)
    # See: <http://stackoverflow.com/questions/3876886/timeout-a-subprocess>
    process = subprocess.Popen(
        args,
        stdin=stdin,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        close_fds=True,
        cwd=cwd,
        preexec_fn=os.setpgrp)

    # See:
    # <http://stackoverflow.com/questions/375427/non-blocking-read-on-a-stream-in-python>
    fd = process.stdout.fileno()
    fl = fcntl.fcntl(fd, fcntl.F_GETFL)
    fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
    fd = process.stderr.fileno()
    fl = fcntl.fcntl(fd, fcntl.F_GETFL)
    fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
    fd_to_poll = [process.stdout, process.stderr]
    select_timeout = 0.5
    t1 = time.time()
    try:
        while process.poll() is None:
            if time.time() - t1 >= timeout:
                if process.stdin is not None:
                    process.stdin.close()
                time.sleep(1)
                if process.poll() is None:
                    # See:
                    # <http://stackoverflow.com/questions/3876886/timeout-a-subprocess>
                    os.killpg(process.pid, signal.SIGTERM)
                    time.sleep(1)
                if process.poll() is None:
                    os.killpg(process.pid, signal.SIGKILL)
                try:
                    os.waitpid(process.pid, 0)
                except OSError:
                    pass
                raise Timeout()
            for fd in select.select(fd_to_poll, [], [], select_timeout)[0]:
                if fd == process.stdout:
                    buf = process.stdout.read(65536)
                    if stdout is None:
                        tmp_stdout.write(buf)
                    else:
                        stdout.write(buf)
                elif fd == process.stderr:
                    buf = process.stderr.read(65536)
                    if stderr is None:
                        tmp_stderr.write(buf)
                    else:
                        stderr.write(buf)
                else:
                    raise OSError("fd %s is not a valid file descriptor" % fd)
    finally:
        while True:
            # Let's just read what is remaining to read.
            for fd in select.select(fd_to_poll, [], [], select_timeout)[0]:
                if fd == process.stdout:
                    buf = process.stdout.read(65536)
                    tmp_stdout.write(buf)
                    if stdout is not None:
                        stdout.write(buf)
                elif fd == process.stderr:
                    buf = process.stderr.read(65536)
                    tmp_stderr.write(buf)
                    if stderr is not None:
                        stderr.write(buf)
                else:
                    raise OSError("fd %s is not a valid file descriptor" % fd)
            else:
                break
    return process.poll(), tmp_stdout.getvalue(), tmp_stderr.getvalue()
def run_process_with_timeout ( args , filename_in = None , filename_out = None , filename_err = None , cwd = None , timeout = None , sudo = None ) : if timeout is None : timeout = current_app . config [ 'CFG_MISCUTIL_DEFAULT_PROCESS_TIMEOUT' ] stdout = stderr = None if filename_in is not None : stdin = open ( filename_in ) else : stdin = open ( '/dev/null' , 'r' ) if filename_out : stdout = open ( filename_out , 'w' ) if filename_err : stderr = open ( filename_err , 'w' ) tmp_stdout = StringIO ( ) tmp_stderr = StringIO ( ) if sudo is not None : args = [ 'sudo' , '-u' , sudo , '-S' ] + list ( args ) process = subprocess . Popen ( args , stdin = stdin , stdout = subprocess . PIPE , stderr = subprocess . PIPE , close_fds = True , cwd = cwd , preexec_fn = os . setpgrp ) fd = process . stdout . fileno ( ) fl = fcntl . fcntl ( fd , fcntl . F_GETFL ) fcntl . fcntl ( fd , fcntl . F_SETFL , fl | os . O_NONBLOCK ) fd = process . stderr . fileno ( ) fl = fcntl . fcntl ( fd , fcntl . F_GETFL ) fcntl . fcntl ( fd , fcntl . F_SETFL , fl | os . O_NONBLOCK ) fd_to_poll = [ process . stdout , process . stderr ] select_timeout = 0.5 t1 = time . time ( ) try : while process . poll ( ) is None : if time . time ( ) - t1 >= timeout : if process . stdin is not None : process . stdin . close ( ) time . sleep ( 1 ) if process . poll ( ) is None : os . killpg ( process . pid , signal . SIGTERM ) time . sleep ( 1 ) if process . poll ( ) is None : os . killpg ( process . pid , signal . SIGKILL ) try : os . waitpid ( process . pid , 0 ) except OSError : pass raise Timeout ( ) for fd in select . select ( fd_to_poll , [ ] , [ ] , select_timeout ) [ 0 ] : if fd == process . stdout : buf = process . stdout . read ( 65536 ) if stdout is None : tmp_stdout . write ( buf ) else : stdout . write ( buf ) elif fd == process . stderr : buf = process . stderr . read ( 65536 ) if stderr is None : tmp_stderr . write ( buf ) else : stderr . write ( buf ) else : raise OSError ( "fd %s is not a valid file descriptor" % fd ) finally : while True : for fd in select . select ( fd_to_poll , [ ] , [ ] , select_timeout ) [ 0 ] : if fd == process . stdout : buf = process . stdout . read ( 65536 ) tmp_stdout . write ( buf ) if stdout is not None : stdout . write ( buf ) elif fd == process . stderr : buf = process . stderr . read ( 65536 ) tmp_stderr . write ( buf ) if stderr is not None : stderr . write ( buf ) else : raise OSError ( "fd %s is not a valid file descriptor" % fd ) else : break return process . poll ( ) , tmp_stdout . getvalue ( ) , tmp_stderr . getvalue ( )
Execute the specified process but within a certain timeout.

    @param args: the actuall process. This should be a list of string as in:
         ['/usr/bin/foo', '--bar', 'baz']
    @type args: list of string

    @param filename_in: the path to a file that should be provided as standard
        input to the process. If None this will default to /dev/null
    @type filename_in: string

    @param filename_out: Desired filename for stdout output
        (optional; see below).
    @type filename_out: string

    @param filename_err: Desired filename for stderr output
        (optional; see below).
    @type filename_err: string

    @param cwd: the path from where to execute the process
    @type cwd: string

    @param timeout: the timeout in seconds after which to consider the
        process execution as failed. a Timeout exception will be raised
    @type timeout: int

    @param sudo: the optional name of the user under which to execute the
        process (by using sudo, without prompting for a password)
    @type sudo: string

    @return: Tuple (exit code, string containing stdout output buffer,
        string containing stderr output buffer).

        However, if either filename_out or filename_err are defined,
        then the output buffers are not passed back but rather written
        into filename_out/filename_err pathnames.  This is useful for
        commands that produce big files, for which it is not practical
        to pass results back to the callers in a Python text buffer.
        Note that it is the client's responsibility to name these
        files in the proper fashion (e.g. to be unique) and to close
        these files after use.
    @rtype: (number, string, string)

    @raise Timeout: if the process does not terminate within the timeout