aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/markdown/Installing.md4
-rw-r--r--docs/markdown/snippets/meson_install_drop_privs.md16
-rw-r--r--mesonbuild/minstall.py38
3 files changed, 55 insertions, 3 deletions
diff --git a/docs/markdown/Installing.md b/docs/markdown/Installing.md
index 0bc9a47..2d18c17 100644
--- a/docs/markdown/Installing.md
+++ b/docs/markdown/Installing.md
@@ -110,7 +110,9 @@ targets as root. This results in various bad behaviors due to build outputs and
ninja internal files being owned by root.
Running `meson install` is preferred for several reasons. It can rebuild out of
-date targets and then re-invoke itself as root.
+date targets and then re-invoke itself as root. *(since 1.1.0)* Additionally,
+running `sudo meson install` will drop permissions and rebuild out of date
+targets as the original user, not as root.
*(since 1.1.0)* Re-invoking as root will try to guess the user's preferred method for
re-running commands as root. The order of precedence is: sudo, doas, pkexec
diff --git a/docs/markdown/snippets/meson_install_drop_privs.md b/docs/markdown/snippets/meson_install_drop_privs.md
new file mode 100644
index 0000000..e08dfc0
--- /dev/null
+++ b/docs/markdown/snippets/meson_install_drop_privs.md
@@ -0,0 +1,16 @@
+## `sudo meson install` now drops privileges when rebuilding targets
+
+It is common to install projects using sudo, which should not affect build
+outputs but simply install the results. Unfortunately, since the ninja backend
+updates a state file when run, it's not safe to run ninja as root at all.
+
+It has always been possible to carefully build with:
+
+```
+ninja && sudo meson install --no-rebuild
+```
+
+Meson now tries to be extra safe as a general solution. `sudo meson install`
+will attempt to rebuild, but has learned to run `ninja` as the original
+(pre-sudo or pre-doas) user, ensuring that build outputs are generated/compiled
+as non-root.
diff --git a/mesonbuild/minstall.py b/mesonbuild/minstall.py
index ab797b5..a645960 100644
--- a/mesonbuild/minstall.py
+++ b/mesonbuild/minstall.py
@@ -42,7 +42,7 @@ if T.TYPE_CHECKING:
ExecutableSerialisation, InstallDataBase, InstallEmptyDir,
InstallSymlinkData, TargetInstallData
)
- from .mesonlib import FileMode
+ from .mesonlib import FileMode, EnvironOrDict
try:
from typing import Protocol
@@ -753,7 +753,41 @@ def rebuild_all(wd: str) -> bool:
print("Can't find ninja, can't rebuild test.")
return False
- ret = subprocess.run(ninja + ['-C', wd]).returncode
+ def drop_privileges() -> T.Tuple[T.Optional[EnvironOrDict], T.Optional[T.Callable[[], None]]]:
+ if not is_windows() and os.geteuid() == 0:
+ import pwd
+ env = os.environ.copy()
+
+ if os.environ.get('SUDO_USER') is not None:
+ orig_user = env.pop('SUDO_USER')
+ orig_uid = env.pop('SUDO_UID', 0)
+ orig_gid = env.pop('SUDO_GID', 0)
+ homedir = pwd.getpwuid(int(orig_uid)).pw_dir
+ elif os.environ.get('DOAS_USER') is not None:
+ orig_user = env.pop('DOAS_USER')
+ pwdata = pwd.getpwnam(orig_user)
+ orig_uid = pwdata.pw_uid
+ orig_gid = pwdata.pw_gid
+ homedir = pwdata.pw_dir
+ else:
+ return None, None
+
+ env['USER'] = orig_user
+ env['HOME'] = homedir
+
+ def wrapped() -> None:
+ print(f'Dropping privileges to {orig_user!r} before running ninja...')
+ if orig_gid is not None:
+ os.setgid(int(orig_gid))
+ if orig_uid is not None:
+ os.setuid(int(orig_uid))
+
+ return env, wrapped
+ else:
+ return None, None
+
+ env, preexec_fn = drop_privileges()
+ ret = subprocess.run(ninja + ['-C', wd], env=env, preexec_fn=preexec_fn).returncode
if ret != 0:
print(f'Could not rebuild {wd}')
return False