From c6933d5c5df880f883ddd77d8a9a11fbfa15fa55 Mon Sep 17 00:00:00 2001 From: Winson Luk Date: Sat, 6 Mar 2021 07:59:39 -0500 Subject: [PATCH] Add a warning when run as root (e.g., sudo pip) (#9394) --- news/6409.bugfix.rst | 1 + src/pip/_internal/cli/base_command.py | 4 ++-- src/pip/_internal/cli/req_command.py | 31 +++++++++++++++++++++++++ src/pip/_internal/commands/install.py | 7 +++++- src/pip/_internal/commands/uninstall.py | 3 ++- 5 files changed, 42 insertions(+), 4 deletions(-) create mode 100644 news/6409.bugfix.rst diff --git a/news/6409.bugfix.rst b/news/6409.bugfix.rst new file mode 100644 index 00000000000..e906c15fac6 --- /dev/null +++ b/news/6409.bugfix.rst @@ -0,0 +1 @@ +Add a warning, discouraging the usage of pip as root, outside a virtual environment. diff --git a/src/pip/_internal/cli/base_command.py b/src/pip/_internal/cli/base_command.py index 6d5798a0bdc..2dc9845c2ab 100644 --- a/src/pip/_internal/cli/base_command.py +++ b/src/pip/_internal/cli/base_command.py @@ -149,8 +149,8 @@ def _main(self, args): "The directory '%s' or its parent directory is not owned " "or is not writable by the current user. The cache " "has been disabled. Check the permissions and owner of " - "that directory. If executing pip with sudo, you may want " - "sudo's -H flag.", + "that directory. If executing pip with sudo, you should " + "use sudo's -H flag.", options.cache_dir, ) options.cache_dir = None diff --git a/src/pip/_internal/cli/req_command.py b/src/pip/_internal/cli/req_command.py index a55dd7516d8..3fc00d4f47b 100644 --- a/src/pip/_internal/cli/req_command.py +++ b/src/pip/_internal/cli/req_command.py @@ -7,6 +7,7 @@ import logging import os +import sys from functools import partial from optparse import Values from typing import Any, List, Optional, Tuple @@ -38,6 +39,7 @@ TempDirectoryTypeRegistry, tempdir_kinds, ) +from pip._internal.utils.virtualenv import running_under_virtualenv logger = logging.getLogger(__name__) @@ -152,6 +154,35 @@ def handle_pip_version_check(self, options): ] +def warn_if_run_as_root(): + # type: () -> None + """Output a warning for sudo users on Unix. + + In a virtual environment, sudo pip still writes to virtualenv. + On Windows, users may run pip as Administrator without issues. + This warning only applies to Unix root users outside of virtualenv. + """ + if running_under_virtualenv(): + return + if not hasattr(os, "getuid"): + return + # On Windows, there are no "system managed" Python packages. Installing as + # Administrator via pip is the correct way of updating system environments. + # + # We choose sys.platform over utils.compat.WINDOWS here to enable Mypy platform + # checks: https://mypy.readthedocs.io/en/stable/common_issues.html + if sys.platform == "win32" or sys.platform == "cygwin": + return + if sys.platform == "darwin" or sys.platform == "linux": + if os.getuid() != 0: + return + logger.warning( + "Running pip as root will break packages and permissions. " + "You should install packages reliably by using venv: " + "https://pip.pypa.io/warnings/venv" + ) + + def with_cleanup(func): # type: (Any) -> Any """Decorator for common logic related to managing temporary diff --git a/src/pip/_internal/commands/install.py b/src/pip/_internal/commands/install.py index dc743ee0b50..c4273eda9cf 100644 --- a/src/pip/_internal/commands/install.py +++ b/src/pip/_internal/commands/install.py @@ -12,7 +12,11 @@ from pip._internal.cache import WheelCache from pip._internal.cli import cmdoptions from pip._internal.cli.cmdoptions import make_target_python -from pip._internal.cli.req_command import RequirementCommand, with_cleanup +from pip._internal.cli.req_command import ( + RequirementCommand, + warn_if_run_as_root, + with_cleanup, +) from pip._internal.cli.status_codes import ERROR, SUCCESS from pip._internal.exceptions import CommandError, InstallationError from pip._internal.locations import get_scheme @@ -443,6 +447,7 @@ def run(self, options, args): options.target_dir, target_temp_dir, options.upgrade ) + warn_if_run_as_root() return SUCCESS def _handle_target_dir(self, target_dir, target_temp_dir, upgrade): diff --git a/src/pip/_internal/commands/uninstall.py b/src/pip/_internal/commands/uninstall.py index da56f4f5582..9a3c9f8815c 100644 --- a/src/pip/_internal/commands/uninstall.py +++ b/src/pip/_internal/commands/uninstall.py @@ -4,7 +4,7 @@ from pip._vendor.packaging.utils import canonicalize_name from pip._internal.cli.base_command import Command -from pip._internal.cli.req_command import SessionCommandMixin +from pip._internal.cli.req_command import SessionCommandMixin, warn_if_run_as_root from pip._internal.cli.status_codes import SUCCESS from pip._internal.exceptions import InstallationError from pip._internal.req import parse_requirements @@ -88,4 +88,5 @@ def run(self, options, args): if uninstall_pathset: uninstall_pathset.commit() + warn_if_run_as_root() return SUCCESS