diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 3e6493b42..766d009e5 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -29,7 +29,7 @@ liboverdrop = "0.1.0" once_cell = "1.9" openssl = "^0.10" # TODO drop this in favor of rustix -nix = { version = "0.27", features = ["ioctl"] } +nix = { version = "0.27", features = ["ioctl", "sched"] } regex = "1.7.1" rustix = { "version" = "0.38", features = ["thread", "fs", "system", "process"] } schemars = { version = "0.8.6", features = ["chrono"] } diff --git a/lib/src/blockdev.rs b/lib/src/blockdev.rs index 51e4cdbf6..42406a992 100644 --- a/lib/src/blockdev.rs +++ b/lib/src/blockdev.rs @@ -1,5 +1,5 @@ +use crate::install::run_in_host_mountns; use crate::task::Task; -use crate::utils::run_in_host_mountns; use anyhow::{anyhow, Context, Result}; use camino::Utf8Path; use fn_error_context::context; diff --git a/lib/src/cli.rs b/lib/src/cli.rs index 9f17fc177..42af1bcba 100644 --- a/lib/src/cli.rs +++ b/lib/src/cli.rs @@ -155,6 +155,11 @@ pub(crate) enum Opt { /// Install to the target block device #[cfg(feature = "install")] Install(crate::install::InstallOpts), + /// Execute the given command in the host mount namespace + #[cfg(feature = "install")] + #[clap(hide = true)] + #[command(external_subcommand)] + ExecInHostMountNamespace(Vec), /// Install to the target filesystem. #[cfg(feature = "install")] InstallToFilesystem(crate::install::InstallToFilesystemOpts), @@ -503,6 +508,10 @@ async fn run_from_opt(opt: Opt) -> Result<()> { Opt::Install(opts) => crate::install::install(opts).await, #[cfg(feature = "install")] Opt::InstallToFilesystem(opts) => crate::install::install_to_filesystem(opts).await, + #[cfg(feature = "install")] + Opt::ExecInHostMountNamespace(args) => { + crate::install::exec_in_host_mountns(args.as_slice()) + } Opt::Status(opts) => super::status::status(opts).await, #[cfg(feature = "internal-testing-api")] Opt::InternalTests(opts) => crate::privtests::run(opts).await, diff --git a/lib/src/install.rs b/lib/src/install.rs index 295b61c8d..57db68987 100644 --- a/lib/src/install.rs +++ b/lib/src/install.rs @@ -11,6 +11,8 @@ mod baseline; use std::io::BufWriter; use std::io::Write; use std::os::fd::AsFd; +use std::os::unix::process::CommandExt; +use std::process::Command; use std::str::FromStr; use std::sync::Arc; @@ -37,7 +39,6 @@ use serde::{Deserialize, Serialize}; use self::baseline::InstallBlockDeviceOpts; use crate::containerenv::ContainerExecutionInfo; use crate::task::Task; -use crate::utils::run_in_host_mountns; /// The default "stateroot" or "osname"; see https://github.com/ostreedev/ostree/issues/2794 const STATEROOT_DEFAULT: &str = "default"; @@ -608,6 +609,30 @@ fn copy_to_oci( Ok(dest_imageref) } +/// Run a command in the host mount namespace +pub(crate) fn run_in_host_mountns(cmd: &str) -> Command { + let mut c = Command::new("/proc/self/exe"); + c.args(["exec-in-host-mount-namespace", cmd]); + c +} + +#[context("Re-exec in host mountns")] +pub(crate) fn exec_in_host_mountns(args: &[std::ffi::OsString]) -> Result<()> { + let (cmd, args) = args[1..] + .split_first() + .ok_or_else(|| anyhow::anyhow!("Missing command"))?; + let pid1mountns = std::fs::File::open("/proc/1/ns/mnt")?; + nix::sched::setns(pid1mountns.as_fd(), nix::sched::CloneFlags::CLONE_NEWNS).context("setns")?; + rustix::process::chdir("/")?; + // Work around supermin doing chroot() and not pivot_root + // https://github.com/libguestfs/supermin/blob/5230e2c3cd07e82bd6431e871e239f7056bf25ad/init/init.c#L288 + if !Utf8Path::new("/usr").try_exists()? && Utf8Path::new("/root/usr").try_exists()? { + tracing::debug!("Using supermin workaround"); + rustix::process::chroot("/root")?; + } + Err(Command::new(cmd).args(args).exec())? +} + #[context("Querying skopeo version")] fn skopeo_supports_containers_storage() -> Result { let o = run_in_host_mountns("skopeo").arg("--version").output()?; diff --git a/lib/src/podman.rs b/lib/src/podman.rs index 03c0fab92..3b625fd28 100644 --- a/lib/src/podman.rs +++ b/lib/src/podman.rs @@ -1,7 +1,7 @@ use anyhow::{anyhow, Result}; use serde::Deserialize; -use crate::utils::run_in_host_mountns; +use crate::install::run_in_host_mountns; #[derive(Deserialize)] #[serde(rename_all = "PascalCase")] diff --git a/lib/src/utils.rs b/lib/src/utils.rs index 3c73e8f66..d7e959eb0 100644 --- a/lib/src/utils.rs +++ b/lib/src/utils.rs @@ -32,14 +32,6 @@ pub(crate) fn find_mount_option<'a>( .next() } -/// Run a command in the host mount namespace -#[allow(dead_code)] -pub(crate) fn run_in_host_mountns(cmd: &str) -> Command { - let mut c = Command::new("nsenter"); - c.args(["-m", "-t", "1", "--", cmd]); - c -} - pub(crate) fn spawn_editor(tmpf: &tempfile::NamedTempFile) -> Result<()> { let v = "EDITOR"; let editor = std::env::var_os(v)