def _subprocess(args, cwd, env, expected, debug): os.chdir(cwd) os.environ.update(env) portage.const.EPREFIX = env["PORTAGE_OVERRIDE_EPREFIX"] if debug: args = ["-vvvv"] + args repoman_vars = _repoman_init(["repoman"] + args) if repoman_vars.exitcode is not None: return {"returncode": repoman_vars.exitcode} result = _repoman_scan(*repoman_vars) returncode = _handle_result(*repoman_vars, result) qawarnings = repoman_vars.vcs_settings.qatracker.qawarnings warns = collections.defaultdict(list) fails = collections.defaultdict(list) for qacat, issues in repoman_vars.vcs_settings.qatracker.fails.items(): if qacat in qawarnings: warns[qacat].extend(issues) else: fails[qacat].extend(issues) result = {"returncode": returncode} if fails: result["fails"] = fails if warns: result["warns"] = warns return result
def binTestsInit(): binTestsCleanup() global basedir, env basedir = tempfile.mkdtemp() env = os.environ.copy() env["D"] = os.path.join(basedir, "image") env["T"] = os.path.join(basedir, "temp") env["S"] = os.path.join(basedir, "workdir") env["PF"] = "portage-tests-0.09-r1" env["PATH"] = bindir + ":" + env["PATH"] env["PORTAGE_BIN_PATH"] = bindir env["PORTAGE_PYM_PATH"] = pymdir os.mkdir(env["D"]) os.mkdir(env["T"]) os.mkdir(env["S"]) os.chdir(env["S"])
def xpand(myid, mydest): myindex = myid[0] mydata = myid[1] try: origdir = os.getcwd() except SystemExit as e: raise except: os.chdir("/") origdir = "/" os.chdir(mydest) myindexlen = len(myindex) startpos = 0 while ((startpos + 8) < myindexlen): namelen = decodeint(myindex[startpos:startpos + 4]) datapos = decodeint(myindex[startpos + 4 + namelen:startpos + 8 + namelen]) datalen = decodeint(myindex[startpos + 8 + namelen:startpos + 12 + namelen]) myname = myindex[startpos + 4:startpos + 4 + namelen] dirname = os.path.dirname(myname) if dirname: if not os.path.exists(dirname): os.makedirs(dirname) mydat = open( _unicode_encode(myname, encoding=_encodings['fs'], errors='strict'), 'wb') mydat.write(mydata[datapos:datapos + datalen]) mydat.close() startpos = startpos + namelen + 12 os.chdir(origdir)
def xpand(myid,mydest): myindex=myid[0] mydata=myid[1] try: origdir=os.getcwd() except SystemExit as e: raise except: os.chdir("/") origdir="/" os.chdir(mydest) myindexlen=len(myindex) startpos=0 while ((startpos+8)<myindexlen): namelen=decodeint(myindex[startpos:startpos+4]) datapos=decodeint(myindex[startpos+4+namelen:startpos+8+namelen]); datalen=decodeint(myindex[startpos+8+namelen:startpos+12+namelen]); myname=myindex[startpos+4:startpos+4+namelen] dirname=os.path.dirname(myname) if dirname: if not os.path.exists(dirname): os.makedirs(dirname) mydat = open(_unicode_encode(myname, encoding=_encodings['fs'], errors='strict'), 'wb') mydat.write(mydata[datapos:datapos+datalen]) mydat.close() startpos=startpos+namelen+12 os.chdir(origdir)
def unpackinfo(self, mydest): """Unpacks all the files from the dataSegment into 'mydest'.""" if not self.scan(): return 0 try: origdir = os.getcwd() except SystemExit as e: raise except: os.chdir("/") origdir = "/" a = open( _unicode_encode(self.file, encoding=_encodings['fs'], errors='strict'), 'rb') if not os.path.exists(mydest): os.makedirs(mydest) os.chdir(mydest) startpos = 0 while ((startpos + 8) < self.indexsize): namelen = decodeint(self.index[startpos:startpos + 4]) datapos = decodeint(self.index[startpos + 4 + namelen:startpos + 8 + namelen]) datalen = decodeint(self.index[startpos + 8 + namelen:startpos + 12 + namelen]) myname = self.index[startpos + 4:startpos + 4 + namelen] myname = _unicode_decode(myname, encoding=_encodings['repo.content'], errors='replace') dirname = os.path.dirname(myname) if dirname: if not os.path.exists(dirname): os.makedirs(dirname) mydat = open( _unicode_encode(myname, encoding=_encodings['fs'], errors='strict'), 'wb') a.seek(self.datapos + datapos) mydat.write(a.read(datalen)) mydat.close() startpos = startpos + namelen + 12 a.close() os.chdir(origdir) return 1
def unpackinfo(self,mydest): """Unpacks all the files from the dataSegment into 'mydest'.""" if not self.scan(): return 0 try: origdir=os.getcwd() except SystemExit as e: raise except: os.chdir("/") origdir="/" a = open(_unicode_encode(self.file, encoding=_encodings['fs'], errors='strict'), 'rb') if not os.path.exists(mydest): os.makedirs(mydest) os.chdir(mydest) startpos=0 while ((startpos+8)<self.indexsize): namelen=decodeint(self.index[startpos:startpos+4]) datapos=decodeint(self.index[startpos+4+namelen:startpos+8+namelen]); datalen=decodeint(self.index[startpos+8+namelen:startpos+12+namelen]); myname=self.index[startpos+4:startpos+4+namelen] myname = _unicode_decode(myname, encoding=_encodings['repo.content'], errors='replace') dirname=os.path.dirname(myname) if dirname: if not os.path.exists(dirname): os.makedirs(dirname) mydat = open(_unicode_encode(myname, encoding=_encodings['fs'], errors='strict'), 'wb') a.seek(self.datapos+datapos) mydat.write(a.read(datalen)) mydat.close() startpos=startpos+namelen+12 a.close() os.chdir(origdir) return 1
def post_emerge(myaction, myopts, myfiles, target_root, trees, mtimedb, retval): """ Misc. things to run at the end of a merge session. Update Info Files Update Config Files Update News Items Commit mtimeDB Display preserved libs warnings @param myaction: The action returned from parse_opts() @type myaction: String @param myopts: emerge options @type myopts: dict @param myfiles: emerge arguments @type myfiles: list @param target_root: The target EROOT for myaction @type target_root: String @param trees: A dictionary mapping each ROOT to it's package databases @type trees: dict @param mtimedb: The mtimeDB to store data needed across merge invocations @type mtimedb: MtimeDB class instance @param retval: Emerge's return value @type retval: Int """ root_config = trees[target_root]["root_config"] vardbapi = trees[target_root]['vartree'].dbapi settings = vardbapi.settings info_mtimes = mtimedb["info"] # Load the most current variables from ${ROOT}/etc/profile.env settings.unlock() settings.reload() settings.regenerate() settings.lock() config_protect = portage.util.shlex_split( settings.get("CONFIG_PROTECT", "")) infodirs = settings.get("INFOPATH","").split(":") + \ settings.get("INFODIR","").split(":") os.chdir("/") if retval == os.EX_OK: exit_msg = " *** exiting successfully." else: exit_msg = " *** exiting unsuccessfully with status '%s'." % retval emergelog("notitles" not in settings.features, exit_msg) _flush_elog_mod_echo() if not vardbapi._pkgs_changed: # GLEP 42 says to display news *after* an emerge --pretend if "--pretend" in myopts: display_news_notification(root_config, myopts) # If vdb state has not changed then there's nothing else to do. return vdb_path = os.path.join(root_config.settings['EROOT'], portage.VDB_PATH) portage.util.ensure_dirs(vdb_path) vdb_lock = None if os.access(vdb_path, os.W_OK) and not "--pretend" in myopts: vardbapi.lock() vdb_lock = True if vdb_lock: try: if "noinfo" not in settings.features: chk_updated_info_files(target_root, infodirs, info_mtimes) mtimedb.commit() finally: if vdb_lock: vardbapi.unlock() # Explicitly load and prune the PreservedLibsRegistry in order # to ensure that we do not display stale data. vardbapi._plib_registry.load() if vardbapi._plib_registry.hasEntries(): if "--quiet" in myopts: print() print(colorize("WARN", "!!!") + " existing preserved libs found") else: print() print(colorize("WARN", "!!!") + " existing preserved libs:") display_preserved_libs(vardbapi) print("Use " + colorize("GOOD", "emerge @preserved-rebuild") + " to rebuild packages using these libraries") chk_updated_cfg_files(settings['EROOT'], config_protect) display_news_notification(root_config, myopts) postemerge = os.path.join(settings["PORTAGE_CONFIGROOT"], portage.USER_CONFIG_PATH, "bin", "post_emerge") if os.access(postemerge, os.X_OK): hook_retval = portage.process.spawn( [postemerge], env=settings.environ()) if hook_retval != os.EX_OK: portage.util.writemsg_level( " %s spawn failed of %s\n" % (colorize("BAD", "*"), postemerge,), level=logging.ERROR, noiselevel=-1) clean_logs(settings) if "--quiet" not in myopts and \ myaction is None and "@world" in myfiles: show_depclean_suggestion()
# - will do as 'dohtml' but filter on .png,.gif,.html,.htm (default filter # list is ignored) # dohtml -x CVS,SCCS,RCS -r <list-of-files-and-directories> # - will do as 'dohtml -r', but ignore directories named CVS, SCCS, RCS # from __future__ import print_function, unicode_literals import os as _os import sys from portage import _unicode_encode, _unicode_decode, os, shutil from portage.util import normalize_path, writemsg # Change back to original cwd _after_ all imports (bug #469338). os.chdir(os.environ["__PORTAGE_HELPER_CWD"]) def dodir(path): try: os.makedirs(path, 0o755) except OSError: if not os.path.isdir(path): raise os.chmod(path, 0o755) def dofile(src, dst): shutil.copy(src, dst) os.chmod(dst, 0o644)
def pre_exec(): os.chdir(env['S'])
def post_emerge(myaction, myopts, myfiles, target_root, trees, mtimedb, retval): """ Misc. things to run at the end of a merge session. Update Info Files Update Config Files Update News Items Commit mtimeDB Display preserved libs warnings @param myaction: The action returned from parse_opts() @type myaction: String @param myopts: emerge options @type myopts: dict @param myfiles: emerge arguments @type myfiles: list @param target_root: The target EROOT for myaction @type target_root: String @param trees: A dictionary mapping each ROOT to it's package databases @type trees: dict @param mtimedb: The mtimeDB to store data needed across merge invocations @type mtimedb: MtimeDB class instance @param retval: Emerge's return value @type retval: Int """ root_config = trees[target_root]["root_config"] vardbapi = trees[target_root]["vartree"].dbapi settings = vardbapi.settings info_mtimes = mtimedb["info"] # Load the most current variables from ${ROOT}/etc/profile.env settings.unlock() settings.reload() settings.regenerate() settings.lock() config_protect = portage.util.shlex_split( settings.get("CONFIG_PROTECT", "")) infodirs = settings.get("INFOPATH", "").split(":") + settings.get( "INFODIR", "").split(":") os.chdir("/") if retval == os.EX_OK: exit_msg = " *** exiting successfully." else: exit_msg = " *** exiting unsuccessfully with status '%s'." % retval emergelog("notitles" not in settings.features, exit_msg) _flush_elog_mod_echo() if not vardbapi._pkgs_changed: # GLEP 42 says to display news *after* an emerge --pretend if "--pretend" in myopts: display_news_notification(root_config, myopts) # If vdb state has not changed then there's nothing else to do. return vdb_path = os.path.join(root_config.settings["EROOT"], portage.VDB_PATH) portage.util.ensure_dirs(vdb_path) vdb_lock = None if os.access(vdb_path, os.W_OK) and not "--pretend" in myopts: vardbapi.lock() vdb_lock = True if vdb_lock: try: if "noinfo" not in settings.features: chk_updated_info_files(target_root, infodirs, info_mtimes) mtimedb.commit() finally: if vdb_lock: vardbapi.unlock() # Explicitly load and prune the PreservedLibsRegistry in order # to ensure that we do not display stale data. vardbapi._plib_registry.load() if vardbapi._plib_registry.hasEntries(): if "--quiet" in myopts: print() print(colorize("WARN", "!!!") + " existing preserved libs found") else: print() print(colorize("WARN", "!!!") + " existing preserved libs:") display_preserved_libs(vardbapi, verbose="--verbose" in myopts) print("Use " + colorize("GOOD", "emerge @preserved-rebuild") + " to rebuild packages using these libraries") chk_updated_cfg_files(settings["EROOT"], config_protect) display_news_notification(root_config, myopts) postemerge = os.path.join(settings["PORTAGE_CONFIGROOT"], portage.USER_CONFIG_PATH, "bin", "post_emerge") if os.access(postemerge, os.X_OK): hook_retval = portage.process.spawn([postemerge], env=settings.environ()) if hook_retval != os.EX_OK: portage.util.writemsg_level( " %s spawn failed of %s\n" % ( colorize("BAD", "*"), postemerge, ), level=logging.ERROR, noiselevel=-1, ) clean_logs(settings) if "--quiet" not in myopts and myaction is None and "@world" in myfiles: show_depclean_suggestion()
# - will do as 'dohtml' but filter on .png,.gif,.html,.htm (default filter # list is ignored) # dohtml -x CVS,SCCS,RCS -r <list-of-files-and-directories> # - will do as 'dohtml -r', but ignore directories named CVS, SCCS, RCS # from __future__ import print_function, unicode_literals import os as _os import sys from portage import _unicode_encode, _unicode_decode, os, shutil from portage.util import normalize_path, writemsg # Change back to original cwd _after_ all imports (bug #469338). os.chdir(os.environ["__PORTAGE_HELPER_CWD"]) def dodir(path): try: os.makedirs(path, 0o755) except OSError: if not os.path.isdir(path): raise os.chmod(path, 0o755) def dofile(src,dst): shutil.copy(src, dst) os.chmod(dst, 0o644) def eqawarn(lines): cmd = "source '%s/isolated-functions.sh' ; " % \
def post_emerge(root_config, myopts, mtimedb, retval): """ Misc. things to run at the end of a merge session. Update Info Files Update Config Files Update News Items Commit mtimeDB Display preserved libs warnings Exit Emerge @param trees: A dictionary mapping each ROOT to it's package databases @type trees: dict @param mtimedb: The mtimeDB to store data needed across merge invocations @type mtimedb: MtimeDB class instance @param retval: Emerge's return value @type retval: Int @rype: None @returns: 1. Calls sys.exit(retval) """ target_root = root_config.root trees = { target_root : root_config.trees } vardbapi = trees[target_root]["vartree"].dbapi settings = vardbapi.settings info_mtimes = mtimedb["info"] # Load the most current variables from ${ROOT}/etc/profile.env settings.unlock() settings.reload() settings.regenerate() settings.lock() config_protect = settings.get("CONFIG_PROTECT","").split() infodirs = settings.get("INFOPATH","").split(":") + \ settings.get("INFODIR","").split(":") os.chdir("/") if retval == os.EX_OK: exit_msg = " *** exiting successfully." else: exit_msg = " *** exiting unsuccessfully with status '%s'." % retval emergelog("notitles" not in settings.features, exit_msg) _flush_elog_mod_echo() if not vardbapi._pkgs_changed: display_news_notification(root_config, myopts) # If vdb state has not changed then there's nothing else to do. sys.exit(retval) vdb_path = os.path.join(target_root, portage.VDB_PATH) portage.util.ensure_dirs(vdb_path) vdb_lock = None if os.access(vdb_path, os.W_OK) and not "--pretend" in myopts: vdb_lock = portage.locks.lockdir(vdb_path) if vdb_lock: try: if "noinfo" not in settings.features: chk_updated_info_files(target_root, infodirs, info_mtimes, retval) mtimedb.commit() finally: if vdb_lock: portage.locks.unlockdir(vdb_lock) chk_updated_cfg_files(target_root, config_protect) display_news_notification(root_config, myopts) if retval in (None, os.EX_OK) or (not "--pretend" in myopts): display_preserved_libs(vardbapi, myopts) sys.exit(retval)
def pre_exec(): os.chdir(env["S"])
def _exec(binary, mycommand, opt_name, fd_pipes, env, gid, groups, uid, umask, cwd, pre_exec, close_fds, unshare_net, unshare_ipc, unshare_mount, unshare_pid, unshare_flags, cgroup): """ Execute a given binary with options @param binary: Name of program to execute @type binary: String @param mycommand: Options for program @type mycommand: String @param opt_name: Name of process (defaults to binary) @type opt_name: String @param fd_pipes: Mapping pipes to destination; { 0:0, 1:1, 2:2 } @type fd_pipes: Dictionary @param env: Key,Value mapping for Environmental Variables @type env: Dictionary @param gid: Group ID to run the process under @type gid: Integer @param groups: Groups the Process should be in. @type groups: List @param uid: User ID to run the process under @type uid: Integer @param umask: an int representing a unix umask (see man chmod for umask details) @type umask: Integer @param cwd: Current working directory @type cwd: String @param pre_exec: A function to be called with no arguments just prior to the exec call. @type pre_exec: callable @param unshare_net: If True, networking will be unshared from the spawned process @type unshare_net: Boolean @param unshare_ipc: If True, IPC will be unshared from the spawned process @type unshare_ipc: Boolean @param unshare_mount: If True, mount namespace will be unshared and mounts will be private to the namespace @type unshare_mount: Boolean @param unshare_pid: If True, PID ns will be unshared from the spawned process @type unshare_pid: Boolean @param unshare_flags: Flags for the unshare(2) function @type unshare_flags: Integer @param cgroup: CGroup path to bind the process to @type cgroup: String @rtype: None @return: Never returns (calls os.execve) """ # If the process we're creating hasn't been given a name # assign it the name of the executable. if not opt_name: if binary is portage._python_interpreter: # NOTE: PyPy 1.7 will die due to "libary path not found" if argv[0] # does not contain the full path of the binary. opt_name = binary else: opt_name = os.path.basename(binary) # Set up the command's argument list. myargs = [opt_name] myargs.extend(mycommand[1:]) # Avoid a potential UnicodeEncodeError from os.execve(). myargs = [_unicode_encode(x, encoding=_encodings['fs'], errors='strict') for x in myargs] # Use default signal handlers in order to avoid problems # killing subprocesses as reported in bug #353239. signal.signal(signal.SIGINT, signal.SIG_DFL) signal.signal(signal.SIGTERM, signal.SIG_DFL) # Unregister SIGCHLD handler and wakeup_fd for the parent # process's event loop (bug 655656). signal.signal(signal.SIGCHLD, signal.SIG_DFL) try: wakeup_fd = signal.set_wakeup_fd(-1) if wakeup_fd > 0: os.close(wakeup_fd) except (ValueError, OSError): pass # Quiet killing of subprocesses by SIGPIPE (see bug #309001). signal.signal(signal.SIGPIPE, signal.SIG_DFL) # Avoid issues triggered by inheritance of SIGQUIT handler from # the parent process (see bug #289486). signal.signal(signal.SIGQUIT, signal.SIG_DFL) _setup_pipes(fd_pipes, close_fds=close_fds, inheritable=True) # Add to cgroup # it's better to do it from the child since we can guarantee # it is done before we start forking children if cgroup: with open(os.path.join(cgroup, 'cgroup.procs'), 'a') as f: f.write('%d\n' % os.getpid()) # Unshare (while still uid==0) if unshare_net or unshare_ipc or unshare_mount or unshare_pid: filename = find_library("c") if filename is not None: libc = LoadLibrary(filename) if libc is not None: try: # Since a failed unshare call could corrupt process # state, first validate that the call can succeed. # The parent process should call _unshare_validate # before it forks, so that all child processes can # reuse _unshare_validate results that have been # cached by the parent process. errno_value = _unshare_validate(unshare_flags) if errno_value == 0 and libc.unshare(unshare_flags) != 0: errno_value = ctypes.get_errno() if errno_value != 0: writemsg("Unable to unshare: %s\n" % ( errno.errorcode.get(errno_value, '?')), noiselevel=-1) else: if unshare_pid: main_child_pid = os.fork() if main_child_pid == 0: # pid namespace requires us to become init binary, myargs = portage._python_interpreter, [ portage._python_interpreter, os.path.join(portage._bin_path, 'pid-ns-init'), _unicode_encode('' if uid is None else str(uid)), _unicode_encode('' if gid is None else str(gid)), _unicode_encode('' if groups is None else ','.join(str(group) for group in groups)), _unicode_encode('' if umask is None else str(umask)), _unicode_encode(','.join(str(fd) for fd in fd_pipes)), binary] + myargs uid = None gid = None groups = None umask = None else: # Execute a supervisor process which will forward # signals to init and forward exit status to the # parent process. The supervisor process runs in # the global pid namespace, so skip /proc remount # and other setup that's intended only for the # init process. binary, myargs = portage._python_interpreter, [ portage._python_interpreter, os.path.join(portage._bin_path, 'pid-ns-init'), str(main_child_pid)] os.execve(binary, myargs, env) if unshare_mount: # mark the whole filesystem as slave to avoid # mounts escaping the namespace s = subprocess.Popen(['mount', '--make-rslave', '/']) mount_ret = s.wait() if mount_ret != 0: # TODO: should it be fatal maybe? writemsg("Unable to mark mounts slave: %d\n" % (mount_ret,), noiselevel=-1) if unshare_pid: # we need at least /proc being slave s = subprocess.Popen(['mount', '--make-slave', '/proc']) mount_ret = s.wait() if mount_ret != 0: # can't proceed with shared /proc writemsg("Unable to mark /proc slave: %d\n" % (mount_ret,), noiselevel=-1) os._exit(1) # mount new /proc for our namespace s = subprocess.Popen(['mount', '-n', '-t', 'proc', 'proc', '/proc']) mount_ret = s.wait() if mount_ret != 0: writemsg("Unable to mount new /proc: %d\n" % (mount_ret,), noiselevel=-1) os._exit(1) if unshare_net: # 'up' the loopback IFF_UP = 0x1 ifreq = struct.pack('16sh', b'lo', IFF_UP) SIOCSIFFLAGS = 0x8914 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0) try: fcntl.ioctl(sock, SIOCSIFFLAGS, ifreq) except IOError as e: writemsg("Unable to enable loopback interface: %s\n" % ( errno.errorcode.get(e.errno, '?')), noiselevel=-1) sock.close() except AttributeError: # unshare() not supported by libc pass # Set requested process permissions. if gid: # Cast proxies to int, in case it matters. os.setgid(int(gid)) if groups: os.setgroups(groups) if uid: # Cast proxies to int, in case it matters. os.setuid(int(uid)) if umask: os.umask(umask) if cwd is not None: os.chdir(cwd) if pre_exec: pre_exec() # And switch to the new process. os.execve(binary, myargs, env)
def _exec(binary, mycommand, opt_name, fd_pipes, env, gid, groups, uid, umask, cwd, pre_exec, close_fds, unshare_net, unshare_ipc, unshare_mount, unshare_pid, unshare_flags, cgroup): """ Execute a given binary with options @param binary: Name of program to execute @type binary: String @param mycommand: Options for program @type mycommand: String @param opt_name: Name of process (defaults to binary) @type opt_name: String @param fd_pipes: Mapping pipes to destination; { 0:0, 1:1, 2:2 } @type fd_pipes: Dictionary @param env: Key,Value mapping for Environmental Variables @type env: Dictionary @param gid: Group ID to run the process under @type gid: Integer @param groups: Groups the Process should be in. @type groups: List @param uid: User ID to run the process under @type uid: Integer @param umask: an int representing a unix umask (see man chmod for umask details) @type umask: Integer @param cwd: Current working directory @type cwd: String @param pre_exec: A function to be called with no arguments just prior to the exec call. @type pre_exec: callable @param unshare_net: If True, networking will be unshared from the spawned process @type unshare_net: Boolean @param unshare_ipc: If True, IPC will be unshared from the spawned process @type unshare_ipc: Boolean @param unshare_mount: If True, mount namespace will be unshared and mounts will be private to the namespace @type unshare_mount: Boolean @param unshare_pid: If True, PID ns will be unshared from the spawned process @type unshare_pid: Boolean @param unshare_flags: Flags for the unshare(2) function @type unshare_flags: Integer @param cgroup: CGroup path to bind the process to @type cgroup: String @rtype: None @return: Never returns (calls os.execve) """ # If the process we're creating hasn't been given a name # assign it the name of the executable. if not opt_name: if binary is portage._python_interpreter: # NOTE: PyPy 1.7 will die due to "libary path not found" if argv[0] # does not contain the full path of the binary. opt_name = binary else: opt_name = os.path.basename(binary) # Set up the command's argument list. myargs = [opt_name] myargs.extend(mycommand[1:]) # Avoid a potential UnicodeEncodeError from os.execve(). myargs = [_unicode_encode(x, encoding=_encodings['fs'], errors='strict') for x in myargs] # Use default signal handlers in order to avoid problems # killing subprocesses as reported in bug #353239. signal.signal(signal.SIGINT, signal.SIG_DFL) signal.signal(signal.SIGTERM, signal.SIG_DFL) # Unregister SIGCHLD handler and wakeup_fd for the parent # process's event loop (bug 655656). signal.signal(signal.SIGCHLD, signal.SIG_DFL) try: wakeup_fd = signal.set_wakeup_fd(-1) if wakeup_fd > 0: os.close(wakeup_fd) except (ValueError, OSError): pass # Quiet killing of subprocesses by SIGPIPE (see bug #309001). signal.signal(signal.SIGPIPE, signal.SIG_DFL) # Avoid issues triggered by inheritance of SIGQUIT handler from # the parent process (see bug #289486). signal.signal(signal.SIGQUIT, signal.SIG_DFL) _setup_pipes(fd_pipes, close_fds=close_fds, inheritable=True) # Add to cgroup # it's better to do it from the child since we can guarantee # it is done before we start forking children if cgroup: with open(os.path.join(cgroup, 'cgroup.procs'), 'a') as f: f.write('%d\n' % os.getpid()) # Unshare (while still uid==0) if unshare_net or unshare_ipc or unshare_mount or unshare_pid: filename = find_library("c") if filename is not None: libc = LoadLibrary(filename) if libc is not None: try: # Since a failed unshare call could corrupt process # state, first validate that the call can succeed. # The parent process should call _unshare_validate # before it forks, so that all child processes can # reuse _unshare_validate results that have been # cached by the parent process. errno_value = _unshare_validate(unshare_flags) if errno_value == 0 and libc.unshare(unshare_flags) != 0: errno_value = ctypes.get_errno() if errno_value != 0: involved_features = [] if unshare_ipc: involved_features.append('ipc-sandbox') if unshare_mount: involved_features.append('mount-sandbox') if unshare_net: involved_features.append('network-sandbox') if unshare_pid: involved_features.append('pid-sandbox') writemsg("Unable to unshare: %s (for FEATURES=\"%s\")\n" % ( errno.errorcode.get(errno_value, '?'), ' '.join(involved_features)), noiselevel=-1) else: if unshare_pid: main_child_pid = os.fork() if main_child_pid == 0: # pid namespace requires us to become init binary, myargs = portage._python_interpreter, [ portage._python_interpreter, os.path.join(portage._bin_path, 'pid-ns-init'), _unicode_encode('' if uid is None else str(uid)), _unicode_encode('' if gid is None else str(gid)), _unicode_encode('' if groups is None else ','.join(str(group) for group in groups)), _unicode_encode('' if umask is None else str(umask)), _unicode_encode(','.join(str(fd) for fd in fd_pipes)), binary] + myargs uid = None gid = None groups = None umask = None else: # Execute a supervisor process which will forward # signals to init and forward exit status to the # parent process. The supervisor process runs in # the global pid namespace, so skip /proc remount # and other setup that's intended only for the # init process. binary, myargs = portage._python_interpreter, [ portage._python_interpreter, os.path.join(portage._bin_path, 'pid-ns-init'), str(main_child_pid)] os.execve(binary, myargs, env) if unshare_mount: # mark the whole filesystem as slave to avoid # mounts escaping the namespace s = subprocess.Popen(['mount', '--make-rslave', '/']) mount_ret = s.wait() if mount_ret != 0: # TODO: should it be fatal maybe? writemsg("Unable to mark mounts slave: %d\n" % (mount_ret,), noiselevel=-1) if unshare_pid: # we need at least /proc being slave s = subprocess.Popen(['mount', '--make-slave', '/proc']) mount_ret = s.wait() if mount_ret != 0: # can't proceed with shared /proc writemsg("Unable to mark /proc slave: %d\n" % (mount_ret,), noiselevel=-1) os._exit(1) # mount new /proc for our namespace s = subprocess.Popen(['mount', '-n', '-t', 'proc', 'proc', '/proc']) mount_ret = s.wait() if mount_ret != 0: writemsg("Unable to mount new /proc: %d\n" % (mount_ret,), noiselevel=-1) os._exit(1) if unshare_net: # use 'localhost' to avoid hostname resolution problems try: socket.sethostname('localhost') except Exception as e: writemsg("Unable to set hostname: %s (for FEATURES=\"network-sandbox\")\n" % ( e,), noiselevel=-1) _configure_loopback_interface() except AttributeError: # unshare() not supported by libc pass # Set requested process permissions. if gid: # Cast proxies to int, in case it matters. os.setgid(int(gid)) if groups: os.setgroups(groups) if uid: # Cast proxies to int, in case it matters. os.setuid(int(uid)) if umask: os.umask(umask) if cwd is not None: os.chdir(cwd) if pre_exec: pre_exec() # And switch to the new process. os.execve(binary, myargs, env)
def _exec(binary, mycommand, opt_name, fd_pipes, env, gid, groups, uid, umask, cwd, pre_exec, close_fds, unshare_net, unshare_ipc, unshare_mount, unshare_pid, cgroup): """ Execute a given binary with options @param binary: Name of program to execute @type binary: String @param mycommand: Options for program @type mycommand: String @param opt_name: Name of process (defaults to binary) @type opt_name: String @param fd_pipes: Mapping pipes to destination; { 0:0, 1:1, 2:2 } @type fd_pipes: Dictionary @param env: Key,Value mapping for Environmental Variables @type env: Dictionary @param gid: Group ID to run the process under @type gid: Integer @param groups: Groups the Process should be in. @type groups: Integer @param uid: User ID to run the process under @type uid: Integer @param umask: an int representing a unix umask (see man chmod for umask details) @type umask: Integer @param cwd: Current working directory @type cwd: String @param pre_exec: A function to be called with no arguments just prior to the exec call. @type pre_exec: callable @param unshare_net: If True, networking will be unshared from the spawned process @type unshare_net: Boolean @param unshare_ipc: If True, IPC will be unshared from the spawned process @type unshare_ipc: Boolean @param unshare_mount: If True, mount namespace will be unshared and mounts will be private to the namespace @type unshare_mount: Boolean @param unshare_pid: If True, PID ns will be unshared from the spawned process @type unshare_pid: Boolean @param cgroup: CGroup path to bind the process to @type cgroup: String @rtype: None @return: Never returns (calls os.execve) """ # If the process we're creating hasn't been given a name # assign it the name of the executable. if not opt_name: if binary is portage._python_interpreter: # NOTE: PyPy 1.7 will die due to "libary path not found" if argv[0] # does not contain the full path of the binary. opt_name = binary else: opt_name = os.path.basename(binary) # Set up the command's argument list. myargs = [opt_name] myargs.extend(mycommand[1:]) # Avoid a potential UnicodeEncodeError from os.execve(). myargs = [ _unicode_encode(x, encoding=_encodings['fs'], errors='strict') for x in myargs ] # Use default signal handlers in order to avoid problems # killing subprocesses as reported in bug #353239. signal.signal(signal.SIGINT, signal.SIG_DFL) signal.signal(signal.SIGTERM, signal.SIG_DFL) # Unregister SIGCHLD handler and wakeup_fd for the parent # process's event loop (bug 655656). signal.signal(signal.SIGCHLD, signal.SIG_DFL) try: wakeup_fd = signal.set_wakeup_fd(-1) if wakeup_fd > 0: os.close(wakeup_fd) except (ValueError, OSError): pass # Quiet killing of subprocesses by SIGPIPE (see bug #309001). signal.signal(signal.SIGPIPE, signal.SIG_DFL) # Avoid issues triggered by inheritance of SIGQUIT handler from # the parent process (see bug #289486). signal.signal(signal.SIGQUIT, signal.SIG_DFL) _setup_pipes(fd_pipes, close_fds=close_fds, inheritable=True) # Add to cgroup # it's better to do it from the child since we can guarantee # it is done before we start forking children if cgroup: with open(os.path.join(cgroup, 'cgroup.procs'), 'a') as f: f.write('%d\n' % os.getpid()) # Unshare (while still uid==0) if unshare_net or unshare_ipc or unshare_mount or unshare_pid: filename = find_library("c") if filename is not None: libc = LoadLibrary(filename) if libc is not None: # from /usr/include/bits/sched.h CLONE_NEWNS = 0x00020000 CLONE_NEWIPC = 0x08000000 CLONE_NEWPID = 0x20000000 CLONE_NEWNET = 0x40000000 flags = 0 if unshare_net: flags |= CLONE_NEWNET if unshare_ipc: flags |= CLONE_NEWIPC if unshare_mount: # NEWNS = mount namespace flags |= CLONE_NEWNS if unshare_pid: # we also need mount namespace for slave /proc flags |= CLONE_NEWPID | CLONE_NEWNS try: if libc.unshare(flags) != 0: writemsg( "Unable to unshare: %s\n" % (errno.errorcode.get(ctypes.get_errno(), '?')), noiselevel=-1) else: if unshare_pid: # pid namespace requires us to become init fork_ret = os.fork() if fork_ret != 0: os.execv(portage._python_interpreter, [ portage._python_interpreter, os.path.join(portage._bin_path, 'pid-ns-init'), '%s' % fork_ret, ]) if unshare_mount: # mark the whole filesystem as slave to avoid # mounts escaping the namespace s = subprocess.Popen( ['mount', '--make-rslave', '/']) mount_ret = s.wait() if mount_ret != 0: # TODO: should it be fatal maybe? writemsg("Unable to mark mounts slave: %d\n" % (mount_ret, ), noiselevel=-1) if unshare_pid: # we need at least /proc being slave s = subprocess.Popen( ['mount', '--make-slave', '/proc']) mount_ret = s.wait() if mount_ret != 0: # can't proceed with shared /proc writemsg("Unable to mark /proc slave: %d\n" % (mount_ret, ), noiselevel=-1) os._exit(1) # mount new /proc for our namespace s = subprocess.Popen( ['mount', '-t', 'proc', 'proc', '/proc']) mount_ret = s.wait() if mount_ret != 0: writemsg("Unable to mount new /proc: %d\n" % (mount_ret, ), noiselevel=-1) os._exit(1) if unshare_net: # 'up' the loopback IFF_UP = 0x1 ifreq = struct.pack('16sh', b'lo', IFF_UP) SIOCSIFFLAGS = 0x8914 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0) try: fcntl.ioctl(sock, SIOCSIFFLAGS, ifreq) except IOError as e: writemsg( "Unable to enable loopback interface: %s\n" % (errno.errorcode.get(e.errno, '?')), noiselevel=-1) sock.close() except AttributeError: # unshare() not supported by libc pass # Set requested process permissions. if gid: # Cast proxies to int, in case it matters. os.setgid(int(gid)) if groups: os.setgroups(groups) if uid: # Cast proxies to int, in case it matters. os.setuid(int(uid)) if umask: os.umask(umask) if cwd is not None: os.chdir(cwd) if pre_exec: pre_exec() # And switch to the new process. os.execve(binary, myargs, env)