summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJason Stubbs <jstubbs@gentoo.org>2005-11-03 14:34:08 +0000
committerJason Stubbs <jstubbs@gentoo.org>2005-11-03 14:34:08 +0000
commit581de87ec02778e95aaf9720b4a69e1ba5852a18 (patch)
tree9a2f5553f09135a99b243c49138b40e4de4ecd38 /pym/portage_exec.py
parentStart from 1st element rather than 2nd when doing checks on *DEPEND (diff)
downloadportage-multirepo-581de87ec02778e95aaf9720b4a69e1ba5852a18.tar.gz
portage-multirepo-581de87ec02778e95aaf9720b4a69e1ba5852a18.tar.bz2
portage-multirepo-581de87ec02778e95aaf9720b4a69e1ba5852a18.zip
A couple of bug fixes, large cleanups, commenting and a bit of dead code removal.
svn path=/main/branches/2.0/; revision=2254
Diffstat (limited to 'pym/portage_exec.py')
-rw-r--r--pym/portage_exec.py361
1 files changed, 184 insertions, 177 deletions
diff --git a/pym/portage_exec.py b/pym/portage_exec.py
index 8a92c7ae..c62d0a54 100644
--- a/pym/portage_exec.py
+++ b/pym/portage_exec.py
@@ -4,212 +4,219 @@
# $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,types,atexit,string,stat
-import signal
+import os, atexit, signal, sys
import portage_data
-import portage_util
+
+from portage_const import BASH_BINARY, SANDBOX_BINARY
+
try:
import resource
- max_fd_limit=resource.getrlimit(RLIMIT_NOFILE)
-except SystemExit, e:
- raise
-except:
- # hokay, no resource module.
- max_fd_limit=256
+ max_fd_limit = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
+except ImportError:
+ max_fd_limit = 256
-spawned_pids = []
-def cleanup():
- global spawned_pids
- while spawned_pids:
- pid = spawned_pids.pop()
- try:
- os.kill(pid,SIGKILL)
- except SystemExit, e:
- raise
- except:
- pass
-atexit.register(cleanup)
-from portage_const import BASH_BINARY,SANDBOX_BINARY,SANDBOX_PIDS_FILE
+sandbox_capable = (os.path.isfile(SANDBOX_BINARY) and
+ os.access(SANDBOX_BINARY, os.X_OK))
-sandbox_capable = (os.path.exists(SANDBOX_BINARY) and os.access(SANDBOX_BINARY, os.X_OK))
-def spawn_bash(mycommand,env={},debug=False,opt_name=None,**keywords):
- args=[BASH_BINARY]
+def spawn_bash(mycommand, debug=False, opt_name=None, **keywords):
+ args = [BASH_BINARY]
if not opt_name:
- opt_name=mycommand.split()[0]
- if not env.has_key("BASH_ENV"):
- env["BASH_ENV"] = "/etc/spork/is/not/valid/profile.env"
+ 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,env=env,opt_name=opt_name,**keywords)
+ return spawn(args, opt_name=opt_name, **keywords)
-def spawn_sandbox(mycommand,uid=None,opt_name=None,**keywords):
+
+def spawn_sandbox(mycommand, opt_name=None, **keywords):
if not sandbox_capable:
- return spawn_bash(mycommand,opt_name=opt_name,**keywords)
+ return spawn_bash(mycommand, opt_name=opt_name, **keywords)
args=[SANDBOX_BINARY]
if not opt_name:
- opt_name=mycommand.split()[0]
+ opt_name = os.path.basename(mycommand.split()[0])
args.append(mycommand)
- if not uid:
- uid=os.getuid()
- try:
- os.chown(SANDBOX_PIDS_FILE,uid,portage_data.portage_gid)
- os.chmod(SANDBOX_PIDS_FILE,0664)
- except SystemExit, e:
- raise
- except:
- pass
- return spawn(args,uid=uid,opt_name=opt_name,**keywords)
-
-# base spawn function
-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):
- if type(mycommand)==types.StringType:
- mycommand=mycommand.split()
- myc = mycommand[0]
- if not os.path.isabs(myc) or not os.access(myc, os.X_OK):
- if not path_lookup:
- return None
- myc = find_binary(myc)
- if myc == None:
- return None
-
- mypid=[]
- if logfile:
- pr,pw=os.pipe()
- mypid.extend(spawn(('tee','-i','-a',logfile),returnpid=True,fd_pipes={0:pr,1:1,2:2}))
- retval=os.waitpid(mypid[-1],os.WNOHANG)[1]
- if retval != 0:
- # he's dead jim.
- if (retval & 0xff)==0:
- return (retval >> 8) # exit code
- else:
- return ((retval & 0xff) << 8) # signal
- if not fd_pipes:
- fd_pipes={}
- fd_pipes[0] = 0
- fd_pipes[1]=pw
- fd_pipes[2]=pw
-
- if not opt_name:
- opt_name = mycommand[0]
- myargs=[opt_name]
- myargs.extend(mycommand[1:])
- mypid.append(os.fork())
- if mypid[-1] == 0:
- # this may look ugly, but basically it moves file descriptors around to ensure no
- # handles that are needed are accidentally closed during the final dup2 calls.
- trg_fd=[]
- if type(fd_pipes)==types.DictType:
- src_fd=[]
- k=fd_pipes.keys()
- k.sort()
- for x in k:
- trg_fd.append(x)
- src_fd.append(fd_pipes[x])
- for x in range(0,len(trg_fd)):
- if trg_fd[x] == src_fd[x]:
- continue
- if trg_fd[x] in src_fd[x+1:]:
- new=os.dup2(trg_fd[x],max(src_fd) + 1)
- os.close(trg_fd[x])
- try:
- while True:
- src_fd[s.index(trg_fd[x])]=new
- except SystemExit, e:
- raise
- except:
- pass
- for x in range(0,len(trg_fd)):
- if trg_fd[x] != src_fd[x]:
- os.dup2(src_fd[x], trg_fd[x])
- else:
- trg_fd=[0,1,2]
- for x in range(0,max_fd_limit):
- if x not in trg_fd:
- try:
- os.close(x)
- except SystemExit, e:
- raise
- except:
- pass
- # note this order must be preserved- can't change gid/groups if you change uid first.
- if gid:
- os.setgid(gid)
- if groups:
- os.setgroups(groups)
- if uid:
- os.setuid(uid)
- if umask:
- os.umask(umask)
+ 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:
- # XXX: We would do this to stop ebuild.sh from getting any
- # XXX: output, and consequently, we'd get to handle the sigINT.
- #os.close(sys.stdin.fileno())
- pass
- except SystemExit, e:
- raise
- except:
+ 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:
- #print "execing", myc, myargs
- os.execve(myc,myargs,env)
- except SystemExit, e:
- raise
+ _exec(binary, mycommand, opt_name, fd_pipes,
+ env, gid, groups, uid, umask)
except Exception, e:
- raise str(e)+":\n "+myc+" "+string.join(myargs)
- # If the execve fails, we need to report it, and exit
- # *carefully* --- report error here
- os._exit(1)
- sys.exit(1)
- return # should never get reached
+ # 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(pr)
os.close(pw)
-
+
+ # If the caller wants to handle cleaning up the processes, we tell
+ # it about all processes that were created.
if returnpid:
- global spawned_pids
- spawned_pids.append(mypid[-1])
- return mypid
- while len(mypid):
- retval=os.waitpid(mypid[-1],0)[1]
- if retval != 0:
- for x in mypid[0:-1]:
- try:
- os.kill(x,signal.SIGTERM)
- if os.waitpid(x,os.WNOHANG)[1] == 0:
- # feisty bugger, still alive.
- os.kill(x,signal.SIGKILL)
- os.waitpid(x,0)
- except OSError, oe:
- if oe.errno not in (10,3):
- raise oe
-
- # at this point we've killed all other kid pids generated via this call.
- # return now.
-
- if (retval & 0xff)==0:
- return (retval >> 8) # return exit code
- else:
- return ((retval & 0xff) << 8) # interrupted by signal
- else:
- mypid.pop(-1)
+ 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 find_binary(myc):
- p=os.getenv("PATH")
- if p == None:
- return None
- for x in p.split(":"):
- # if it exists, and is executable
- if os.access("%s/%s" % (x,myc), os.X_OK):
- return "%s/%s" % (x,myc)
- return None
+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