Index: pym/portage_util.py =================================================================== --- pym/portage_util.py (revision 5113) +++ pym/portage_util.py (revision 5115) @@ -489,7 +489,7 @@ return u def apply_permissions(filename, uid=-1, gid=-1, mode=-1, mask=-1, - stat_cached=None): + stat_cached=None, follow_links=True): """Apply user, group, and mode bits to a file if the existing bits do not already match. The default behavior is to force an exact match of mode bits. When mask=0 is specified, mode bits on the target file are allowed @@ -502,7 +502,10 @@ if stat_cached is None: try: - stat_cached = os.stat(filename) + if follow_links: + stat_cached = os.stat(filename) + else: + stat_cached = os.lstat(filename) except OSError, oe: func_call = "stat('%s')" % filename if oe.errno == errno.EPERM: @@ -517,7 +520,10 @@ if (uid != -1 and uid != stat_cached.st_uid) or \ (gid != -1 and gid != stat_cached.st_gid): try: - os.chown(filename, uid, gid) + if follow_links: + os.chown(filename, uid, gid) + else: + os.lchown(filename, uid, gid) modified = True except OSError, oe: func_call = "chown('%s', %i, %i)" % (filename, uid, gid) @@ -548,6 +554,19 @@ if mode != st_mode: new_mode = mode + # The chown system call may clear S_ISUID and S_ISGID + # bits, so those bits are restored if necessary. + if modified and new_mode == -1 and \ + (st_mode & stat.S_ISUID or st_mode & stat.S_ISGID): + if mode == -1: + new_mode = st_mode + elif mode & stat.S_ISUID or mode & stat.S_ISGID: + new_mode = mode & 07777 + + if not follow_links and stat.S_ISLNK(stat_cached.st_mode): + # Mode doesn't matter for symlinks. + new_mode = -1 + if new_mode != -1: try: os.chmod(filename, new_mode) @@ -614,7 +633,7 @@ return all_applied def apply_secpass_permissions(filename, uid=-1, gid=-1, mode=-1, mask=-1, - stat_cached=None): + stat_cached=None, follow_links=True): """A wrapper around apply_permissions that uses secpass and simple logic to apply as much of the permissions as possible without generating an obviously avoidable permission exception. Despite @@ -625,7 +644,10 @@ if stat_cached is None: try: - stat_cached = os.stat(filename) + if follow_links: + stat_cached = os.stat(filename) + else: + stat_cached = os.lstat(filename) except OSError, oe: func_call = "stat('%s')" % filename if oe.errno == errno.EPERM: @@ -653,7 +675,8 @@ all_applied = False gid = -1 - apply_permissions(filename, uid=uid, gid=gid, mode=mode, mask=mask, stat_cached=stat_cached) + apply_permissions(filename, uid=uid, gid=gid, mode=mode, mask=mask, + stat_cached=stat_cached, follow_links=follow_links) return all_applied class atomic_ofstream(file): Index: pym/portage.py =================================================================== --- pym/portage.py (revision 5113) +++ pym/portage.py (revision 5115) @@ -1344,6 +1344,16 @@ self["PORTAGE_PYM_PATH"] = PORTAGE_PYM_PATH self.backup_changes("PORTAGE_PYM_PATH") + for var in ("PORTAGE_INST_UID", "PORTAGE_INST_GID"): + try: + self[var] = str(int(self.get(var, "0"))) + except ValueError: + writemsg(("!!! %s='%s' is not a valid integer. " + \ + "Falling back to '0'.\n") % (var, self[var]), + noiselevel=-1) + self[var] = "0" + self.backup_changes(var) + self.regenerate() self.features = portage_util.unique_array(self["FEATURES"].split()) @@ -2614,6 +2624,29 @@ if phase_retval == os.EX_OK: if mydo == "install": + # User and group bits that match the "portage" user or group are + # automatically mapped to PORTAGE_INST_UID and PORTAGE_INST_GID if + # necessary. The chown system call may clear S_ISUID and S_ISGID + # bits, so those bits are restored if necessary. + from itertools import chain + inst_uid = int(mysettings["PORTAGE_INST_UID"]) + inst_gid = int(mysettings["PORTAGE_INST_GID"]) + for parent, dirs, files in os.walk(mysettings["D"]): + for fname in chain(dirs, files): + fpath = os.path.join(parent, fname) + mystat = os.lstat(fpath) + if mystat.st_uid != portage_uid and \ + mystat.st_gid != portage_gid: + continue + myuid = -1 + mygid = -1 + if mystat.st_uid == portage_uid: + myuid = inst_uid + if mystat.st_gid == portage_gid: + mygid = inst_gid + apply_secpass_permissions(fpath, uid=myuid, gid=mygid, + mode=mystat.st_mode, stat_cached=mystat, + follow_links=False) mycommand = " ".join([MISC_SH_BINARY, "install_qa_check"]) qa_retval = spawn(mycommand, mysettings, debug=debug, logfile=logfile, **kwargs) if qa_retval: Index: bin/misc-functions.sh =================================================================== --- bin/misc-functions.sh (revision 5113) +++ bin/misc-functions.sh (revision 5115) @@ -182,17 +182,6 @@ unset INSTALLTOD fi - local find_log="${T}/find-portage-log" - find "${D}"/ -user portage -print0 > "${find_log}" - if [[ -s ${find_log} ]] ; then - xargs -0 chown -h ${PORTAGE_INST_UID:-0} < "${find_log}" - fi - find "${D}"/ -group portage -print0 > "${find_log}" - if [[ -s ${find_log} ]] ; then - xargs -0 chgrp -h ${PORTAGE_INST_GID:-0} < "${find_log}" - fi - rm -f "${find_log}" - # Portage regenerates this on the installed system. if [ -f "${D}/usr/share/info/dir.gz" ]; then rm -f "${D}/usr/share/info/dir.gz"