Skip to content

Commit 395021a

Browse files
committed
install: Re-implement hostns to work around supermin
I'm experimenting with doing `bootc install` in a supermin VM like we do in coreos-assembler, but hit upon the fact that supermin's `/sbin/init` implementation incorrectly does `chroot()` instead of a `pivot_root`: https://github.com/libguestfs/supermin/blob/5230e2c3cd07e82bd6431e871e239f7056bf25ad/init/init.c#L288 The problem here then is doing `nsenter` just takes us into the original rootfs, and all the real content is in `/root`. Re-implement this by re-executing our own binary and doing the `setns()` and `chroot()` dance internally, because we can't really do it with external binaries. Signed-off-by: Colin Walters <[email protected]>
1 parent 9137908 commit 395021a

File tree

6 files changed

+38
-12
lines changed

6 files changed

+38
-12
lines changed

lib/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ liboverdrop = "0.1.0"
2929
once_cell = "1.9"
3030
openssl = "^0.10"
3131
# TODO drop this in favor of rustix
32-
nix = { version = "0.27", features = ["ioctl"] }
32+
nix = { version = "0.27", features = ["ioctl", "sched"] }
3333
regex = "1.7.1"
3434
rustix = { "version" = "0.38", features = ["thread", "fs", "system", "process"] }
3535
schemars = { version = "0.8.6", features = ["chrono"] }

lib/src/blockdev.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1+
use crate::install::run_in_host_mountns;
12
use crate::task::Task;
2-
use crate::utils::run_in_host_mountns;
33
use anyhow::{anyhow, Context, Result};
44
use camino::Utf8Path;
55
use fn_error_context::context;

lib/src/cli.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,11 @@ pub(crate) enum Opt {
155155
/// Install to the target block device
156156
#[cfg(feature = "install")]
157157
Install(crate::install::InstallOpts),
158+
/// Execute the given command in the host mount namespace
159+
#[cfg(feature = "install")]
160+
#[clap(hide = true)]
161+
#[command(external_subcommand)]
162+
ExecInHostMountNamespace(Vec<OsString>),
158163
/// Install to the target filesystem.
159164
#[cfg(feature = "install")]
160165
InstallToFilesystem(crate::install::InstallToFilesystemOpts),
@@ -503,6 +508,10 @@ async fn run_from_opt(opt: Opt) -> Result<()> {
503508
Opt::Install(opts) => crate::install::install(opts).await,
504509
#[cfg(feature = "install")]
505510
Opt::InstallToFilesystem(opts) => crate::install::install_to_filesystem(opts).await,
511+
#[cfg(feature = "install")]
512+
Opt::ExecInHostMountNamespace(args) => {
513+
crate::install::exec_in_host_mountns(args.as_slice())
514+
}
506515
Opt::Status(opts) => super::status::status(opts).await,
507516
#[cfg(feature = "internal-testing-api")]
508517
Opt::InternalTests(opts) => crate::privtests::run(opts).await,

lib/src/install.rs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ mod baseline;
1111
use std::io::BufWriter;
1212
use std::io::Write;
1313
use std::os::fd::AsFd;
14+
use std::os::unix::process::CommandExt;
15+
use std::process::Command;
1416
use std::str::FromStr;
1517
use std::sync::Arc;
1618

@@ -37,7 +39,6 @@ use serde::{Deserialize, Serialize};
3739
use self::baseline::InstallBlockDeviceOpts;
3840
use crate::containerenv::ContainerExecutionInfo;
3941
use crate::task::Task;
40-
use crate::utils::run_in_host_mountns;
4142

4243
/// The default "stateroot" or "osname"; see https://github.com/ostreedev/ostree/issues/2794
4344
const STATEROOT_DEFAULT: &str = "default";
@@ -608,6 +609,30 @@ fn copy_to_oci(
608609
Ok(dest_imageref)
609610
}
610611

612+
/// Run a command in the host mount namespace
613+
pub(crate) fn run_in_host_mountns(cmd: &str) -> Command {
614+
let mut c = Command::new("/proc/self/exe");
615+
c.args(["exec-in-host-mount-namespace", cmd]);
616+
c
617+
}
618+
619+
#[context("Re-exec in host mountns")]
620+
pub(crate) fn exec_in_host_mountns(args: &[std::ffi::OsString]) -> Result<()> {
621+
let (cmd, args) = args[1..]
622+
.split_first()
623+
.ok_or_else(|| anyhow::anyhow!("Missing command"))?;
624+
let pid1mountns = std::fs::File::open("/proc/1/ns/mnt")?;
625+
nix::sched::setns(pid1mountns.as_fd(), nix::sched::CloneFlags::CLONE_NEWNS).context("setns")?;
626+
rustix::process::chdir("/")?;
627+
// Work around supermin doing chroot() and not pivot_root
628+
// https://github.com/libguestfs/supermin/blob/5230e2c3cd07e82bd6431e871e239f7056bf25ad/init/init.c#L288
629+
if !Utf8Path::new("/usr").try_exists()? && Utf8Path::new("/root/usr").try_exists()? {
630+
tracing::debug!("Using supermin workaround");
631+
rustix::process::chroot("/root")?;
632+
}
633+
Err(Command::new(cmd).args(args).exec())?
634+
}
635+
611636
#[context("Querying skopeo version")]
612637
fn skopeo_supports_containers_storage() -> Result<bool> {
613638
let o = run_in_host_mountns("skopeo").arg("--version").output()?;

lib/src/podman.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use anyhow::{anyhow, Result};
22
use serde::Deserialize;
33

4-
use crate::utils::run_in_host_mountns;
4+
use crate::install::run_in_host_mountns;
55

66
#[derive(Deserialize)]
77
#[serde(rename_all = "PascalCase")]

lib/src/utils.rs

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,6 @@ pub(crate) fn find_mount_option<'a>(
3232
.next()
3333
}
3434

35-
/// Run a command in the host mount namespace
36-
#[allow(dead_code)]
37-
pub(crate) fn run_in_host_mountns(cmd: &str) -> Command {
38-
let mut c = Command::new("nsenter");
39-
c.args(["-m", "-t", "1", "--", cmd]);
40-
c
41-
}
42-
4335
pub(crate) fn spawn_editor(tmpf: &tempfile::NamedTempFile) -> Result<()> {
4436
let v = "EDITOR";
4537
let editor = std::env::var_os(v)

0 commit comments

Comments
 (0)