diff options
author | Michael E Brown <michael_e_brown@dell.com> | 2008-01-22 12:32:25 -0600 |
---|---|---|
committer | Michael E Brown <michael_e_brown@dell.com> | 2008-01-22 12:32:25 -0600 |
commit | beed7f3a868c22a9c75064888c0e3f70dc3b523b (patch) | |
tree | a1c1b035ac68303ce8bd5c8c5780b89441aa1f92 /py | |
parent | 041b8dac4983ae69dd5f0edbc9522dce662fb798 (diff) | |
parent | 78d6a209b8dfe438880e4d62a58693e33693560b (diff) | |
download | mock-beed7f3a868c22a9c75064888c0e3f70dc3b523b.tar.gz mock-beed7f3a868c22a9c75064888c0e3f70dc3b523b.tar.xz mock-beed7f3a868c22a9c75064888c0e3f70dc3b523b.zip |
Merge branch 'master' of /var/ftp/pub/Applications/git/mock
* 'master' of /var/ftp/pub/Applications/git/mock:
get rid of one level of shell indirection where possible.
revert accidental comment-out of test cleanup that I was using to debug tar problem.
add back in dropped '.' to tar cvf command that is causing root cache creation to fail. Add debug logging for running commands.
convert mock.util.do() to use subprocess.Popen() rather than raw fork/exec.\nThis cleans up the code considerably. Also, start reducing the places where we use a shell in the subcommand.
better unit test error message.
mount everything when running chroot command.
updated change log
added compat symlinks
version bump
update manpage with new site-defaults ref.
the great config file rename.
some manpage clarifications and arrangements.
clarify info message to make it obvious that root cache is being unpacked.
cleanup trailing whitespace.
cleanup trailing whitespace.
add ability to conditionally enable tmpfs based on minimum ram availability.
add initfailed hook so tmpfs plugin can properly unmount tmpfs on failure. make sure we call postbuild hooks even on failure.
Diffstat (limited to 'py')
-rwxr-xr-x | py/mock.py | 34 | ||||
-rw-r--r-- | py/mock/backend.py | 54 | ||||
-rw-r--r-- | py/mock/exception.py | 4 | ||||
-rw-r--r-- | py/mock/plugins/ccache.py | 2 | ||||
-rw-r--r-- | py/mock/plugins/root_cache.py | 12 | ||||
-rw-r--r-- | py/mock/plugins/tmpfs.py | 24 | ||||
-rw-r--r-- | py/mock/plugins/yum_cache.py | 2 | ||||
-rwxr-xr-x | py/mock/trace_decorator.py | 8 | ||||
-rw-r--r-- | py/mock/util.py | 249 |
9 files changed, 206 insertions, 183 deletions
@@ -102,9 +102,9 @@ def command_parse(config_opts): help="Copy file(s) into the specified chroot") parser.add_option("--copyout", action="store_const", const="copyout", - dest="mode", + dest="mode", help="Copy file(s) from the specified chroot") - + # options parser.add_option("-r", "--root", action="store", type="string", dest="chroot", help="chroot name/config file name default: %default", @@ -212,8 +212,8 @@ def setup_default_config_opts(config_opts, unprivUid): config_opts['cleanup_on_failure'] = 1 # (global) plugins and plugin configs. - # ordering constraings: tmpfs must be first. - # root_cache next. + # ordering constraings: tmpfs must be first. + # root_cache next. # after that, any plugins that must create dirs (yum_cache) # any plugins without preinit hooks should be last. config_opts['plugins'] = ('tmpfs', 'root_cache', 'yum_cache', 'bind_mount', 'ccache') @@ -240,7 +240,7 @@ def setup_default_config_opts(config_opts, unprivUid): # ('/another/host/path', '/another/bind/mount/path/in/chroot/'), ]}, 'tmpfs_enable': False, - 'tmpfs_opts': {}, + 'tmpfs_opts': {'required_ram_mb': 900}, } # dependent on guest OS @@ -411,7 +411,7 @@ def main(ret): setup_default_config_opts(config_opts, unprivUid) (options, args) = command_parse(config_opts) - if options.printrootpath: + if options.printrootpath: options.verbose = 0 # config path -- can be overridden on cmdline @@ -420,7 +420,7 @@ def main(ret): config_path = options.configdir # Read in the config files: default, and then user specified - for cfg in ( os.path.join(config_path, 'defaults.cfg'), '%s/%s.cfg' % (config_path, options.chroot)): + for cfg in ( os.path.join(config_path, 'site-defaults.cfg'), '%s/%s.cfg' % (config_path, options.chroot)): if os.path.exists(cfg): execfile(cfg) else: @@ -496,7 +496,8 @@ def main(ret): log.info("Namespace unshare failed.") # set personality (ie. setarch) - mock.util.condPersonality(config_opts['target_arch']) + if config_opts['internal_setarch']: + mock.util.condPersonality(config_opts['target_arch']) if options.mode == 'init': if config_opts['clean']: @@ -510,8 +511,6 @@ def main(ret): chroot.tryLockBuildRoot() try: chroot._mountall() - if config_opts['internal_setarch']: - mock.util.condPersonality(config_opts['target_arch']) cmd = ' '.join(args) status = os.system("PS1='mock-chroot> ' /usr/sbin/chroot %s %s" % (chroot.makeChrootPath(), cmd)) ret['exitStatus'] = os.WEXITSTATUS(status) @@ -520,16 +519,22 @@ def main(ret): chroot._umountall() elif options.mode == 'chroot': + shell=False if len(args) == 0: log.critical("You must specify a command to run") sys.exit(50) elif len(args) == 1: args = args[0] + shell=True log.info("Running in chroot: %s" % args) chroot.tryLockBuildRoot() chroot._resetLogging() - chroot.doChroot(args) + try: + chroot._mountall() + chroot.doChroot(args, shell=shell) + finally: + chroot._umountall() elif options.mode == 'installdeps': if len(args) == 0: @@ -622,12 +627,7 @@ if __name__ == '__main__': exitStatus = 7 log.error("Exiting on user interrupt, <CTRL>-C") - except (mock.exception.BadCmdline), exc: - exitStatus = exc.resultcode - log.error(str(exc)) - killOrphans = 0 - - except (mock.exception.BuildRootLocked), exc: + except (mock.exception.BadCmdline, mock.exception.BuildRootLocked), exc: exitStatus = exc.resultcode log.error(str(exc)) killOrphans = 0 diff --git a/py/mock/backend.py b/py/mock/backend.py index 9f08c49..7d11532 100644 --- a/py/mock/backend.py +++ b/py/mock/backend.py @@ -147,6 +147,14 @@ class Root(object): decorate(traceLog()) def init(self): + try: + self._init() + except (KeyboardInterrupt, Exception): + self._callHooks('initfailed') + raise + + decorate(traceLog()) + def _init(self): self.state("init") # NOTE: removed the following stuff vs mock v0: @@ -276,8 +284,9 @@ class Root(object): os.mknod( self.makeChrootPath(i[2]), i[0], i[1]) # set context. (only necessary if host running selinux enabled.) # fails gracefully if chcon not installed. - mock.util.do("chcon --reference=/%s %s" % - (i[2], self.makeChrootPath(i[2])), raiseExc=0) + mock.util.do( + ["chcon", "--reference=/%s"% i[2], self.makeChrootPath(i[2])] + , raiseExc=0, shell=False) os.symlink("/proc/self/fd/0", self.makeChrootPath("dev/stdin")) os.symlink("/proc/self/fd/1", self.makeChrootPath("dev/stdout")) @@ -296,9 +305,10 @@ class Root(object): # bad hack # comment out decorator here so we dont get double exceptions in the root log #decorate(traceLog()) - def doChroot(self, command, env="", *args, **kargs): + def doChroot(self, command, env="", shell=True, *args, **kargs): """execute given command in root""" - return mock.util.do( command, chrootPath=self.makeChrootPath(), *args, **kargs ) + return mock.util.do(command, chrootPath=self.makeChrootPath(), + shell=shell, *args, **kargs ) decorate(traceLog()) def yumInstall(self, *srpms): @@ -369,8 +379,8 @@ class Root(object): os.environ["HOME"] = self.homedir # Completely/Permanently drop privs while running the following: self.doChroot( - "rpm -Uvh --nodeps %s" % (srpmChrootFilename,), - uidManager=self.uidManager, + ["rpm", "-Uvh", "--nodeps", srpmChrootFilename], + shell=False, uid=self.chrootuid, gid=self.chrootgid, ) @@ -384,9 +394,9 @@ class Root(object): chrootspec = spec.replace(self.makeChrootPath(), '') # get rid of rootdir prefix # Completely/Permanently drop privs while running the following: self.doChroot( - "bash --login -c 'rpmbuild -bs --target %s --nodeps %s'" % (self.rpmbuild_arch, chrootspec), + ["bash", "--login", "-c", 'rpmbuild -bs --target %s --nodeps %s' % (self.rpmbuild_arch, chrootspec)], + shell=False, logger=self.build_log, timeout=timeout, - uidManager=self.uidManager, uid=self.chrootuid, gid=self.chrootgid, ) @@ -405,9 +415,9 @@ class Root(object): self._callHooks('prebuild') self.doChroot( - "bash --login -c 'rpmbuild -bb --target %s --nodeps %s'" % (self.rpmbuild_arch, chrootspec), + ["bash", "--login", "-c", 'rpmbuild -bb --target %s --nodeps %s' % (self.rpmbuild_arch, chrootspec)], + shell=False, logger=self.build_log, timeout=timeout, - uidManager=self.uidManager, uid=self.chrootuid, gid=self.chrootgid, ) @@ -425,8 +435,8 @@ class Root(object): self.uidManager.restorePrivs() self._umountall() - # tell caching we are done building - self._callHooks('postbuild') + # tell caching we are done building + self._callHooks('postbuild') # ============= # 'Private' API @@ -459,14 +469,14 @@ class Root(object): """mount 'normal' fs like /dev/ /proc/ /sys""" for cmd in self.mountCmds: self.root_log.debug(cmd) - mock.util.do(cmd) + mock.util.do(cmd, shell=True) decorate(traceLog()) def _umountall(self): """umount all mounted chroot fs.""" for cmd in self.umountCmds: self.root_log.debug(cmd) - mock.util.do(cmd, raiseExc=0) + mock.util.do(cmd, raiseExc=0, shell=True) decorate(traceLog()) def _yum(self, cmd, returnOutput=0): @@ -481,7 +491,7 @@ class Root(object): output = "" try: self._callHooks("preyum") - output = mock.util.do(cmd, returnOutput=returnOutput) + output = mock.util.do(cmd, returnOutput=returnOutput, shell=True) self._callHooks("postyum") return output except mock.exception.Error, e: @@ -494,14 +504,16 @@ class Root(object): # safe and easy. blow away existing /builddir and completely re-create. mock.util.rmtree(self.makeChrootPath(self.homedir)) - dets = { 'uid': self.chrootuid, 'gid': self.chrootgid, 'user': self.chrootuser, 'group': self.chrootgroup, 'home': self.homedir } + dets = { 'uid': str(self.chrootuid), 'gid': str(self.chrootgid), 'user': self.chrootuser, 'group': self.chrootgroup, 'home': self.homedir } - self.doChroot('/usr/sbin/userdel -r %(user)s' % dets, raiseExc=False) - self.doChroot('/usr/sbin/groupdel %(group)s' % dets, raiseExc=False) + self.doChroot(['/usr/sbin/userdel', '-r', dets['user']], shell=False, raiseExc=False) + self.doChroot(['/usr/sbin/groupdel', dets['group']], shell=False, raiseExc=False) - self.doChroot('/usr/sbin/groupadd -g %(gid)s %(group)s' % dets) - self.doChroot(self.useradd % dets) - self.doChroot("perl -p -i -e 's/^(%s:)!!/$1/;' /etc/passwd" % (self.chrootuser), raiseExc=True) + self.doChroot(['/usr/sbin/groupadd', '-g', dets['gid'], dets['group']], shell=False) + self.doChroot(self.useradd % dets, shell=True) + self.doChroot( + ["perl", "-p", "-i", "-e", 's/^(%s:)!!/$1/;' % self.chrootuser, "/etc/passwd"], + shell=False, raiseExc=True) decorate(traceLog()) def _resetLogging(self): diff --git a/py/mock/exception.py b/py/mock/exception.py index b6d6282..d02bddb 100644 --- a/py/mock/exception.py +++ b/py/mock/exception.py @@ -19,8 +19,8 @@ class Error(Exception): Exception.__init__(self) self.msg = msg self.resultcode = 1 - if status is not None and os.WIFEXITED(status): - self.resultcode = os.WEXITSTATUS(status) + if status is not None: + self.resultcode = status def __str__(self): return self.msg diff --git a/py/mock/plugins/ccache.py b/py/mock/plugins/ccache.py index b5f3c35..04768f2 100644 --- a/py/mock/plugins/ccache.py +++ b/py/mock/plugins/ccache.py @@ -39,7 +39,7 @@ class CCache(object): # ccache itself manages size and settings. decorate(traceLog()) def _ccacheBuildHook(self): - self.rootObj.doChroot("ccache -M %s" % self.ccache_opts['max_cache_size']) + self.rootObj.doChroot(["ccache", "-M", str(self.ccache_opts['max_cache_size'])], shell=False) # basic idea here is that we add 'cc', 'gcc', 'g++' shell scripts to # to /tmp/ccache, which is bind-mounted from a shared location. diff --git a/py/mock/plugins/root_cache.py b/py/mock/plugins/root_cache.py index 6e9c60e..9cb0a13 100644 --- a/py/mock/plugins/root_cache.py +++ b/py/mock/plugins/root_cache.py @@ -72,9 +72,12 @@ class RootCache(object): # optimization: dont unpack root cache if chroot was not cleaned if os.path.exists(self.rootCacheFile) and self.rootObj.chrootWasCleaned: - self.state("unpacking cache") + self.state("unpacking root cache") self._rootCacheLock() - mock.util.do("tar xzf %s -C %s" % (self.rootCacheFile, self.rootObj.makeChrootPath())) + mock.util.do( + ["tar", "xzf", self.rootCacheFile, "-C", self.rootObj.makeChrootPath()], + shell=False + ) self._rootCacheUnlock() self.chroot_setup_cmd = "update" self.rootObj.chrootWasCleaned = False @@ -85,6 +88,9 @@ class RootCache(object): if self.rootObj.chrootWasCleaned: self.state("creating cache") self._rootCacheLock(shared=0) - mock.util.do("tar czf %s -C %s ." % (self.rootCacheFile, self.rootObj.makeChrootPath())) + mock.util.do( + ["tar", "czf", self.rootCacheFile, "-C", self.rootObj.makeChrootPath(), "."], + shell=False + ) self._rootCacheUnlock() diff --git a/py/mock/plugins/tmpfs.py b/py/mock/plugins/tmpfs.py index b725fb9..cf89bc1 100644 --- a/py/mock/plugins/tmpfs.py +++ b/py/mock/plugins/tmpfs.py @@ -15,7 +15,16 @@ requires_api_version = "1.0" # plugin entry point decorate(traceLog()) def init(rootObj, conf): - Tmpfs(rootObj, conf) + system_ram_bytes = os.sysconf(os.sysconf_names['SC_PAGE_SIZE']) * os.sysconf(os.sysconf_names['SC_PHYS_PAGES']) + system_ram_mb = system_ram_bytes / (1024 * 1024) + if system_ram_mb > conf['required_ram_mb']: + Tmpfs(rootObj, conf) + else: + getLog().warning("Tmpfs plugin disabled. " + "System does not have the required amount of RAM to enable the tmpfs plugin. " + "System has %sMB RAM, but the config specifies the minimum required is %sMB RAM. " + % + (system_ram_mb, conf['required_ram_mb'])) # classes class Tmpfs(object): @@ -26,16 +35,19 @@ class Tmpfs(object): self.conf = conf rootObj.addHook("preinit", self._tmpfsPreInitHook) rootObj.addHook("postbuild", self._tmpfsPostBuildHook) + rootObj.addHook("initfailed", self._tmpfsPostBuildHook) decorate(traceLog()) def _tmpfsPreInitHook(self): getLog().info("mounting tmpfs.") - mountCmd = "mount -n -t tmpfs mock_chroot_tmpfs %s" % self.rootObj.makeChrootPath() - mock.util.do(mountCmd) + mountCmd = ["mount", "-n", "-t", "tmpfs", "mock_chroot_tmpfs", + self.rootObj.makeChrootPath()] + mock.util.do(mountCmd, shell=False) + decorate(traceLog()) def _tmpfsPostBuildHook(self): getLog().info("unmounting tmpfs.") - mountCmd = "umount -n %s" % self.rootObj.makeChrootPath() - mock.util.do(mountCmd) - + mountCmd = ["umount", "-n", self.rootObj.makeChrootPath()] + mock.util.do(mountCmd, shell=False) + diff --git a/py/mock/plugins/yum_cache.py b/py/mock/plugins/yum_cache.py index bcdf465..2e12b3f 100644 --- a/py/mock/plugins/yum_cache.py +++ b/py/mock/plugins/yum_cache.py @@ -83,7 +83,7 @@ class YumCache(object): os.unlink(fullPath) fullPath = None break - + if fullPath is None: continue if file_age_days > self.yum_cache_opts['max_age_days']: os.unlink(fullPath) diff --git a/py/mock/trace_decorator.py b/py/mock/trace_decorator.py index 05c340c..837afd7 100755 --- a/py/mock/trace_decorator.py +++ b/py/mock/trace_decorator.py @@ -18,7 +18,7 @@ class getLog(object): if name is None: frame = sys._getframe(1) name = frame.f_globals["__name__"] - + self.name = prefix + name def __getattr__(self, name): @@ -44,11 +44,11 @@ def traceLog(log = None): # can override by passing logger=foo as function parameter. # make sure this doesnt conflict with one of the parameters # you are expecting - + filename = os.path.normcase(func.func_code.co_filename) func_name = func.func_code.co_name lineno = func.func_code.co_firstlineno - + l2 = kw.get('logger', log) if l2 is None: l2 = logging.getLogger("trace.%s" % func.__module__) @@ -61,7 +61,7 @@ def traceLog(log = None): for k,v in kw.items(): message = message + "%s=%s" % (k,repr(v)) message = message + ")" - + frame = sys._getframe(2) doLog(l2, logging.INFO, os.path.normcase(frame.f_code.co_filename), frame.f_lineno, message, args=[], exc_info=None, func=frame.f_code.co_name) try: diff --git a/py/mock/util.py b/py/mock/util.py index 8f3d221..f93f98b 100644 --- a/py/mock/util.py +++ b/py/mock/util.py @@ -6,19 +6,41 @@ # Copyright (C) 2007 Michael E Brown <mebrown@michaels-house.net> # python library imports +import ctypes import os import os.path import popen2 import rpm import rpmUtils import rpmUtils.transaction +import select import shutil import signal +import subprocess import time # our imports import mock.exception from mock.trace_decorator import traceLog, decorate, getLog +import mock.uid as uid + +_libc = ctypes.cdll.LoadLibrary(None) +_errno = ctypes.c_int.in_dll(_libc, "errno") +_libc.personality.argtypes = [ctypes.c_ulong] +_libc.personality.restype = ctypes.c_int +_libc.unshare.argtypes = [ctypes.c_int,] +_libc.unshare.restype = ctypes.c_int +CLONE_NEWNS = 0x00020000 + +# taken from sys/personality.h +PER_LINUX32=0x0008 +PER_LINUX=0x0000 +personality_defs = { + 'x86_64': PER_LINUX, 'ppc64': PER_LINUX, 'sparc64': PER_LINUX, + 'i386': PER_LINUX32, 'i586': PER_LINUX32, 'i686': PER_LINUX32, + 'ppc': PER_LINUX32, 'sparc': PER_LINUX32, 'sparcv9': PER_LINUX32, + 'ia64' : PER_LINUX, 'alpha' : PER_LINUX, +} # classes class commandTimeoutExpired(mock.exception.Error): @@ -153,29 +175,6 @@ def uniqReqs(*args): master.extend(l) return rpmUtils.miscutils.unique(master) -decorate(traceLog()) -def condChroot(chrootPath, uidManager=None): - if chrootPath is not None: - getLog().debug("chroot %s" % chrootPath) - if uidManager: - getLog().debug("elevate privs to run chroot") - uidManager.becomeUser(0) - os.chdir(chrootPath) - os.chroot(chrootPath) - if uidManager: - getLog().debug("back to other privs") - uidManager.restorePrivs() - -decorate(traceLog()) -def condDropPrivs(uidManager, uid, gid): - if uidManager is not None: - getLog().debug("about to drop privs") - if uid is not None: - uidManager.unprivUid = uid - if gid is not None: - uidManager.unprivGid = gid - uidManager.dropPrivsForever() - # not traced... def chomp(line): if line.endswith("\n"): @@ -183,131 +182,125 @@ def chomp(line): else: return line -# taken from sys/personality.h -PER_LINUX32=0x0008 -PER_LINUX=0x0000 -personality_defs = { - 'x86_64': PER_LINUX, 'ppc64': PER_LINUX, 'sparc64': PER_LINUX, - 'i386': PER_LINUX32, 'i586': PER_LINUX32, 'i686': PER_LINUX32, - 'ppc': PER_LINUX32, 'sparc': PER_LINUX32, 'sparcv9': PER_LINUX32, - 'ia64' : PER_LINUX, 'alpha' : PER_LINUX, -} +decorate(traceLog()) +def unshare(flags): + getLog().debug("Unsharing. Flags: %s" % flags) + try: + res = _libc.unshare(flags) + if res: + raise OSError(_errno.value, os.strerror(_errno.value)) + except AttributeError, e: + pass -import ctypes -_libc = ctypes.cdll.LoadLibrary(None) -_errno = ctypes.c_int.in_dll(_libc, "errno") -_libc.personality.argtypes = [ctypes.c_ulong] -_libc.personality.restype = ctypes.c_int +# these are called in child process, so no logging +def condChroot(chrootPath): + if chrootPath is not None: + saved = { "ruid": os.getuid(), "euid": os.geteuid(), } + uid.setresuid(0,0,0) + os.chdir(chrootPath) + os.chroot(chrootPath) + uid.setresuid(saved['ruid'], saved['euid']) + +def condDropPrivs(uid, gid): + if gid is not None: + os.setregid(gid, gid) + if uid is not None: + os.setreuid(uid, uid) -decorate(traceLog()) def condPersonality(per=None): if per is None or per in ('noarch',): return if personality_defs.get(per, None) is None: - getLog().warning("Unable to find predefined setarch personality constant for '%s' arch." - " You may have to manually run setarch."% per) return res = _libc.personality(personality_defs[per]) if res == -1: raise OSError(_errno.value, os.strerror(_errno.value)) - getLog().debug("Ran setarch '%s'" % per) -CLONE_NEWNS = 0x00020000 -decorate(traceLog()) -def unshare(flags): - getLog().debug("Unsharing. Flags: %s" % flags) - try: - _libc.unshare.argtypes = [ctypes.c_int,] - _libc.unshare.restype = ctypes.c_int - res = _libc.unshare(flags) - if res: - raise OSError(_errno.value, os.strerror(_errno.value)) - except AttributeError, e: - pass +def logOutput(fds, logger, returnOutput=1, start=0, timeout=0): + output="" + done = 0 + while not done: + if (time.time() - start)>timeout and timeout!=0: + done = 1 + break + + i_rdy,o_rdy,e_rdy = select.select(fds,[],[],1) + for s in i_rdy: + # this isnt perfect as a whole line of input may not be + # ready, but should be "good enough" for now + line = s.readline() + if line == "": + done = 1 + break + logger.debug(chomp(line)) + if returnOutput: + output += line + return output # logger = # output = [1|0] # chrootPath # -# Warning: this is the function from hell. :( +# The "Not-as-complicated" version # decorate(traceLog()) -def do(command, chrootPath=None, timeout=0, raiseExc=True, returnOutput=0, uidManager=None, uid=None, gid=None, personality=None, *args, **kargs): - """execute given command outside of chroot""" +def do(command, shell=False, chrootPath=None, timeout=0, raiseExc=True, returnOutput=0, uid=None, gid=None, personality=None, *args, **kargs): logger = kargs.get("logger", getLog()) - logger.debug("run cmd timeout(%s): %s" % (timeout, command)) - - def alarmhandler(signum, stackframe): - raise commandTimeoutExpired("Timeout(%s) exceeded for command: %s" % (timeout, command)) - - retval = 0 - output = "" - (r, w) = os.pipe() - pid = os.fork() - if pid: #parent - rpid = ret = 0 - os.close(w) - oldhandler = signal.signal(signal.SIGALRM, alarmhandler) - # timeout=0 means disable alarm signal. no timeout - signal.alarm(timeout) - - try: - # read output from child - r_fh = os.fdopen(r, "r") - for line in r_fh: - logger.debug(chomp(line)) - - if returnOutput: - output += line - - # close read handle, get child return status, etc - r_fh.close() - (rpid, ret) = os.waitpid(pid, 0) - signal.alarm(0) - signal.signal(signal.SIGALRM, oldhandler) - - # kill children for any exception... - finally: - try: - os.kill(-pid, signal.SIGTERM) - time.sleep(1) - os.kill(-pid, signal.SIGKILL) - except OSError: - pass - signal.signal(signal.SIGALRM, oldhandler) - - # mask and return just return value, plus child output - if raiseExc and ((os.WIFEXITED(ret) and os.WEXITSTATUS(ret)) or os.WIFSIGNALED(ret)): - if returnOutput: - raise mock.exception.Error, ("Command failed: \n # %s\n%s" % (command, output), ret) - else: - raise mock.exception.Error, ("Command failed. See logs for output.\n # %s" % (command,), ret) - - return output + start = time.time() + preexec = ChildPreExec(personality, chrootPath, uid, gid) + try: + child = None + logger.debug("Executing command: %s" % command) + child = subprocess.Popen( + command, + shell=shell, + bufsize=0, close_fds=True, + stdin=open("/dev/null", "r"), + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + preexec_fn = preexec, + ) + + # use select() to poll for output so we dont block + output = logOutput([child.stdout, child.stderr], + logger, returnOutput, start, timeout) + + except: + # kill children if they arent done + if child is not None and child.returncode is None: + os.kill(-child.pid, 15) + os.kill(-child.pid, 9) + raise + + # wait until child is done, kill it if it passes timeout + while child.poll() is None: + if (time.time() - start)>timeout and timeout!=0: + os.kill(-child.pid, 15) + os.kill(-child.pid, 9) + raise commandTimeoutExpired, ("Timeout(%s) expired for command:\n # %s\n%s" % (command, output)) + + + if raiseExc and child.returncode: + if returnOutput: + raise mock.exception.Error, ("Command failed: \n # %s\n%s" % (command, output), child.returncode) + else: + raise mock.exception.Error, ("Command failed. See logs for output.\n # %s" % (command,), child.returncode) + + return output + +class ChildPreExec(object): + def __init__(self, personality, chrootPath, uid, gid): + self.personality = personality + self.chrootPath = chrootPath + self.uid = uid + self.gid = gid + + def __call__(self, *args, **kargs): + os.setpgrp() + condPersonality(self.personality) + condChroot(self.chrootPath) + condDropPrivs(self.uid, self.gid) - else: #child - retval = 255 - try: - os.close(r) - # become process group leader so that our parent - # can kill our children - os.setpgrp() - - condPersonality(personality) - condChroot(chrootPath, uidManager) - condDropPrivs(uidManager, uid, gid) - - child = popen2.Popen4(command) - child.tochild.close() - - w = os.fdopen(w, "w") - for line in child.fromchild: - w.write(line) - w.flush() - w.close() - retval = child.wait() - finally: - os._exit(os.WEXITSTATUS(retval)) |