feat: update depot package version to 1.0.2 and enhance fakeroot support with ownership tracking
This commit is contained in:
Generated
+1
-1
@@ -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
@@ -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]
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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
@@ -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]
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user