From b47a9bde41df9e51bf47025b1d8a1e076e802fcd Mon Sep 17 00:00:00 2001 From: Zac Medico Date: Sun, 14 Sep 2014 19:30:52 -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. X-Gentoo-Bug: 433453 X-Gentoo-Bug-URL: https://bugs.gentoo.org/show_bug.cgi?id=433453 --- pym/portage/const.py | 1 + pym/portage/data.py | 95 ++++++++++++++++++++++----------- pym/portage/package/ebuild/config.py | 21 +++++--- pym/portage/tests/emerge/test_simple.py | 2 +- 4 files changed, 80 insertions(+), 39 deletions(-) diff --git a/pym/portage/const.py b/pym/portage/const.py index acb90f9..d472075 100644 --- a/pym/portage/const.py +++ b/pym/portage/const.py @@ -189,6 +189,7 @@ SUPPORTED_FEATURES = frozenset([ "unmerge-backup", "unmerge-logs", "unmerge-orphans", + "unprivileged", "userfetch", "userpriv", "usersandbox", diff --git a/pym/portage/data.py b/pym/portage/data.py index 54e3a8d..3d91e48 100644 --- a/pym/portage/data.py +++ b/pym/portage/data.py @@ -59,6 +59,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 try: @@ -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 keyerror = False try: @@ -98,9 +127,6 @@ def _get_global(k): keyerror = True portage_gid = 0 - if secpass < 1 and portage_gid in os.getgroups(): - secpass = 1 - # Suppress this error message if both PORTAGE_GRPNAME and # PORTAGE_USERNAME are set to "root", for things like # Android (see bug #454060). @@ -118,16 +144,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) @@ -178,11 +203,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: @@ -190,20 +213,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' @@ -254,3 +278,14 @@ def _init(settings): v = portage._native_string(v) 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 264ed8e..1087443 100644 --- a/pym/portage/package/ebuild/config.py +++ b/pym/portage/package/ebuild/config.py @@ -834,14 +834,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) @@ -880,6 +882,9 @@ class config(object): # initialize self.features self.regenerate() + if unprivileged: + self.features.add('unprivileged') + if bsd_chflags: self.features.add('chflags') diff --git a/pym/portage/tests/emerge/test_simple.py b/pym/portage/tests/emerge/test_simple.py index 9c1b1bf..ba5a12c 100644 --- a/pym/portage/tests/emerge/test_simple.py +++ b/pym/portage/tests/emerge/test_simple.py @@ -351,7 +351,7 @@ pkg_preinst() { os.environ["__PORTAGE_TEST_HARDLINK_LOCKS"] updates_dir = os.path.join(test_repo_location, "profiles", "updates") - dirs = [cachedir, cachedir_pregen, distdir, fake_bin, + dirs = [cachedir, cachedir_pregen, cross_prefix, distdir, fake_bin, portage_tmpdir, updates_dir, user_config_dir, var_cache_edb] etc_symlinks = ("dispatch-conf.conf", "etc-update.conf") -- 1.8.5.5