diff --git a/news/5359.bugfix b/news/5359.bugfix new file mode 100644 index 00000000000..48b5fb7fb76 --- /dev/null +++ b/news/5359.bugfix @@ -0,0 +1,48 @@ +When executing a pip install from within a Python process in which the euid has +been changed, the appdirs helpers which use `expanduser()` return paths +relative to the home directory of the actual user, rather than the effective +user. This causes the install to fail when the effective user cannot write to +the cachedir: + +.. code-block:: python + + >>> import subprocess + >>> import os + >>> os.geteuid() + 0 + >>> os.seteuid(1000) + >>> os.geteuid() + 1000 + >>> p = subprocess.Popen(['/home/erik/virtualenv/pipdev/bin/pip', 'install', 'pudb'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + >>> stdout, stderr = p.communicate() + >>> p.returncode + 1 + >>> stderr + "Could not install packages due to an EnvironmentError: [Errno 13] Permission denied: '/root/.cache/pip/wheels/ec/bb/79/09cd8a9982b637b251208ac3400f9702c72e63accc8e4b69e3'\nConsider using the `--user` option or check the permissions.\n\n" + >>> + +With this bugfix, the installation proceeds as expected, and the sources/wheels +are downloaded to the effective user's cachedir: + +.. code-block:: python + + >>> import subprocess + >>> import os + >>> os.geteuid() + 0 + >>> os.seteuid(1000) + >>> os.geteuid() + 1000 + >>> p = subprocess.Popen(['/home/erik/virtualenv/pipdev/bin/pip', 'install', 'pudb'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + >>> stdout, stderr = p.communicate() + >>> p.returncode + 0 + >>> print(stdout) + Collecting pudb + Collecting urwid>=1.1.1 (from pudb) + Collecting pygments>=1.0 (from pudb) + Using cached https://files.pythonhosted.org/packages/02/ee/b6e02dc6529e82b75bb06823ff7d005b141037cb1416b10c6f00fc419dca/Pygments-2.2.0-py2.py3-none-any.whl + Installing collected packages: urwid, pygments, pudb + Successfully installed pudb-2017.1.4 pygments-2.2.0 urwid-2.0.1 + + >>> diff --git a/src/pip/_internal/utils/appdirs.py b/src/pip/_internal/utils/appdirs.py index 28c5d4b3c75..d650524f31d 100644 --- a/src/pip/_internal/utils/appdirs.py +++ b/src/pip/_internal/utils/appdirs.py @@ -11,6 +11,18 @@ from pip._internal.compat import WINDOWS, expanduser +try: + import pwd +except ImportError: + pwd = None + + +def _effective_user(): + try: + return pwd.getpwuid(os.geteuid()).pw_name + except Exception: + return '' + def user_cache_dir(appname): r""" @@ -44,15 +56,17 @@ def user_cache_dir(appname): # Add our app name and Cache directory to it path = os.path.join(path, appname, "Cache") - elif sys.platform == "darwin": - # Get the base path - path = expanduser("~/Library/Caches") - - # Add our app name to it - path = os.path.join(path, appname) else: - # Get the base path - path = os.getenv("XDG_CACHE_HOME", expanduser("~/.cache")) + user = _effective_user() + if sys.platform == "darwin": + # Get the base path + path = expanduser("~{0}/Library/Caches".format(user)) + else: + # Get the base path + path = os.getenv( + "XDG_CACHE_HOME", + expanduser("~{0}/.cache".format(user)) + ) # Add our app name to it path = os.path.join(path, appname) @@ -91,23 +105,28 @@ def user_data_dir(appname, roaming=False): if WINDOWS: const = roaming and "CSIDL_APPDATA" or "CSIDL_LOCAL_APPDATA" path = os.path.join(os.path.normpath(_get_win_folder(const)), appname) - elif sys.platform == "darwin": - path = os.path.join( - expanduser('~/Library/Application Support/'), - appname, - ) if os.path.isdir(os.path.join( - expanduser('~/Library/Application Support/'), - appname, - ) - ) else os.path.join( - expanduser('~/.config/'), - appname, - ) else: - path = os.path.join( - os.getenv('XDG_DATA_HOME', expanduser("~/.local/share")), - appname, - ) + user = _effective_user() + if sys.platform == "darwin": + path = os.path.join( + expanduser('~{0}/Library/Application Support/'.format(user)), + appname, + ) if os.path.isdir(os.path.join( + expanduser('~{0}/Library/Application Support/'.format(user)), + appname, + ) + ) else os.path.join( + expanduser('~{0}/.config/'.format(user)), + appname, + ) + else: + path = os.path.join( + os.getenv( + 'XDG_DATA_HOME', + expanduser("~{0}/.local/share".format(user)) + ), + appname, + ) return path @@ -137,7 +156,11 @@ def user_config_dir(appname, roaming=True): elif sys.platform == "darwin": path = user_data_dir(appname) else: - path = os.getenv('XDG_CONFIG_HOME', expanduser("~/.config")) + user = _effective_user() + path = os.getenv( + 'XDG_CONFIG_HOME', + expanduser("~{0}/.config".format(user)) + ) path = os.path.join(path, appname) return path