示例#1
0
def cleanup():
    while spawned_pids:
        pid = spawned_pids.pop()
        try:
            # With waitpid and WNOHANG, only check the
            # first element of the tuple since the second
            # element may vary (bug #337465).
            if os.waitpid(pid, os.WNOHANG)[0] == 0:
                os.kill(pid, signal.SIGTERM)
                os.waitpid(pid, 0)
        except OSError:
            # This pid has been cleaned up outside
            # of spawn().
            pass
示例#2
0
def cleanup():
    while spawned_pids:
        pid = spawned_pids.pop()
        try:
            # With waitpid and WNOHANG, only check the
            # first element of the tuple since the second
            # element may vary (bug #337465).
            if os.waitpid(pid, os.WNOHANG)[0] == 0:
                os.kill(pid, signal.SIGTERM)
                os.waitpid(pid, 0)
        except OSError:
            # This pid has been cleaned up outside
            # of spawn().
            pass
示例#3
0
def spawn(mycommand,
          env={},
          opt_name=None,
          fd_pipes=None,
          returnpid=False,
          uid=None,
          gid=None,
          groups=None,
          umask=None,
          logfile=None,
          path_lookup=True,
          pre_exec=None):
    """
    Spawns a given command.
    
    @param mycommand: the command to execute
    @type mycommand: String or List (Popen style list)
    @param env: A dict of Key=Value pairs for env variables
    @type env: Dictionary
    @param opt_name: an optional name for the spawn'd process (defaults to the binary name)
    @type opt_name: String
    @param fd_pipes: A dict of mapping for pipes, { '0': stdin, '1': stdout } for example
    @type fd_pipes: Dictionary
    @param returnpid: Return the Process IDs for a successful spawn.
    NOTE: This requires the caller clean up all the PIDs, otherwise spawn will clean them.
    @type returnpid: Boolean
    @param uid: User ID to spawn as; useful for dropping privilages
    @type uid: Integer
    @param gid: Group ID to spawn as; useful for dropping privilages
    @type gid: Integer
    @param groups: Group ID's to spawn in: useful for having the process run in multiple group contexts.
    @type groups: List
    @param umask: An integer representing the umask for the process (see man chmod for umask details)
    @type umask: Integer
    @param logfile: name of a file to use for logging purposes
    @type logfile: String
    @param path_lookup: If the binary is not fully specified then look for it in PATH
    @type path_lookup: Boolean
    @param pre_exec: A function to be called with no arguments just prior to the exec call.
    @type pre_exec: callable
    
    logfile requires stdout and stderr to be assigned to this process (ie not pointed
       somewhere else.)
    
    """

    # mycommand is either a str or a list
    if isinstance(mycommand, basestring):
        mycommand = mycommand.split()

    if sys.hexversion < 0x3000000:
        # Avoid a potential UnicodeEncodeError from os.execve().
        env_bytes = {}
        for k, v in env.items():
            env_bytes[_unicode_encode(k, encoding=_encodings['content'])] = \
                _unicode_encode(v, encoding=_encodings['content'])
        env = env_bytes
        del env_bytes

    # If an absolute path to an executable file isn't given
    # search for it unless we've been told not to.
    binary = mycommand[0]
    if binary not in (BASH_BINARY, SANDBOX_BINARY, FAKEROOT_BINARY) and \
        (not os.path.isabs(binary) or not os.path.isfile(binary)
        or not os.access(binary, os.X_OK)):
        binary = path_lookup and find_binary(binary) or None
        if not binary:
            raise CommandNotFound(mycommand[0])

    # If we haven't been told what file descriptors to use
    # default to propagating our stdin, stdout and stderr.
    if fd_pipes is None:
        fd_pipes = {
            0: sys.stdin.fileno(),
            1: sys.stdout.fileno(),
            2: sys.stderr.fileno(),
        }

    # mypids will hold the pids of all processes created.
    mypids = []

    if logfile:
        # Using a log file requires that stdout and stderr
        # are assigned to the process we're running.
        if 1 not in fd_pipes or 2 not in fd_pipes:
            raise ValueError(fd_pipes)

        # Create a pipe
        (pr, pw) = os.pipe()

        # Create a tee process, giving it our stdout and stderr
        # as well as the read end of the pipe.
        mypids.extend(
            spawn(('tee', '-i', '-a', logfile),
                  returnpid=True,
                  fd_pipes={
                      0: pr,
                      1: fd_pipes[1],
                      2: fd_pipes[2]
                  }))

        # We don't need the read end of the pipe, so close it.
        os.close(pr)

        # Assign the write end of the pipe to our stdout and stderr.
        fd_pipes[1] = pw
        fd_pipes[2] = pw

    pid = os.fork()

    if pid == 0:
        try:
            _exec(binary, mycommand, opt_name, fd_pipes, env, gid, groups, uid,
                  umask, pre_exec)
        except SystemExit:
            raise
        except Exception as e:
            # We need to catch _any_ exception so that it doesn't
            # propagate out of this function and cause exiting
            # with anything other than os._exit()
            sys.stderr.write("%s:\n   %s\n" % (e, " ".join(mycommand)))
            traceback.print_exc()
            sys.stderr.flush()
            os._exit(1)

    if not isinstance(pid, int):
        raise AssertionError("fork returned non-integer: %s" % (repr(pid), ))

    # Add the pid to our local and the global pid lists.
    mypids.append(pid)
    spawned_pids.append(pid)

    # If we started a tee process the write side of the pipe is no
    # longer needed, so close it.
    if logfile:
        os.close(pw)

    # If the caller wants to handle cleaning up the processes, we tell
    # it about all processes that were created.
    if returnpid:
        return mypids

    # Otherwise we clean them up.
    while mypids:

        # Pull the last reader in the pipe chain. If all processes
        # in the pipe are well behaved, it will die when the process
        # it is reading from dies.
        pid = mypids.pop(0)

        # and wait for it.
        retval = os.waitpid(pid, 0)[1]

        # When it's done, we can remove it from the
        # global pid list as well.
        spawned_pids.remove(pid)

        if retval:
            # If it failed, kill off anything else that
            # isn't dead yet.
            for pid in mypids:
                # With waitpid and WNOHANG, only check the
                # first element of the tuple since the second
                # element may vary (bug #337465).
                if os.waitpid(pid, os.WNOHANG)[0] == 0:
                    os.kill(pid, signal.SIGTERM)
                    os.waitpid(pid, 0)
                spawned_pids.remove(pid)

            # If it got a signal, return the signal that was sent.
            if (retval & 0xff):
                return ((retval & 0xff) << 8)

            # Otherwise, return its exit code.
            return (retval >> 8)

    # Everything succeeded
    return 0
示例#4
0
def spawn(mycommand, env={}, opt_name=None, fd_pipes=None, returnpid=False,
          uid=None, gid=None, groups=None, umask=None, logfile=None,
          path_lookup=True, pre_exec=None):
    """
    Spawns a given command.
    
    @param mycommand: the command to execute
    @type mycommand: String or List (Popen style list)
    @param env: A dict of Key=Value pairs for env variables
    @type env: Dictionary
    @param opt_name: an optional name for the spawn'd process (defaults to the binary name)
    @type opt_name: String
    @param fd_pipes: A dict of mapping for pipes, { '0': stdin, '1': stdout } for example
    @type fd_pipes: Dictionary
    @param returnpid: Return the Process IDs for a successful spawn.
    NOTE: This requires the caller clean up all the PIDs, otherwise spawn will clean them.
    @type returnpid: Boolean
    @param uid: User ID to spawn as; useful for dropping privilages
    @type uid: Integer
    @param gid: Group ID to spawn as; useful for dropping privilages
    @type gid: Integer
    @param groups: Group ID's to spawn in: useful for having the process run in multiple group contexts.
    @type groups: List
    @param umask: An integer representing the umask for the process (see man chmod for umask details)
    @type umask: Integer
    @param logfile: name of a file to use for logging purposes
    @type logfile: String
    @param path_lookup: If the binary is not fully specified then look for it in PATH
    @type path_lookup: Boolean
    @param pre_exec: A function to be called with no arguments just prior to the exec call.
    @type pre_exec: callable
    
    logfile requires stdout and stderr to be assigned to this process (ie not pointed
       somewhere else.)
    
    """

    # mycommand is either a str or a list
    if isinstance(mycommand, basestring):
        mycommand = mycommand.split()

    if sys.hexversion < 0x3000000:
        # Avoid a potential UnicodeEncodeError from os.execve().
        env_bytes = {}
        for k, v in env.items():
            env_bytes[_unicode_encode(k, encoding=_encodings['content'])] = \
                _unicode_encode(v, encoding=_encodings['content'])
        env = env_bytes
        del env_bytes

    # If an absolute path to an executable file isn't given
    # search for it unless we've been told not to.
    binary = mycommand[0]
    if binary not in (BASH_BINARY, SANDBOX_BINARY, FAKEROOT_BINARY) and \
        (not os.path.isabs(binary) or not os.path.isfile(binary)
        or not os.access(binary, os.X_OK)):
        binary = path_lookup and find_binary(binary) or None
        if not binary:
            raise CommandNotFound(mycommand[0])

    # If we haven't been told what file descriptors to use
    # default to propagating our stdin, stdout and stderr.
    if fd_pipes is None:
        fd_pipes = {
            0:sys.stdin.fileno(),
            1:sys.stdout.fileno(),
            2:sys.stderr.fileno(),
        }

    # mypids will hold the pids of all processes created.
    mypids = []

    if logfile:
        # Using a log file requires that stdout and stderr
        # are assigned to the process we're running.
        if 1 not in fd_pipes or 2 not in fd_pipes:
            raise ValueError(fd_pipes)

        # Create a pipe
        (pr, pw) = os.pipe()

        # Create a tee process, giving it our stdout and stderr
        # as well as the read end of the pipe.
        mypids.extend(spawn(('tee', '-i', '-a', logfile),
                      returnpid=True, fd_pipes={0:pr,
                      1:fd_pipes[1], 2:fd_pipes[2]}))

        # We don't need the read end of the pipe, so close it.
        os.close(pr)

        # Assign the write end of the pipe to our stdout and stderr.
        fd_pipes[1] = pw
        fd_pipes[2] = pw

    pid = os.fork()

    if pid == 0:
        try:
            _exec(binary, mycommand, opt_name, fd_pipes,
                  env, gid, groups, uid, umask, pre_exec)
        except SystemExit:
            raise
        except Exception as e:
            # We need to catch _any_ exception so that it doesn't
            # propagate out of this function and cause exiting
            # with anything other than os._exit()
            sys.stderr.write("%s:\n   %s\n" % (e, " ".join(mycommand)))
            traceback.print_exc()
            sys.stderr.flush()
            os._exit(1)

    if not isinstance(pid, int):
        raise AssertionError("fork returned non-integer: %s" % (repr(pid),))

    # Add the pid to our local and the global pid lists.
    mypids.append(pid)
    spawned_pids.append(pid)

    # If we started a tee process the write side of the pipe is no
    # longer needed, so close it.
    if logfile:
        os.close(pw)

    # If the caller wants to handle cleaning up the processes, we tell
    # it about all processes that were created.
    if returnpid:
        return mypids

    # Otherwise we clean them up.
    while mypids:

        # Pull the last reader in the pipe chain. If all processes
        # in the pipe are well behaved, it will die when the process
        # it is reading from dies.
        pid = mypids.pop(0)

        # and wait for it.
        retval = os.waitpid(pid, 0)[1]

        # When it's done, we can remove it from the
        # global pid list as well.
        spawned_pids.remove(pid)

        if retval:
            # If it failed, kill off anything else that
            # isn't dead yet.
            for pid in mypids:
                # With waitpid and WNOHANG, only check the
                # first element of the tuple since the second
                # element may vary (bug #337465).
                if os.waitpid(pid, os.WNOHANG)[0] == 0:
                    os.kill(pid, signal.SIGTERM)
                    os.waitpid(pid, 0)
                spawned_pids.remove(pid)

            # If it got a signal, return the signal that was sent.
            if (retval & 0xff):
                return ((retval & 0xff) << 8)

            # Otherwise, return its exit code.
            return (retval >> 8)

    # Everything succeeded
    return 0