blob: db3c8be363ab69245b87677ce5b3e1ee032eb09d [file] [log] [blame]
# portage.py -- core Portage functionality
# Copyright 1998-2004 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2
# $Id: /var/cvsroot/gentoo-src/portage/pym/portage_exec.py,v 1.13.2.4 2005/04/17 09:01:56 jstubbs Exp $
import os, atexit, signal, sys
import portage_data
from portage_const import BASH_BINARY, SANDBOX_BINARY
try:
import resource
max_fd_limit = resource.getrlimit(resource.RLIMIT_NOFILE)[0]
except ImportError:
max_fd_limit = 256
sandbox_capable = (os.path.isfile(SANDBOX_BINARY) and
os.access(SANDBOX_BINARY, os.X_OK))
def spawn_bash(mycommand, debug=False, opt_name=None, **keywords):
args = [BASH_BINARY]
if not opt_name:
opt_name = os.path.basename(mycommand.split()[0])
if debug:
# Print commands and their arguments as they are executed.
args.append("-x")
args.append("-c")
args.append(mycommand)
return spawn(args, opt_name=opt_name, **keywords)
def spawn_sandbox(mycommand, opt_name=None, **keywords):
if not sandbox_capable:
return spawn_bash(mycommand, opt_name=opt_name, **keywords)
args=[SANDBOX_BINARY]
if not opt_name:
opt_name = os.path.basename(mycommand.split()[0])
args.append(mycommand)
return spawn(args, opt_name=opt_name, **keywords)
# We need to make sure that any processes spawned are killed off when
# we exit. spawn() takes care of adding and removing pids to this list
# as it creates and cleans up processes.
spawned_pids = []
def cleanup():
while spawned_pids:
pid = spawned_pids.pop()
try:
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
atexit.register(cleanup)
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):
# mycommand is either a str or a list
if isinstance(mycommand, str):
mycommand = mycommand.split()
# 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 (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:
return -1
# If we haven't been told what file descriptors to use
# default to propogating our stdin, stdout and stderr.
if fd_pipes is None:
fd_pipes = {0:0, 1:1, 2:2}
# 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 not pid:
try:
_exec(binary, mycommand, opt_name, fd_pipes,
env, gid, groups, uid, umask)
except Exception, e:
# We need to catch _any_ exception so that it doesn't
# propogate out of this function and cause exiting
# with anything other than os._exit()
sys.stderr.write("%s:\n %s\n" % (e, " ".join(mycommand)))
os._exit(1)
# 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:
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
def _exec(binary, mycommand, opt_name, fd_pipes, env, gid, groups, uid, umask):
# If the process we're creating hasn't been given a name
# assign it the name of the executable.
if not opt_name:
opt_name = os.path.basename(binary)
# Set up the command's argument list.
myargs = [opt_name]
myargs.extend(mycommand[1:])
# Set up the command's pipes.
my_fds = {}
# To protect from cases where direct assignment could
# clobber needed fds ({1:2, 2:1}) we first dupe the fds
# into unused fds.
for fd in fd_pipes:
my_fds[fd] = os.dup(fd_pipes[fd])
# Then assign them to what they should be.
for fd in my_fds:
os.dup2(my_fds[fd], fd)
# Then close _all_ fds that haven't been explictly
# requested to be kept open.
for fd in range(max_fd_limit):
if fd not in my_fds:
try:
os.close(fd)
except OSError:
pass
# Set requested process permissions.
if gid:
os.setgid(gid)
if groups:
os.setgroups(groups)
if uid:
os.setuid(uid)
if umask:
os.umask(umask)
# And switch to the new process.
os.execve(binary, myargs, env)
def find_binary(binary):
for path in os.getenv("PATH", "").split(":"):
filename = "%s/%s" % (path, binary)
if os.access(filename, os.X_OK) and os.path.isfile(filename):
return filename
return None