@@ -, +, @@ --- bin/ebuild-helpers/fowners | 4 +- pym/portage/const.py | 3 +- pym/portage/data.py | 94 +++++++++++++++++++++++++----------- pym/portage/package/ebuild/config.py | 21 +++++--- 4 files changed, 82 insertions(+), 40 deletions(-) --- a/bin/ebuild-helpers/fowners +++ a/bin/ebuild-helpers/fowners @@ -13,8 +13,8 @@ slash="/" chown "${@/#${slash}/${ED}${slash}}" ret=$? -if [[ ${ret} != 0 && -n ${EPREFIX} && ${EUID} != 0 ]] ; then - ewarn "fowners failure ignored in Prefix with non-privileged user" +if [[ ${ret} != 0 && ${EUID} != 0 && " ${FEATURES} " == *" unprivileged "* ]] ; then + ewarn "fowners failure ignored with non-privileged user" exit 0 fi --- a/pym/portage/const.py +++ a/pym/portage/const.py @@ -106,7 +106,8 @@ SUPPORTED_FEATURES = frozenset([ "strict", "stricter", "suidctl", "test", "test-fail-continue", "unknown-features-filter", "unknown-features-warn", "unmerge-backup", - "unmerge-logs", "unmerge-orphans", "userfetch", "userpriv", + "unmerge-logs", "unmerge-orphans", + "unprivileged", "userfetch", "userpriv", "usersandbox", "usersync", "webrsync-gpg", "xattr"]) EAPI = 4 --- a/pym/portage/data.py +++ a/pym/portage/data.py @@ -58,6 +58,15 @@ def portage_group_warning(): # If the "wheel" group does not exist then wheelgid falls back to 0. # If the "portage" group does not exist then portage_uid falls back to wheelgid. +# If the current user is not root, but has write access to the +# EROOT directory (not due to the 0002 bit), then use "unprivileged" +# mode which sets secpass = 2 and uses the UID and GID of the EROOT +# directory to generate default PORTAGE_INST_GID, PORTAGE_INST_UID, +# PORTAGE_USERNAME, and PORTAGE_GRPNAME settings. +def _unprivileged_mode(eroot, eroot_st): + return os.getuid() != 0 and os.access(eroot, os.W_OK) and \ + not eroot_st.st_mode & 0o0002 + uid=os.getuid() wheelgid=0 @@ -77,13 +86,33 @@ def _get_global(k): if k in _initialized_globals: return globals()[k] - if k in ('portage_gid', 'portage_uid', 'secpass'): - global portage_gid, portage_uid, secpass - secpass = 0 + if k == 'secpass': + + unprivileged = False + if hasattr(portage, 'settings'): + unprivileged = "unprivileged" in portage.settings.features + else: + # The config class has equivalent code, but we also need to + # do it here if _disable_legacy_globals() has been called. + eroot = os.path.join(os.environ.get('ROOT', os.sep), + portage.const.EPREFIX.lstrip(os.sep)) + try: + eroot_st = os.stat(eroot) + except OSError: + pass + else: + unprivileged = _unprivileged_mode(eroot, eroot_st) + + v = 0 if uid == 0: - secpass = 2 - elif portage.const.EPREFIX: - secpass = 2 + v = 2 + elif unprivileged: + v = 2 + elif portage_gid in os.getgroups(): + v = 1 + + elif k in ('portage_gid', 'portage_uid'): + #Discover the uid and gid of the portage user/group try: portage_uid = pwd.getpwnam(_get_global('_portage_username')).pw_uid @@ -93,8 +122,6 @@ def _get_global(k): # from grp.getgrnam() with PyPy 1.7 _portage_grpname = str(_portage_grpname) portage_gid = grp.getgrnam(_portage_grpname).gr_gid - if secpass < 1 and portage_gid in os.getgroups(): - secpass = 1 except KeyError: portage_uid = 0 portage_gid = 0 @@ -110,16 +137,15 @@ def _get_global(k): noiselevel=-1) portage_group_warning() + globals()['portage_gid'] = portage_gid _initialized_globals.add('portage_gid') + globals()['portage_uid'] = portage_uid _initialized_globals.add('portage_uid') - _initialized_globals.add('secpass') if k == 'portage_gid': return portage_gid elif k == 'portage_uid': return portage_uid - elif k == 'secpass': - return secpass else: raise AssertionError('unknown name: %s' % k) @@ -152,11 +178,9 @@ def _get_global(k): v = os.environ[env_key] elif hasattr(portage, 'settings'): v = portage.settings.get(env_key) - elif portage.const.EPREFIX: - # For prefix environments, default to the UID and GID of - # the top-level EROOT directory. The config class has - # equivalent code, but we also need to do it here if - # _disable_legacy_globals() has been called. + else: + # The config class has equivalent code, but we also need to + # do it here if _disable_legacy_globals() has been called. eroot = os.path.join(os.environ.get('ROOT', os.sep), portage.const.EPREFIX.lstrip(os.sep)) try: @@ -164,20 +188,21 @@ def _get_global(k): except OSError: pass else: - if k == '_portage_grpname': - try: - grp_struct = grp.getgrgid(eroot_st.st_gid) - except KeyError: - pass + if _unprivileged_mode(eroot, eroot_st): + if k == '_portage_grpname': + try: + grp_struct = grp.getgrgid(eroot_st.st_gid) + except KeyError: + pass + else: + v = grp_struct.gr_name else: - v = grp_struct.gr_name - else: - try: - pwd_struct = pwd.getpwuid(eroot_st.st_uid) - except KeyError: - pass - else: - v = pwd_struct.pw_name + try: + pwd_struct = pwd.getpwuid(eroot_st.st_uid) + except KeyError: + pass + else: + v = pwd_struct.pw_name if v is None: v = 'portage' @@ -220,3 +245,14 @@ def _init(settings): v = settings.get('PORTAGE_USERNAME', 'portage') globals()['_portage_username'] = v _initialized_globals.add('_portage_username') + + if 'secpass' not in _initialized_globals: + v = 0 + if uid == 0: + v = 2 + elif "unprivileged" in settings.features: + v = 2 + elif portage_gid in os.getgroups(): + v = 1 + globals()['secpass'] = v + _initialized_globals.add('secpass') --- a/pym/portage/package/ebuild/config.py +++ a/pym/portage/package/ebuild/config.py @@ -750,14 +750,16 @@ class config(object): "PORTAGE_INST_UID": "0", } - if eprefix: - # For prefix environments, default to the UID and GID of - # the top-level EROOT directory. - try: - eroot_st = os.stat(eroot) - except OSError: - pass - else: + unprivileged = False + try: + eroot_st = os.stat(eroot) + except OSError: + pass + else: + + if portage.data._unprivileged_mode(eroot, eroot_st): + unprivileged = True + default_inst_ids["PORTAGE_INST_GID"] = str(eroot_st.st_gid) default_inst_ids["PORTAGE_INST_UID"] = str(eroot_st.st_uid) @@ -792,6 +794,9 @@ class config(object): # initialize self.features self.regenerate() + if unprivileged: + self.features.add('unprivileged') + if bsd_chflags: self.features.add('chflags') --