From 57241a07a3bccaba4875e23df2d6929c374d96d6 Mon Sep 17 00:00:00 2001 From: Zac Medico Date: Sat, 8 Sep 2012 16:05:04 -0700 Subject: [PATCH] Support unprivileged mode for bug #433453. This takes the existing unprivileged prefix support and enables it to work when EPREFIX is empty. This "unprivileged" is automatically enabled if the current user is not root, but has write access to the EROOT directory (not due to the 0002 bit). For use in conditional logic (for example, see fowners diff), "unprivileged" is automatically added to the FEATURES variable. --- 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(-) diff --git a/bin/ebuild-helpers/fowners b/bin/ebuild-helpers/fowners index a213c9e..f12d99e 100755 --- a/bin/ebuild-helpers/fowners +++ b/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 diff --git a/pym/portage/const.py b/pym/portage/const.py index 3b8e350..894fec4 100644 --- a/pym/portage/const.py +++ b/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 diff --git a/pym/portage/data.py b/pym/portage/data.py index c4d967a..0d0bf28 100644 --- a/pym/portage/data.py +++ b/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') diff --git a/pym/portage/package/ebuild/config.py b/pym/portage/package/ebuild/config.py index 8f3b59b..1d31718 100644 --- a/pym/portage/package/ebuild/config.py +++ b/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') -- 1.7.12