From 581de87ec02778e95aaf9720b4a69e1ba5852a18 Mon Sep 17 00:00:00 2001 From: Jason Stubbs Date: Thu, 3 Nov 2005 14:34:08 +0000 Subject: [PATCH] A couple of bug fixes, large cleanups, commenting and a bit of dead code removal. svn path=/main/branches/2.0/; revision=2254 --- pym/portage_exec.py | 361 ++++++++++++++++++++++---------------------- 1 file changed, 184 insertions(+), 177 deletions(-) diff --git a/pym/portage_exec.py b/pym/portage_exec.py index 8a92c7ae2..c62d0a549 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 -- 2.26.2