fi
if [[ ${input} == -5 ]] ; then
input=-3
- export mv_opts=""
+ export mv_opts=" ${mv_opts} "
+ mv_opts="${mv_opts// -i / }"
fi
if [[ ${input} == -3 ]] ; then
input=0
PORTAGE_TMPDIR=@DOMAIN_PREFIX@/var/tmp
# Fetching command (5 tries, passive ftp for firewall compatibility)
-FETCHCOMMAND="wget -t 5 --passive-ftp -P \${DISTDIR} \${URI}"
-RESUMECOMMAND="wget -c -t 5 --passive-ftp -P \${DISTDIR} \${URI}"
+FETCHCOMMAND="wget -t 5 -T 60 --passive-ftp -O \${DISTDIR}/\${FILE} \${URI}"
+RESUMECOMMAND="wget -c -t 5 -T 60 --passive-ftp -O \${DISTDIR}/\${FILE} \${URI}"
# Default user options
FEATURES="sandbox distlocks metadata-transfer"
a sync operation.
.TP
.BR "\-\-resume"
-Resumes the last merge operation. Please note that this operation
-will only return an error on failure. If there is nothing for portage
-to do, then portage will exit with a message and a success condition.
+Resumes the most recent merge list that has been aborted due to an error.
+Please note that this operation will only return an error on failure. If there
+is nothing for portage to do, then portage will exit with a message and a
+success condition. A resume list will persist until it has been completed in
+entirety or until another aborted merge list replaces it. The resume history
+is capable of storing two merge lists. After one resume list completes, it is
+possible to invoke \-\-resume once again in order to resume an older list.
.TP
.BR "\-\-search " (\fB\-s\fR)
Searches for matches of the supplied string in the portage tree.
depth >= mylist[i+1][1]:
del mylist[i]
+ from portage import flatten
+ from portage.dep import use_reduce, paren_reduce
display_overlays=False
# files to fetch list - avoids counting a same file twice
# in size display (verbose mode)
blockers.append(addl)
else:
mydbapi = self.trees[myroot][self.pkg_tree_map[pkg_type]].dbapi
+ pkg_status = x[3]
binary_package = True
if "ebuild" == pkg_type:
if "merge" == x[3] or \
self.useFlags[myroot][pkg_key] = mydbapi.aux_get(
pkg_key, ["USE"])[0].split()
+ try:
+ restrict = flatten(use_reduce(paren_reduce(
+ mydbapi.aux_get(pkg_key, ["RESTRICT"])[0]),
+ uselist=self.useFlags[myroot][pkg_key]))
+ except portage.exception.InvalidDependString, e:
+ if pkg_status != "nomerge":
+ restrict = mydbapi.aux_get(pkg_key, ["RESTRICT"])[0]
+ show_invalid_depstring_notice(x, restrict, str(e))
+ del e
+ sys.exit(1)
+ restrict = []
if "ebuild" == pkg_type and x[3] != "nomerge" and \
- "fetch" in portdb.aux_get(
- x[2], ["RESTRICT"])[0].split():
+ "fetch" in restrict:
fetch = red("F")
if ordered:
counters.restrict_fetch += 1
else:
verboseadd += "[No ebuild?]"
- xs=portage.pkgsplit(x[2])
+ xs = list(portage.pkgsplit(x[2]))
if xs[2]=="r0":
xs[2]=""
else:
print " Rsync users should simply 'emerge --sync' to regenerate."
print
print " "+green("--resume")
- print " Resumes the last merge operation. It can be treated just like a"
- print " regular emerge: --pretend and other options work alongside it."
- print " 'emerge --resume' only returns an error on failure. When there is"
- print " nothing to do, it exits with a message and a success condition."
+ print " Resumes the most recent merge list that has been aborted due to an"
+ print " error. Please note that this operation will only return an error"
+ print " on failure. If there is nothing for portage to do, then portage"
+ print " will exit with a message and a success condition. A resume list"
+ print " will persist until it has been completed in entirety or until"
+ print " another aborted merge list replaces it. The resume history is"
+ print " capable of storing two merge lists. After one resume list"
+ print " completes, it is possible to invoke --resume once again in order"
+ print " to resume an older list."
print
print " "+green("--search")+" ("+green("-s")+" short option)"
print " Searches for matches of the supplied string in the current local"
mylist = []
for myconfig in config_list:
if var in myconfig:
- mylist.extend(filter(None, myconfig[var].split()))
+ for item in myconfig[var].split():
+ if item and not item in mylist:
+ mylist.append(item)
del myconfig[var] # prepare for env.update(myconfig)
if mylist:
env[var] = " ".join(mylist)
mylist = []
for myconfig in config_list:
if var in myconfig:
- mylist.extend(filter(None, myconfig[var].split(":")))
+ for item in myconfig[var].split(":"):
+ if item and not item in mylist:
+ mylist.append(item)
del myconfig[var] # prepare for env.update(myconfig)
if mylist:
env[var] = ":".join(mylist)
slave_fd = None
output_pid = None
input_pid = None
+ stdin_termios = None
+ stdin_fd = None
if logfile:
del keywords["logfile"]
fd_pipes = keywords.get("fd_pipes")
raise ValueError(fd_pipes)
from pty import openpty
master_fd, slave_fd = openpty()
- # Disable the ECHO attribute so the terminal behaves properly
- # if the subprocess needs to read input from stdin.
- import termios
- term_attr = termios.tcgetattr(slave_fd)
- term_attr[3] &= ~termios.ECHO
- termios.tcsetattr(slave_fd, termios.TCSAFLUSH, term_attr)
+ fd_pipes.setdefault(0, sys.stdin.fileno())
+ stdin_fd = fd_pipes[0]
+ if os.isatty(stdin_fd):
+ # Copy the termios attributes from stdin_fd to the slave_fd and put
+ # the stdin_fd into raw mode with ECHO disabled. The stdin
+ # termios attributes are reverted before returning, or via the
+ # atexit hook in portage.process when killed by a signal.
+ import termios, tty
+ stdin_termios = termios.tcgetattr(stdin_fd)
+ tty.setraw(stdin_fd)
+ term_attr = termios.tcgetattr(stdin_fd)
+ term_attr[3] &= ~termios.ECHO
+ termios.tcsetattr(stdin_fd, termios.TCSAFLUSH, term_attr)
+ termios.tcsetattr(slave_fd, termios.TCSAFLUSH, stdin_termios)
+ from output import get_term_size, set_term_size
+ rows, columns = get_term_size()
+ set_term_size(rows, columns, slave_fd)
+ pre_exec = keywords.get("pre_exec")
+ def setup_ctty():
+ os.setsid()
+ # Make it into the "controlling terminal".
+ import termios
+ if hasattr(termios, "TIOCSCTTY"):
+ # BSD 4.3 approach
+ import fcntl
+ fcntl.ioctl(0, termios.TIOCSCTTY)
+ else:
+ # SVR4 approach
+ fd = os.open(os.ttyname(0), os.O_RDWR)
+ for x in 0, 1, 2:
+ os.dup2(fd, x)
+ os.close(fd)
+ if pre_exec:
+ pre_exec()
+ keywords["pre_exec"] = setup_ctty
# tee will always exit with an IO error, so ignore it's stderr.
null_file = open('/dev/null', 'w')
mypids.extend(portage.process.spawn(['tee', '-i', '-a', logfile],
keywords["fd_pipes"] = fd_pipes
features = mysettings.features
- restrict = mysettings.get("RESTRICT", "").split()
+ restrict = mysettings.get("PORTAGE_RESTRICT","").split()
droppriv=(droppriv and "userpriv" in features and not \
("nouserpriv" in restrict or "userpriv" in restrict))
if droppriv and not uid and portage_gid and portage_uid:
os.waitpid(input_pid, 0)
portage.process.spawned_pids.remove(input_pid)
pid = mypids[-1]
- retval = os.waitpid(pid, 0)[1]
- portage.process.spawned_pids.remove(pid)
+ try:
+ retval = os.waitpid(pid, 0)[1]
+ portage.process.spawned_pids.remove(pid)
+ finally:
+ if stdin_termios:
+ termios.tcsetattr(stdin_fd, termios.TCSAFLUSH, stdin_termios)
if retval != os.EX_OK:
if retval & 0xff:
return (retval & 0xff) << 8
"fetch files. Will use digest file if available."
features = mysettings.features
+ restrict = mysettings.get("PORTAGE_RESTRICT","").split()
# 'nomirror' is bad/negative logic. You Restrict mirroring, not no-mirroring.
- if ("mirror" in mysettings["RESTRICT"].split()) or \
- ("nomirror" in mysettings["RESTRICT"].split()):
+ if "mirror" in restrict or \
+ "nomirror" in restrict:
if ("mirror" in features) and ("lmirror" not in features):
# lmirror should allow you to bypass mirror restrictions.
# XXX: This is not a good thing, and is temporary at best.
if custommirrors.has_key("local"):
mymirrors += custommirrors["local"]
- if ("nomirror" in mysettings["RESTRICT"].split()) or \
- ("mirror" in mysettings["RESTRICT"].split()):
+ if "nomirror" in restrict or \
+ "mirror" in restrict:
# We don't add any mirrors.
pass
else:
fsmirrors += [mymirrors[x]]
del mymirrors[x]
- restrict_fetch = "fetch" in mysettings["RESTRICT"].split()
+ restrict_fetch = "fetch" in restrict
custom_local_mirrors = custommirrors.get("local", [])
if restrict_fetch:
# With fetch restriction, a normal uri may only be fetched from
if restrict_fetch:
# Only fetch from specific mirrors is allowed.
continue
- if "primaryuri" in mysettings["RESTRICT"].split():
+ if "primaryuri" in restrict:
# Use the source site first.
if primaryuri_indexes.has_key(myfile):
primaryuri_indexes[myfile] += 1
del mysettings["PORT_LOGDIR"]
if "PORT_LOGDIR" in mysettings:
try:
- portage.util.ensure_dirs(mysettings["PORT_LOGDIR"],
- uid=portage_uid, gid=portage_gid, mode=02770)
+ modified = portage.util.ensure_dirs(mysettings["PORT_LOGDIR"])
+ if modified:
+ apply_secpass_permissions(mysettings["PORT_LOGDIR"],
+ uid=portage_uid, gid=portage_gid, mode=02770)
except portage.exception.PortageException, e:
writemsg("!!! %s\n" % str(e), noiselevel=-1)
writemsg("!!! Permission issues with PORT_LOGDIR='%s'\n" % \
#initial dep checks complete; time to process main commands
+ restrict = mysettings["PORTAGE_RESTRICT"].split()
nosandbox = (("userpriv" in features) and \
("usersandbox" not in features) and \
- ("userpriv" not in mysettings["RESTRICT"]) and \
- ("nouserpriv" not in mysettings["RESTRICT"]))
+ "userpriv" not in restrict and \
+ "nouserpriv" not in restrict)
if nosandbox and ("userpriv" not in features or \
- "userpriv" in mysettings["RESTRICT"] or \
- "nouserpriv" in mysettings["RESTRICT"]):
+ "userpriv" in restrict or \
+ "nouserpriv" in restrict):
nosandbox = ("sandbox" not in features and \
"usersandbox" not in features)
else:
os.symlink(target,dest)
lchown(dest,sstat[stat.ST_UID],sstat[stat.ST_GID])
+ # utime() only works on the target of a symlink, so it's not
+ # possible to perserve mtime on symlinks.
return os.lstat(dest)[stat.ST_MTIME]
except SystemExit, e:
raise
mysettings.get("CONFIG_PROTECT","").split(),
mysettings.get("CONFIG_PROTECT_MASK","").split())
self.updateprotect = protect_obj.updateprotect
- self._config_protect = protect_obj
+ self.isprotected = protect_obj.isprotected
self._installed_instance = None
- self.contentscache=[]
+ self.contentscache = None
self._contents_inodes = None
def lockdb(self):
contents_file = os.path.join(self.dbdir, "CONTENTS")
if not os.path.exists(contents_file):
return None
- if self.contentscache != []:
+ if self.contentscache is not None:
return self.contentscache
pkgfiles = {}
myc = open(contents_file,"r")
return pkgfiles
def unmerge(self, pkgfiles=None, trimworld=1, cleanup=1,
- ldpath_mtimes=None):
+ ldpath_mtimes=None, new_contents=None):
"""
Calls prerm
Unmerges a given package (CPV)
@type cleanup: Boolean
@param ldpath_mtimes: mtimes to pass to env_update (see env_update)
@type ldpath_mtimes: Dictionary
+ @param new_contents: contents from a new instance that will replace this one
+ @type new_contents: Dictionary
@rtype: Integer
@returns:
1. os.EX_OK if everything went well.
writemsg("!!! FAILED prerm: %s\n" % retval, noiselevel=-1)
return retval
- self._unmerge_pkgfiles(pkgfiles)
+ self._unmerge_pkgfiles(pkgfiles, new_contents=new_contents)
# Remove the registration of preserved libs for this pkg instance
self.vartree.dbapi.plib_registry.unregister(self.mycpv, self.settings["SLOT"], self.settings["COUNTER"])
contents=contents)
return os.EX_OK
- def _unmerge_pkgfiles(self, pkgfiles):
+ def _unmerge_pkgfiles(self, pkgfiles, new_contents=None):
"""
Unmerges the contents of a package from the liveFS
@param pkgfiles: typically self.getcontents()
@type pkgfiles: Dictionary { filename: [ 'type', '?', 'md5sum' ] }
+ @param new_contents: contents from a new instance that will replace this one
+ @type new_contents: Dictionary
@rtype: None
"""
global dircache
writemsg_stdout("No package files given... Grabbing a set.\n")
pkgfiles = self.getcontents()
+ if not new_contents:
+ counter = self.vartree.dbapi.cpv_counter(self.mycpv)
+ slot = self.vartree.dbapi.aux_get(self.mycpv, ["SLOT"])[0]
+ slot_matches = self.vartree.dbapi.match(
+ "%s:%s" % (dep_getkey(self.mycpv), slot))
+ new_cpv = None
+ if slot_matches:
+ max_counter = -1
+ for cur_cpv in slot_matches:
+ cur_counter = self.vartree.dbapi.cpv_counter(cur_cpv)
+ if cur_counter == counter and \
+ cur_cpv == self.mycpv:
+ continue
+ if cur_counter > max_counter:
+ max_counter = cur_counter
+ new_cpv = cur_cpv
+ if new_cpv:
+ # The current instance has been replaced by a newer instance.
+ new_cat, new_pkg = catsplit(new_cpv)
+ new_contents = dblink(new_cat, new_pkg, self.vartree.root,
+ self.settings, vartree=self.vartree).getcontents()
+
if pkgfiles:
mykeys = pkgfiles.keys()
mykeys.sort()
modprotect = "/lib/modules/"
for objkey in mykeys:
obj = normalize_path(objkey)
- if obj[:2] == "//":
- obj = obj[1:]
+ if new_contents and obj in new_contents:
+ # A new instance of this package claims the file, so don't
+ # unmerge it.
+ writemsg_stdout("--- !owned %s %s\n" % \
+ (pkgfiles[objkey][0], obj))
+ continue
statobj = None
try:
statobj = os.stat(obj)
return False
- def isprotected(self, filename):
- """In cases where an installed package in the same slot owns a
- protected file that will be merged, bump the mtime on the installed
- file in order to ensure that it isn't unmerged."""
- if not self._config_protect.isprotected(filename):
- return False
- if self._installed_instance is None:
- return True
- mydata = self._installed_instance.getcontents().get(filename, None)
- if mydata is None:
- return True
-
- # Bump the mtime in order to ensure that the old config file doesn't
- # get unmerged. The user will have an opportunity to merge the new
- # config with the old one.
- try:
- os.utime(filename, None)
- except OSError, e:
- if e.errno != errno.ENOENT:
- raise
- del e
- # The file has disappeared, so it's not protected.
- return False
- return True
-
-
def _preserve_libs(self, srcroot, destroot, mycontents, counter):
# read global reverse NEEDED map
libmap = self.vartree.dbapi.libmap.get()
"%s:%s" % (self.mysplit[0], self.settings["SLOT"]))
if slot_matches:
# Used by self.isprotected().
+ max_cpv = None
+ max_counter = -1
+ for cur_cpv in slot_matches:
+ cur_counter = self.vartree.dbapi.cpv_counter(cur_cpv)
+ if cur_counter > max_counter:
+ max_counter = cur_counter
+ max_cpv = cur_cpv
+ slot_matches = [max_cpv]
self._installed_instance = dblink(self.cat,
catsplit(slot_matches[0])[1], destroot, self.settings,
vartree=self.vartree)
raise
del e
- # get old contents info for later unmerging
- oldcontents = self.getcontents()
-
self.dbdir = self.dbtmpdir
self.delete()
if not os.path.exists(self.dbtmpdir):
else:
cfgfiledict["IGNORE"]=0
- # Timestamp for files being merged. Use time() - 1 in order to prevent
- # a collision with timestamps that are bumped by the utime() call
- # inside isprotected(). This ensures that the new and old config have
- # different timestamps (for the benefit of programs like rsync that
- # that need distiguishable timestamps to detect file changes).
- mymtime = long(time.time() - 1)
+ # Don't bump mtimes on merge since some application require
+ # preservation of timestamps. This means that the unmerge phase must
+ # check to see if file belongs to an installed instance in the same
+ # slot.
+ mymtime = None
# set umask to 0 for merging; back up umask, save old one in prevmask (since this is a global change)
prevmask = os.umask(0)
#if we opened it, close it
outfile.flush()
outfile.close()
+ self.contentscache = None
+ new_contents = self.getcontents()
if os.path.exists(self.dbpkgdir):
writemsg_stdout(">>> Safely unmerging already-installed instance...\n")
- self.dbdir = self.dbpkgdir
- self.unmerge(oldcontents, trimworld=0, ldpath_mtimes=prev_mtimes)
- self.dbdir = self.dbtmpdir
+ dblink(self.cat, self.pkg, destroot, self.settings,
+ vartree=self.vartree).unmerge(trimworld=0,
+ ldpath_mtimes=prev_mtimes, new_contents=new_contents)
writemsg_stdout(">>> Original instance of package unmerged safely.\n")
# We hold both directory locks.
destmd5 = perform_md5(mydest, calc_prelink=1)
if mymd5 == destmd5:
#file already in place; simply update mtimes of destination
- os.utime(mydest, (thismtime, thismtime))
- zing = "---"
- moveme = 0
+ moveme = 1
else:
if mymd5 == cfgfiledict.get(myrealdest, [None])[0]:
""" An identical update has previously been
if mymtime is None:
sys.exit(1)
zing = ">>>"
- else:
- mymtime = long(time.time())
- # We need to touch the destination so that on --update the
- # old package won't yank the file with it. (non-cfgprot related)
- os.utime(mydest, (mymtime, mymtime))
- zing = "---"
- if self.settings["USERLAND"] == "Darwin" and myrealdest[-2:] == ".a":
-
- # XXX kludge, can be killed when portage stops relying on
- # md5+mtime, and uses refcounts
- # alright, we've fooled w/ mtime on the file; this pisses off static archives
- # basically internal mtime != file's mtime, so the linker (falsely) thinks
- # the archive is stale, and needs to have it's toc rebuilt.
-
- myf = open(mydest, "r+")
-
- # ar mtime field is digits padded with spaces, 12 bytes.
- lms = str(thismtime+5).ljust(12)
- myf.seek(0)
- magic = myf.read(8)
- if magic != "!<arch>\n":
- # not an archive (dolib.a from portage.py makes it here fex)
- myf.close()
- else:
- st = os.stat(mydest)
- while myf.tell() < st.st_size - 12:
- # skip object name
- myf.seek(16, 1)
-
- # update mtime
- myf.write(lms)
-
- # skip uid/gid/mperm
- myf.seek(20, 1)
-
- # read the archive member's size
- x = long(myf.read(10))
-
- # skip the trailing newlines, and add the potential
- # extra padding byte if it's not an even size
- myf.seek(x + 2 + (x % 2),1)
-
- # and now we're at the end. yay.
- myf.close()
- mymd5 = perform_md5(mydest, calc_prelink=1)
- os.utime(mydest, (thismtime, thismtime))
if mymtime != None:
zing = ">>>"
else:
ismatch = False
if missing_flag:
- raise portage_exception.InvalidDependString(
+ raise portage.exception.InvalidDependString(
"Conditional without flag: \"" + \
paren_enclose([head+"?", newdeparray[-1]])+"\"")
pass
return -1, -1
+def set_term_size(lines, columns, fd):
+ """
+ Set the number of lines and columns for the tty that is connected to fd.
+ For portability, this simply calls `stty rows $lines columns $columns`.
+ """
+ from portage.process import spawn
+ cmd = ["stty", "rows", str(lines), "columns", str(columns)]
+ spawn(cmd, env=os.environ, fd_pipes={0:fd})
+
class EOutput:
"""
Performs fancy terminal formatting for status and informational messages.
atexit_register(cleanup)
+# Make sure the original terminal attributes are reverted at exit.
+if hasattr(sys.stdin, "isatty") and sys.stdin.isatty():
+ import termios
+ _stdin_termios = termios.tcgetattr(sys.stdin.fileno())
+ def _reset_stdin_termios(stdin_termios):
+ import termios
+ termios.tcsetattr(sys.stdin.fileno(), termios.TCSAFLUSH, stdin_termios)
+ atexit_register(_reset_stdin_termios, _stdin_termios)
+ del termios, _stdin_termios, _reset_stdin_termios
+
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):
+ path_lookup=True, pre_exec=None):
"""
Spawns a given command.
@type logfile: String
@param path_lookup: If the binary is not fully specified then look for it in PATH
@type path_lookup: Boolean
+ @param pre_exec: A function to be called with no arguments just prior to the exec call.
+ @type pre_exec: callable
logfile requires stdout and stderr to be assigned to this process (ie not pointed
somewhere else.)
if not pid:
try:
_exec(binary, mycommand, opt_name, fd_pipes,
- env, gid, groups, uid, umask)
+ env, gid, groups, uid, umask, pre_exec)
except Exception, e:
# We need to catch _any_ exception so that it doesn't
# propogate out of this function and cause exiting
# Everything succeeded
return 0
-def _exec(binary, mycommand, opt_name, fd_pipes, env, gid, groups, uid, umask):
+def _exec(binary, mycommand, opt_name, fd_pipes, env, gid, groups, uid, umask,
+ pre_exec):
"""
Execute a given binary with options
@type uid: Integer
@param umask: an int representing a unix umask (see man chmod for umask details)
@type umask: Integer
+ @param pre_exec: A function to be called with no arguments just prior to the exec call.
+ @type pre_exec: callable
@rtype: None
@returns: Never returns (calls os.execve)
"""
os.setuid(uid)
if umask:
os.umask(umask)
+ if pre_exec:
+ pre_exec()
# And switch to the new process.
os.execve(binary, myargs, env)
try:
if not pkgcache[mypkg]:
return None
- return pkgcache[mypkg][:]
+ return pkgcache[mypkg]
except KeyError:
pass
myparts=mypkg.split("-")
pkgcache[mypkg]=None
return None
#names can't have versiony looking parts
- myval=["-".join(myparts[:verPos]),myparts[verPos],revision]
+ myval=("-".join(myparts[:verPos]),myparts[verPos],revision)
pkgcache[mypkg]=myval
return myval
else:
try:
if not catcache[mydata]:
return None
- return catcache[mydata][:]
+ return catcache[mydata]
except KeyError:
pass
mysplit=mydata.split("/")
catcache[mydata]=None
return None
retval.extend(p_split)
+ retval = tuple(retval)
catcache[mydata]=retval
return retval