FreeBSD: don't use /dev/fd, bug #478446
[portage.git] / pym / portage / data.py
index 5fc811e43d356d7f4122de21be3212d4bb5cebeb..caf4752ac2b04849ee99ef13d9d2eff379956e8a 100644 (file)
@@ -1,15 +1,18 @@
 # data.py -- Calculated/Discovered Data Values
-# Copyright 1998-2004 Gentoo Foundation
+# Copyright 1998-2013 Gentoo Foundation
 # Distributed under the terms of the GNU General Public License v2
-# $Id$
 
-import os, sys, pwd, grp
-from portage.util import writemsg
-from portage.output import green,red
-from portage.output import create_color_func
-bad = create_color_func("BAD")
+import os, pwd, grp, platform, sys
 
-ostype=os.uname()[0]
+import portage
+portage.proxy.lazyimport.lazyimport(globals(),
+       'portage.output:colorize',
+       'portage.util:writemsg',
+       'subprocess'
+)
+from portage.localization import _
+
+ostype=platform.system()
 userland = None
 if ostype == "DragonFly" or ostype.endswith("BSD"):
        userland = "BSD"
@@ -23,17 +26,16 @@ if not lchown:
                def lchown(*pos_args, **key_args):
                        pass
        else:
-               try:
-                       import missingos
-                       lchown = missingos.lchown
-               except ImportError:
-                       def lchown(*pos_args, **key_args):
-                               writemsg(red("!!!") + " It seems that os.lchown does not" + \
-                                       " exist.  Please rebuild python.\n", noiselevel=-1)
-                       lchown()
+               def lchown(*pargs, **kwargs):
+                       writemsg(colorize("BAD", "!!!") + _(
+                               " It seems that os.lchown does not"
+                               " exist.  Please rebuild python.\n"), noiselevel=-1)
+               lchown()
+
+lchown = portage._chown_func_wrapper(lchown)
 
 def portage_group_warning():
-       warn_prefix = bad("*** WARNING ***  ")
+       warn_prefix = colorize("BAD", "*** WARNING ***  ")
        mylines = [
                "For security reasons, only system administrators should be",
                "allowed in the portage group.  Untrusted users or processes",
@@ -57,52 +59,199 @@ 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.
 
-secpass=0
-
 uid=os.getuid()
 wheelgid=0
 
-if uid==0:
-       secpass=2
 try:
        wheelgid=grp.getgrnam("wheel")[2]
 except KeyError:
        pass
 
-#Discover the uid and gid of the portage user/group
-try:
-       portage_uid=pwd.getpwnam("portage")[2]
-       portage_gid=grp.getgrnam("portage")[2]
-       if secpass < 1 and portage_gid in os.getgroups():
-               secpass=1
-except KeyError:
-       portage_uid=0
-       portage_gid=0
-       writemsg("\n")
-       writemsg(  red("portage: 'portage' user or group missing. Please update baselayout\n"))
-       writemsg(  red("         and merge portage user(250) and group(250) into your passwd\n"))
-       writemsg(  red("         and group files. Non-root compilation is disabled until then.\n"))
-       writemsg(      "         Also note that non-root/wheel users will need to be added to\n")
-       writemsg(      "         the portage group to do portage commands.\n")
-       writemsg("\n")
-       writemsg(      "         For the defaults, line 1 goes into passwd, and 2 into group.\n")
-       writemsg(green("         portage:x:250:250:portage:/var/tmp/portage:/bin/false\n"))
-       writemsg(green("         portage::250:portage\n"))
-       writemsg("\n")
-       portage_group_warning()
-
-userpriv_groups = [portage_gid]
-if secpass >= 2:
-       # Get a list of group IDs for the portage user.  Do not use grp.getgrall()
-       # since it is known to trigger spurious SIGPIPE problems with nss_ldap.
-       from commands import getstatusoutput
-       mystatus, myoutput = getstatusoutput("id -G portage")
-       if mystatus == os.EX_OK:
-               for x in myoutput.split():
+# The portage_uid and portage_gid global constants, and others that
+# depend on them are initialized lazily, in order to allow configuration
+# via make.conf. Eventually, these constants may be deprecated in favor
+# of config attributes, since it's conceivable that multiple
+# configurations with different constants could be used simultaneously.
+_initialized_globals = set()
+
+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 uid == 0:
+                       secpass = 2
+               elif portage.const.EPREFIX:
+                       secpass = 2
+               #Discover the uid and gid of the portage user/group
+               keyerror = False
+               try:
+                       portage_uid = pwd.getpwnam(_get_global('_portage_username')).pw_uid
+               except KeyError:
+                       keyerror = True
+                       portage_uid = 0
+
+               try:
+                       portage_gid = grp.getgrnam(_get_global('_portage_grpname')).gr_gid
+               except KeyError:
+                       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).
+               if keyerror and not (_get_global('_portage_username') == "root" and
+                       _get_global('_portage_grpname') == "root"):
+                       writemsg(colorize("BAD",
+                               _("portage: 'portage' user or group missing.")) + "\n", noiselevel=-1)
+                       writemsg(_(
+                               "         For the defaults, line 1 goes into passwd, "
+                               "and 2 into group.\n"), noiselevel=-1)
+                       writemsg(colorize("GOOD",
+                               "         portage:x:250:250:portage:/var/tmp/portage:/bin/false") \
+                               + "\n", noiselevel=-1)
+                       writemsg(colorize("GOOD", "         portage::250:portage") + "\n",
+                               noiselevel=-1)
+                       portage_group_warning()
+
+               _initialized_globals.add('portage_gid')
+               _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)
+
+       elif k == 'userpriv_groups':
+               v = [portage_gid]
+               if secpass >= 2:
+                       # Get a list of group IDs for the portage user. Do not use
+                       # grp.getgrall() since it is known to trigger spurious
+                       # SIGPIPE problems with nss_ldap.
+                       cmd = ["id", "-G", _portage_username]
+
+                       if sys.hexversion < 0x3020000 and sys.hexversion >= 0x3000000:
+                               # Python 3.1 _execvp throws TypeError for non-absolute executable
+                               # path passed as bytes (see http://bugs.python.org/issue8513).
+                               fullname = portage.process.find_binary(cmd[0])
+                               if fullname is None:
+                                       globals()[k] = v
+                                       _initialized_globals.add(k)
+                                       return v
+                               cmd[0] = fullname
+
+                       encoding = portage._encodings['content']
+                       cmd = [portage._unicode_encode(x,
+                               encoding=encoding, errors='strict') for x in cmd]
+                       proc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
+                               stderr=subprocess.STDOUT)
+                       myoutput = proc.communicate()[0]
+                       status = proc.wait()
+                       if os.WIFEXITED(status) and os.WEXITSTATUS(status) == os.EX_OK:
+                               for x in portage._unicode_decode(myoutput,
+                                       encoding=encoding, errors='strict').split():
+                                       try:
+                                               v.append(int(x))
+                                       except ValueError:
+                                               pass
+                               v = sorted(set(v))
+
+       # Avoid instantiating portage.settings when the desired
+       # variable is set in os.environ.
+       elif k in ('_portage_grpname', '_portage_username'):
+               v = None
+               if k == '_portage_grpname':
+                       env_key = 'PORTAGE_GRPNAME'
+               else:
+                       env_key = 'PORTAGE_USERNAME'
+
+               if env_key in os.environ:
+                       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.
+                       eroot = os.path.join(os.environ.get('ROOT', os.sep),
+                               portage.const.EPREFIX.lstrip(os.sep))
                        try:
-                               userpriv_groups.append(int(x))
-                       except ValueError:
+                               eroot_st = os.stat(eroot)
+                       except OSError:
                                pass
-                       del x
-               userpriv_groups = list(set(userpriv_groups))
-       del getstatusoutput, mystatus, myoutput
+                       else:
+                               if k == '_portage_grpname':
+                                       try:
+                                               grp_struct = grp.getgrgid(eroot_st.st_gid)
+                                       except KeyError:
+                                               pass
+                                       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
+
+               if v is None:
+                       v = 'portage'
+       else:
+               raise AssertionError('unknown name: %s' % k)
+
+       globals()[k] = v
+       _initialized_globals.add(k)
+       return v
+
+class _GlobalProxy(portage.proxy.objectproxy.ObjectProxy):
+
+       __slots__ = ('_name',)
+
+       def __init__(self, name):
+               portage.proxy.objectproxy.ObjectProxy.__init__(self)
+               object.__setattr__(self, '_name', name)
+
+       def _get_target(self):
+               return _get_global(object.__getattribute__(self, '_name'))
+
+for k in ('portage_gid', 'portage_uid', 'secpass', 'userpriv_groups',
+       '_portage_grpname', '_portage_username'):
+       globals()[k] = _GlobalProxy(k)
+del k
+
+def _init(settings):
+       """
+       Use config variables like PORTAGE_GRPNAME and PORTAGE_USERNAME to
+       initialize global variables. This allows settings to come from make.conf
+       instead of requiring them to be set in the calling environment.
+       """
+       if '_portage_grpname' not in _initialized_globals and \
+               '_portage_username' not in _initialized_globals:
+
+               # Prevents "TypeError: expected string" errors
+               # from grp.getgrnam() with PyPy
+               native_string = platform.python_implementation() == 'PyPy'
+
+               v = settings.get('PORTAGE_GRPNAME', 'portage')
+               if native_string:
+                       v = portage._native_string(v)
+               globals()['_portage_grpname'] = v
+               _initialized_globals.add('_portage_grpname')
+
+               v = settings.get('PORTAGE_USERNAME', 'portage')
+               if native_string:
+                       v = portage._native_string(v)
+               globals()['_portage_username'] = v
+               _initialized_globals.add('_portage_username')