feat: update depot package version to 1.0.2 and enhance fakeroot support with ownership tracking

This commit is contained in:
2026-07-16 21:43:19 -05:00
parent 9bae0f8642
commit b396a88a4b
12 changed files with 403 additions and 37 deletions
Generated
+1 -1
View File
@@ -481,7 +481,7 @@ checksum = "ac6b926516df9c60bfa16e107b21086399f8285a44ca9711344b9e553c5146e2"
[[package]] [[package]]
name = "depot" name = "depot"
version = "1.0.1" version = "1.0.2"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"ar", "ar",
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "depot" name = "depot"
version = "1.0.1" version = "1.0.2"
edition = "2024" edition = "2024"
[lints.rust] [lints.rust]
+30
View File
@@ -12,6 +12,7 @@ const BUILD_OPTION_KEYS: &[&str] = &[
fn main() { fn main() {
println!("cargo:rerun-if-env-changed=CC"); println!("cargo:rerun-if-env-changed=CC");
println!("cargo:rerun-if-changed=src/fakeroot_preload.c");
for key in BUILD_OPTION_KEYS { for key in BUILD_OPTION_KEYS {
println!("cargo:rerun-if-env-changed={key}"); println!("cargo:rerun-if-env-changed={key}");
if let Ok(value) = std::env::var(key) { if let Ok(value) = std::env::var(key) {
@@ -19,6 +20,10 @@ fn main() {
} }
} }
if std::env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("linux") {
build_fakeroot_preload();
}
if std::env::var_os("CARGO_FEATURE_STATIC_EXCEPT_LIBC").is_some() if std::env::var_os("CARGO_FEATURE_STATIC_EXCEPT_LIBC").is_some()
&& std::env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("linux") && std::env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("linux")
{ {
@@ -32,6 +37,31 @@ fn main() {
} }
} }
fn build_fakeroot_preload() {
let compiler = std::env::var_os("CC").unwrap_or_else(|| "cc".into());
let output_path = std::path::PathBuf::from(
std::env::var_os("OUT_DIR").expect("Cargo must provide OUT_DIR to build.rs"),
)
.join("libdepot_fakeroot.so");
let output = std::process::Command::new(compiler)
.args([
"-std=c11", "-shared", "-fPIC", "-O2", "-Wall", "-Wextra", "-Werror", "-o",
])
.arg(&output_path)
.arg("src/fakeroot_preload.c")
.output()
.unwrap_or_else(|error| {
panic!("failed to launch C compiler for fakeroot preload: {error}")
});
if !output.status.success() {
panic!(
"failed to build fakeroot preload library:\n{}",
String::from_utf8_lossy(&output.stderr)
);
}
}
fn gcc_runtime_archive(name: &str) -> Option<String> { fn gcc_runtime_archive(name: &str) -> Option<String> {
let compiler = std::env::var_os("CC").unwrap_or_else(|| "cc".into()); let compiler = std::env::var_os("CC").unwrap_or_else(|| "cc".into());
let output = std::process::Command::new(compiler) let output = std::process::Command::new(compiler)
+2 -2
View File
@@ -339,7 +339,7 @@ pub fn build(
Some("install"), Some("install"),
); );
for install_dir in install_dirs { for install_dir in install_dirs {
let mut install_cmd = fakeroot::wrap_install_command(make_exec, &install_destdir); let mut install_cmd = fakeroot::wrap_install_command(make_exec, &install_destdir)?;
install_cmd.current_dir(&install_dir); install_cmd.current_dir(&install_dir);
if make_exec_supports_make_assignments(make_exec) if make_exec_supports_make_assignments(make_exec)
&& !has_make_variable_override(&flags.make_install_vars, "DESTDIR") && !has_make_variable_override(&flags.make_install_vars, "DESTDIR")
@@ -652,7 +652,7 @@ pub(crate) fn run_helper_install(
); );
for install_dir in install_dirs { for install_dir in install_dirs {
let mut install_cmd = fakeroot::wrap_install_command(make_exec, Path::new(&destdir)); let mut install_cmd = fakeroot::wrap_install_command(make_exec, Path::new(&destdir))?;
install_cmd.current_dir(&install_dir); install_cmd.current_dir(&install_dir);
if make_exec_supports_make_assignments(make_exec) if make_exec_supports_make_assignments(make_exec)
&& !has_make_variable_override(&flags.make_install_vars, "DESTDIR") && !has_make_variable_override(&flags.make_install_vars, "DESTDIR")
+2 -2
View File
@@ -227,7 +227,7 @@ pub fn build(
})?; })?;
} }
let mut install_cmd = fakeroot::wrap_install_command("cmake", &install_destdir); let mut install_cmd = fakeroot::wrap_install_command("cmake", &install_destdir)?;
if !install_targets.is_empty() { if !install_targets.is_empty() {
install_cmd.arg("--build").arg(&build_dir); install_cmd.arg("--build").arg(&build_dir);
install_cmd.arg("--target"); install_cmd.arg("--target");
@@ -466,7 +466,7 @@ pub(crate) fn run_helper_install(
let destdir = std::env::var("DESTDIR").context("DESTDIR must be set for cmake_install")?; let destdir = std::env::var("DESTDIR").context("DESTDIR must be set for cmake_install")?;
let install_targets = phase_targets(&flags.make_install_target, &flags.make_install_targets); let install_targets = phase_targets(&flags.make_install_target, &flags.make_install_targets);
let mut install_cmd = fakeroot::wrap_install_command("cmake", Path::new(&destdir)); let mut install_cmd = fakeroot::wrap_install_command("cmake", Path::new(&destdir))?;
if install_targets.is_empty() { if install_targets.is_empty() {
install_cmd.arg("--install").arg(&build_dir); install_cmd.arg("--install").arg(&build_dir);
} else { } else {
+10 -8
View File
@@ -156,7 +156,7 @@ pub fn build(
} }
); );
// Use POSIX `sh` (doing something wrong if your system doesn't have it...) // Use POSIX `sh` (doing something wrong if your system doesn't have it...)
let mut cmd = fakeroot::wrap_install_command("sh", &install_destdir); let mut cmd = fakeroot::wrap_install_command("sh", &install_destdir)?;
let wrapper = crate::shell_helpers::wrap_shell_command(". \"$1\""); let wrapper = crate::shell_helpers::wrap_shell_command(". \"$1\"");
// Run custom scripts through `sh -c` so helper commands like `haul` // Run custom scripts through `sh -c` so helper commands like `haul`
// work even when the helper scripts live on a `noexec` mount. // work even when the helper scripts live on a `noexec` mount.
@@ -257,7 +257,7 @@ fn build_function_mode_install_command(
destdir: &Path, destdir: &Path,
build_script: &Path, build_script: &Path,
build_ran: bool, build_ran: bool,
) -> Command { ) -> Result<Command> {
let mut wrapper = function_mode_shell_prelude(); let mut wrapper = function_mode_shell_prelude();
wrapper.push_str(&format!( wrapper.push_str(&format!(
"depot_build_ran={}\n", "depot_build_ran={}\n",
@@ -311,9 +311,9 @@ fn build_function_mode_install_command(
wrapper.push_str("fi\n"); wrapper.push_str("fi\n");
} }
let mut cmd = fakeroot::wrap_install_command("sh", destdir); let mut cmd = fakeroot::wrap_install_command("sh", destdir)?;
cmd.arg("-c").arg(wrapper).arg("sh").arg(build_script); cmd.arg("-c").arg(wrapper).arg("sh").arg(build_script);
cmd Ok(cmd)
} }
fn run_function_mode_build_script( fn run_function_mode_build_script(
@@ -357,7 +357,7 @@ fn run_function_mode_build_script(
.unwrap_or(false); .unwrap_or(false);
let mut install_cmd = let mut install_cmd =
build_function_mode_install_command(spec, install_destdir, build_script, build_ran); build_function_mode_install_command(spec, install_destdir, build_script, build_ran)?;
install_cmd.current_dir(build_dir); install_cmd.current_dir(build_dir);
crate::builder::prepare_tool_command(&mut install_cmd, env_vars); crate::builder::prepare_tool_command(&mut install_cmd, env_vars);
let install_status = crate::interrupts::command_status(&mut install_cmd).map_err(|e| { let install_status = crate::interrupts::command_status(&mut install_cmd).map_err(|e| {
@@ -613,18 +613,20 @@ depot_install() {
} }
#[test] #[test]
fn test_build_function_mode_commands_split_fakeroot_boundary() { fn test_build_function_mode_commands_split_fakeroot_boundary() -> Result<()> {
let tmp_dest = tempdir()?;
let build_script = Path::new("/tmp/build.sh"); let build_script = Path::new("/tmp/build.sh");
let state_file = Path::new("/tmp/build-state"); let state_file = Path::new("/tmp/build-state");
let install_destdir = Path::new("/tmp/destdir"); let install_destdir = tmp_dest.path();
let build_cmd = build_function_mode_build_command(build_script, state_file); let build_cmd = build_function_mode_build_command(build_script, state_file);
assert_eq!(build_cmd.get_program(), std::ffi::OsStr::new("sh")); assert_eq!(build_cmd.get_program(), std::ffi::OsStr::new("sh"));
let spec = mk_spec("custom-function-fakeroot-split", "1.0"); let spec = mk_spec("custom-function-fakeroot-split", "1.0");
let install_cmd = let install_cmd =
build_function_mode_install_command(&spec, install_destdir, build_script, true); build_function_mode_install_command(&spec, install_destdir, build_script, true)?;
assert_eq!(install_cmd.get_program(), std::ffi::OsStr::new("sh")); assert_eq!(install_cmd.get_program(), std::ffi::OsStr::new("sh"));
Ok(())
} }
#[test] #[test]
+1 -1
View File
@@ -100,7 +100,7 @@ pub fn build(
crate::log_info!(" Executing: {}", cmd_str); crate::log_info!(" Executing: {}", cmd_str);
// We need to run each command under internal fakeroot // We need to run each command under internal fakeroot
let mut cmd = crate::fakeroot::wrap_install_command("sh", &install_destdir); let mut cmd = crate::fakeroot::wrap_install_command("sh", &install_destdir)?;
cmd.arg("-c").arg(&cmd_str); cmd.arg("-c").arg(&cmd_str);
cmd.current_dir(src_dir); cmd.current_dir(src_dir);
+2 -2
View File
@@ -170,7 +170,7 @@ pub fn build(
})?; })?;
} }
let mut install_cmd = fakeroot::wrap_install_command("meson", &install_destdir); let mut install_cmd = fakeroot::wrap_install_command("meson", &install_destdir)?;
install_cmd.arg("install"); install_cmd.arg("install");
install_cmd.arg("-C").arg(&build_dir); install_cmd.arg("-C").arg(&build_dir);
@@ -336,7 +336,7 @@ pub(crate) fn run_helper_install(
.unwrap_or_else(|| resolve_build_dir(&helper_source_dir(), &flags)); .unwrap_or_else(|| resolve_build_dir(&helper_source_dir(), &flags));
let destdir = std::env::var("DESTDIR").context("DESTDIR must be set for meson_install")?; let destdir = std::env::var("DESTDIR").context("DESTDIR must be set for meson_install")?;
let mut install_cmd = fakeroot::wrap_install_command("meson", Path::new(&destdir)); let mut install_cmd = fakeroot::wrap_install_command("meson", Path::new(&destdir))?;
install_cmd.arg("install"); install_cmd.arg("install");
install_cmd.arg("-C").arg(&build_dir); install_cmd.arg("-C").arg(&build_dir);
for arg in extra_args { for arg in extra_args {
+2 -2
View File
@@ -212,7 +212,7 @@ pub fn build(
" (with internal fakeroot for build)" " (with internal fakeroot for build)"
} }
); );
let mut install_cmd = fakeroot::wrap_install_command(make_exec, destdir); let mut install_cmd = fakeroot::wrap_install_command(make_exec, destdir)?;
install_cmd.current_dir(&install_dir); install_cmd.current_dir(&install_dir);
if autotools::make_exec_supports_make_assignments(make_exec) if autotools::make_exec_supports_make_assignments(make_exec)
&& !autotools::has_make_variable_override(&flags.make_install_vars, "DESTDIR") && !autotools::has_make_variable_override(&flags.make_install_vars, "DESTDIR")
@@ -343,7 +343,7 @@ pub(crate) fn run_helper_install(
); );
for install_dir in install_dirs { for install_dir in install_dirs {
let mut install_cmd = fakeroot::wrap_install_command(make_exec, Path::new(&destdir)); let mut install_cmd = fakeroot::wrap_install_command(make_exec, Path::new(&destdir))?;
install_cmd.current_dir(&install_dir); install_cmd.current_dir(&install_dir);
if autotools::make_exec_supports_make_assignments(make_exec) if autotools::make_exec_supports_make_assignments(make_exec)
&& !autotools::has_make_variable_override(&flags.make_install_vars, "DESTDIR") && !autotools::has_make_variable_override(&flags.make_install_vars, "DESTDIR")
+145 -8
View File
@@ -1,14 +1,24 @@
//! Rootless install command support backed by Linux user namespaces. //! Rootless install command support backed by Linux user namespaces.
use anyhow::{Context, Result};
use std::ffi::{CString, OsString};
use std::fs; use std::fs;
use std::io; use std::io;
use std::os::unix::ffi::OsStrExt;
use std::os::unix::fs::PermissionsExt;
use std::os::unix::process::CommandExt; use std::os::unix::process::CommandExt;
use std::path::Path; use std::path::{Path, PathBuf};
use std::process::Command; use std::process::Command;
use std::sync::OnceLock;
const UID_MAP_PATH: &[u8] = b"/proc/self/uid_map\0"; const UID_MAP_PATH: &[u8] = b"/proc/self/uid_map\0";
const GID_MAP_PATH: &[u8] = b"/proc/self/gid_map\0"; const GID_MAP_PATH: &[u8] = b"/proc/self/gid_map\0";
const SETGROUPS_PATH: &[u8] = b"/proc/self/setgroups\0"; const SETGROUPS_PATH: &[u8] = b"/proc/self/setgroups\0";
const UID_XATTR: &[u8] = b"user.depot.fakeroot.uid\0";
const GID_XATTR: &[u8] = b"user.depot.fakeroot.gid\0";
const PRELOAD_LIBRARY_BYTES: &[u8] =
include_bytes!(concat!(env!("OUT_DIR"), "/libdepot_fakeroot.so"));
static PRELOAD_LIBRARY_DIR: OnceLock<tempfile::TempDir> = OnceLock::new();
/// Check if the current process is running as root. /// Check if the current process is running as root.
pub fn is_root() -> bool { pub fn is_root() -> bool {
@@ -20,15 +30,133 @@ pub fn is_root() -> bool {
/// The namespace maps UID/GID 0 to the invoking user, allowing install scripts /// The namespace maps UID/GID 0 to the invoking user, allowing install scripts
/// to perform root-owned staged installs without changing host ownership or /// to perform root-owned staged installs without changing host ownership or
/// requiring an external fakeroot implementation. /// requiring an external fakeroot implementation.
pub fn wrap_install_command(program: &str, destdir: &Path) -> Command { pub fn wrap_install_command(program: &str, destdir: &Path) -> Result<Command> {
let mut command = build_command(program, shell_script_path(program)); let mut command = build_command(program, shell_script_path(program));
command.env("DESTDIR", destdir); command.env("DESTDIR", destdir);
if !is_root() { if !is_root() {
configure_ownership_preload(&mut command)?;
configure_rootless_namespace(&mut command); configure_rootless_namespace(&mut command);
} }
command Ok(command)
}
fn configure_ownership_preload(command: &mut Command) -> Result<()> {
let library_path = materialize_preload_library()?;
let mut preload = OsString::from(&library_path);
if let Some(existing) = std::env::var_os("LD_PRELOAD").filter(|value| !value.is_empty()) {
preload.push(":");
preload.push(existing);
}
command.env("LD_PRELOAD", preload);
Ok(())
}
fn materialize_preload_library() -> Result<PathBuf> {
if let Some(dir) = PRELOAD_LIBRARY_DIR.get() {
return Ok(dir.path().join("libdepot_fakeroot.so"));
}
let candidate = tempfile::Builder::new()
.prefix("depot-fakeroot-")
.tempdir()
.context("Failed to create private fakeroot library dir")?;
let candidate_path = candidate.path().join("libdepot_fakeroot.so");
fs::write(&candidate_path, PRELOAD_LIBRARY_BYTES).with_context(|| {
format!(
"Failed to write fakeroot library {}",
candidate_path.display()
)
})?;
fs::set_permissions(&candidate_path, fs::Permissions::from_mode(0o500)).with_context(|| {
format!(
"Failed to set permissions on fakeroot library {}",
candidate_path.display()
)
})?;
let candidate_path = candidate_path.canonicalize().with_context(|| {
format!(
"Failed to resolve fakeroot library {}",
candidate_path.display()
)
})?;
if PRELOAD_LIBRARY_DIR.set(candidate).is_ok() {
Ok(candidate_path)
} else {
Ok(PRELOAD_LIBRARY_DIR
.get()
.expect("fakeroot library dir must be initialized")
.path()
.join("libdepot_fakeroot.so"))
}
}
pub(crate) fn archive_ownership(
path: &Path,
metadata: &fs::Metadata,
symlink: bool,
) -> Result<(u64, u64)> {
use std::os::unix::fs::MetadataExt;
let (default_uid, default_gid) = if is_root() {
(u64::from(metadata.uid()), u64::from(metadata.gid()))
} else {
(0, 0)
};
let uid = read_id_xattr(path, UID_XATTR, symlink)?.unwrap_or(default_uid);
let gid = read_id_xattr(path, GID_XATTR, symlink)?.unwrap_or(default_gid);
Ok((uid, gid))
}
fn read_id_xattr(path: &Path, name: &[u8], symlink: bool) -> Result<Option<u64>> {
let path = CString::new(path.as_os_str().as_bytes())
.with_context(|| format!("Path contains an embedded NUL: {}", path.display()))?;
let mut value = [0_u8; 16];
let length = unsafe {
if symlink {
nix::libc::lgetxattr(
path.as_ptr(),
name.as_ptr().cast(),
value.as_mut_ptr().cast(),
value.len(),
)
} else {
nix::libc::getxattr(
path.as_ptr(),
name.as_ptr().cast(),
value.as_mut_ptr().cast(),
value.len(),
)
}
};
if length < 0 {
let error = io::Error::last_os_error();
return match error.raw_os_error() {
Some(code) if code == nix::libc::ENODATA || code == nix::libc::ENOTSUP => Ok(None),
_ => Err(error).with_context(|| {
format!(
"Failed to read fakeroot ownership from {}",
path.to_string_lossy()
)
}),
};
}
let value = std::str::from_utf8(&value[..length as usize]).with_context(|| {
format!(
"Invalid fakeroot ownership metadata on {}",
path.to_string_lossy()
)
})?;
let id = value.parse::<u64>().with_context(|| {
format!(
"Invalid fakeroot ownership value {value:?} on {}",
path.to_string_lossy()
)
})?;
Ok(Some(id))
} }
fn build_command(program: &str, script_path: Option<&Path>) -> Command { fn build_command(program: &str, script_path: Option<&Path>) -> Command {
@@ -126,11 +254,14 @@ mod tests {
#[test] #[test]
fn rootless_command_runs_as_namespace_root() -> Result<()> { fn rootless_command_runs_as_namespace_root() -> Result<()> {
if is_root() {
return Ok(());
}
let destdir = tempfile::tempdir()?; let destdir = tempfile::tempdir()?;
let mut command = wrap_install_command("/bin/sh", destdir.path()); let mut command = wrap_install_command("/bin/sh", destdir.path())?;
command command
.arg("-c") .arg("-c")
.arg("test \"$(/usr/bin/id -u)\" = 0; test \"$(/usr/bin/id -g)\" = 0; /usr/bin/touch \"$DESTDIR/root-owned\"; /usr/bin/chown 0:0 \"$DESTDIR/root-owned\"; /usr/bin/chmod 4755 \"$DESTDIR/root-owned\""); .arg("test \"$(/usr/bin/id -u)\" = 0; test \"$(/usr/bin/id -g)\" = 0; /usr/bin/touch \"$DESTDIR/root-owned\"; if test -x /usr/bin/python3; then /usr/bin/python3 -c 'import os; os.chown(os.environ[\"DESTDIR\"] + \"/root-owned\", 0, 81)'; else /usr/bin/chown 0:81 \"$DESTDIR/root-owned\"; fi; /usr/bin/chmod 4755 \"$DESTDIR/root-owned\"");
let status = command let status = command
.status() .status()
@@ -141,16 +272,22 @@ mod tests {
assert_eq!(metadata.uid(), nix::unistd::geteuid().as_raw()); assert_eq!(metadata.uid(), nix::unistd::geteuid().as_raw());
assert_eq!(metadata.gid(), nix::unistd::getegid().as_raw()); assert_eq!(metadata.gid(), nix::unistd::getegid().as_raw());
assert_eq!(metadata.mode() & 0o7777, 0o4755); assert_eq!(metadata.mode() & 0o7777, 0o4755);
assert_eq!(
archive_ownership(&destdir.path().join("root-owned"), &metadata, false)?,
(0, 81)
);
Ok(()) Ok(())
} }
#[test] #[test]
fn wrapped_command_preserves_program_and_sets_destdir() { fn wrapped_command_preserves_program_and_sets_destdir() -> Result<()> {
let command = wrap_install_command("make", Path::new("/tmp/package-root")); let destdir = tempfile::tempdir()?;
let command = wrap_install_command("make", destdir.path())?;
assert_eq!(command.get_program(), "make"); assert_eq!(command.get_program(), "make");
assert!(command.get_envs().any(|(key, value)| { assert!(command.get_envs().any(|(key, value)| {
key == "DESTDIR" && value.is_some_and(|value| value == "/tmp/package-root") key == "DESTDIR" && value.is_some_and(|value| value == destdir.path())
})); }));
Ok(())
} }
} }
+144
View File
@@ -0,0 +1,144 @@
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/xattr.h>
#include <unistd.h>
#define DEPOT_UID_XATTR "user.depot.fakeroot.uid"
#define DEPOT_GID_XATTR "user.depot.fakeroot.gid"
static uid_t mapped_uid(uid_t uid) {
return uid == (uid_t)-1 ? uid : 0;
}
static gid_t mapped_gid(gid_t gid) {
return gid == (gid_t)-1 ? gid : 0;
}
static int missing_xattr_error(int error) {
return error == ENODATA || error == ENOTSUP;
}
static int update_id_path(const char *path, const char *name, unsigned int id,
int nofollow) {
char value[16];
int length = snprintf(value, sizeof(value), "%u", id);
if (length < 0 || (size_t)length >= sizeof(value)) {
errno = EOVERFLOW;
return -1;
}
if (id == 0) {
int result = nofollow != 0 ? lremovexattr(path, name)
: removexattr(path, name);
if (result == 0 || missing_xattr_error(errno) ||
(nofollow != 0 && errno == EPERM)) {
return 0;
}
return -1;
}
if (nofollow != 0) {
return lsetxattr(path, name, value, (size_t)length, 0);
}
return setxattr(path, name, value, (size_t)length, 0);
}
static int update_id_fd(int fd, const char *name, unsigned int id) {
char value[16];
int length = snprintf(value, sizeof(value), "%u", id);
if (length < 0 || (size_t)length >= sizeof(value)) {
errno = EOVERFLOW;
return -1;
}
if (id == 0) {
int result = fremovexattr(fd, name);
if (result == 0 || missing_xattr_error(errno)) {
return 0;
}
return -1;
}
return fsetxattr(fd, name, value, (size_t)length, 0);
}
static int record_path(const char *path, uid_t uid, gid_t gid, int nofollow) {
if (uid != (uid_t)-1 &&
update_id_path(path, DEPOT_UID_XATTR, (unsigned int)uid, nofollow) !=
0) {
return -1;
}
if (gid != (gid_t)-1 &&
update_id_path(path, DEPOT_GID_XATTR, (unsigned int)gid, nofollow) !=
0) {
return -1;
}
return 0;
}
static int record_fd(int fd, uid_t uid, gid_t gid) {
if (uid != (uid_t)-1 &&
update_id_fd(fd, DEPOT_UID_XATTR, (unsigned int)uid) != 0) {
return -1;
}
if (gid != (gid_t)-1 &&
update_id_fd(fd, DEPOT_GID_XATTR, (unsigned int)gid) != 0) {
return -1;
}
return 0;
}
int chown(const char *path, uid_t uid, gid_t gid) {
if (syscall(SYS_chown, path, mapped_uid(uid), mapped_gid(gid)) != 0) {
return -1;
}
return record_path(path, uid, gid, 0);
}
int lchown(const char *path, uid_t uid, gid_t gid) {
if (syscall(SYS_lchown, path, mapped_uid(uid), mapped_gid(gid)) != 0) {
return -1;
}
return record_path(path, uid, gid, 1);
}
int fchown(int fd, uid_t uid, gid_t gid) {
if (syscall(SYS_fchown, fd, mapped_uid(uid), mapped_gid(gid)) != 0) {
return -1;
}
return record_fd(fd, uid, gid);
}
int fchownat(int dirfd, const char *path, uid_t uid, gid_t gid, int flags) {
int fd;
int open_flags = O_RDONLY | O_CLOEXEC | O_NONBLOCK;
if (syscall(SYS_fchownat, dirfd, path, mapped_uid(uid), mapped_gid(gid),
flags) != 0) {
return -1;
}
if ((flags & AT_SYMLINK_NOFOLLOW) != 0) {
if (dirfd == AT_FDCWD || path[0] == '/') {
return record_path(path, uid, gid, 1);
}
errno = ENOTSUP;
return -1;
}
fd = openat(dirfd, path, open_flags);
if (fd < 0) {
return -1;
}
if (record_fd(fd, uid, gid) != 0) {
int saved_errno = errno;
close(fd);
errno = saved_errno;
return -1;
}
return close(fd);
}
+63 -10
View File
@@ -8,7 +8,7 @@ use std::collections::HashMap;
use std::fs; use std::fs;
use std::os::unix::fs::MetadataExt; use std::os::unix::fs::MetadataExt;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use tar::{Builder, EntryType}; use tar::{Builder, EntryType, Header, HeaderMode};
use zstd::stream::write::Encoder; use zstd::stream::write::Encoder;
type HardlinkKey = (u64, u64); type HardlinkKey = (u64, u64);
@@ -42,6 +42,20 @@ fn license_value(licenses: &[String]) -> toml::Value {
} }
} }
fn package_header(
path: &Path,
metadata: &fs::Metadata,
symlink: bool,
mode: HeaderMode,
) -> Result<Header> {
let mut header = Header::new_gnu();
header.set_metadata_in_mode(metadata, mode);
let (uid, gid) = crate::fakeroot::archive_ownership(path, metadata, symlink)?;
header.set_uid(uid);
header.set_gid(gid);
Ok(header)
}
pub struct Packager { pub struct Packager {
pub spec: PackageSpec, pub spec: PackageSpec,
pub destdir: PathBuf, pub destdir: PathBuf,
@@ -109,15 +123,16 @@ impl Packager {
let file_type = entry.file_type(); let file_type = entry.file_type();
if file_type.is_dir() { if file_type.is_dir() {
tar.append_dir(rel_path, path)?; let metadata = fs::symlink_metadata(path)?;
let mut header = package_header(path, &metadata, false, HeaderMode::Complete)?;
header.set_size(0);
tar.append_data(&mut header, rel_path, std::io::empty())?;
} else if file_type.is_symlink() { } else if file_type.is_symlink() {
// For symlinks, we need to read the link and append to tar correctly // For symlinks, we need to read the link and append to tar correctly
let target = fs::read_link(path)?; let target = fs::read_link(path)?;
let mut header = tar::Header::new_gnu(); let metadata = fs::symlink_metadata(path)?;
header.set_metadata_in_mode( let mut header = package_header(path, &metadata, true, HeaderMode::Deterministic)?;
&fs::symlink_metadata(path)?, header.set_size(0);
tar::HeaderMode::Deterministic,
);
tar.append_link(&mut header, rel_path, target)?; tar.append_link(&mut header, rel_path, target)?;
} else { } else {
// Files // Files
@@ -126,14 +141,15 @@ impl Packager {
if let Some(first_rel_path) = if let Some(first_rel_path) =
hardlink_key.and_then(|key| archived_hardlinks.get(&key)) hardlink_key.and_then(|key| archived_hardlinks.get(&key))
{ {
let mut header = tar::Header::new_gnu(); let mut header =
header.set_metadata_in_mode(&metadata, tar::HeaderMode::Deterministic); package_header(path, &metadata, false, HeaderMode::Deterministic)?;
header.set_entry_type(EntryType::Link); header.set_entry_type(EntryType::Link);
header.set_size(0); header.set_size(0);
tar.append_link(&mut header, rel_path, first_rel_path)?; tar.append_link(&mut header, rel_path, first_rel_path)?;
} else { } else {
let mut file = fs::File::open(path)?; let mut file = fs::File::open(path)?;
tar.append_file(rel_path, &mut file)?; let mut header = package_header(path, &metadata, false, HeaderMode::Complete)?;
tar.append_data(&mut header, rel_path, &mut file)?;
if let Some(key) = hardlink_key { if let Some(key) = hardlink_key {
archived_hardlinks.insert(key, rel_path.to_path_buf()); archived_hardlinks.insert(key, rel_path.to_path_buf());
} }
@@ -683,4 +699,41 @@ mod tests {
assert_eq!(coreutils_meta.nlink(), 2); assert_eq!(coreutils_meta.nlink(), 2);
assert_eq!(ls_meta.nlink(), 2); assert_eq!(ls_meta.nlink(), 2);
} }
#[test]
fn test_create_package_preserves_rootless_fakeroot_ownership() -> Result<()> {
let tmp = tempfile::tempdir()?;
let dest = tmp.path().join("dest");
let out = tmp.path().join("out");
fs::create_dir_all(dest.join("usr/lib"))?;
fs::create_dir_all(&out)?;
let mut command = crate::fakeroot::wrap_install_command("/bin/sh", &dest)?;
command.arg("-c").arg(
"/usr/bin/touch \"$DESTDIR/usr/lib/dbus-daemon-launch-helper\"; \
/usr/bin/chown 0:81 \"$DESTDIR/usr/lib/dbus-daemon-launch-helper\"; \
/usr/bin/chmod 4750 \"$DESTDIR/usr/lib/dbus-daemon-launch-helper\"",
);
let status = command.status()?;
assert!(status.success());
let packager = mk_packager(dest);
let archive_path = packager.create_package(&out, "x86_64")?;
let archive_file = fs::File::open(archive_path)?;
let decoder = zstd::Decoder::new(archive_file)?;
let mut archive = tar::Archive::new(decoder);
let mut found = false;
for entry in archive.entries()? {
let entry = entry?;
if entry.path()?.as_ref() == Path::new("usr/lib/dbus-daemon-launch-helper") {
assert_eq!(entry.header().uid()?, 0);
assert_eq!(entry.header().gid()?, 81);
assert_eq!(entry.header().mode()? & 0o7777, 0o4750);
found = true;
break;
}
}
assert!(found);
Ok(())
}
} }