Skip to content

Commit fdf5630

Browse files
committed
bwrap: Fall back to direct invocation on namespace creation failure
Under QEMU user-mode emulation the host kernel can return EINVAL from clone(CLONE_NEWUSER) on bwrap's sandbox setup, and bwrap aborts before spawning the child. This trips bootc install on cross-architecture builds (see issue #2111), where bwrap is used to make bootupctl run from the target image, not the buildroot. Workaround until the upstream qemu-user/kernel/bwrap interaction is resolved: detect the "Creating new namespace failed" prefix on bwrap's stderr and re-run the target program directly. A `eprintln!` fires on every fallback so the behaviour is visible in logs. Both bootupctl sites in bootloader opt in: the `--filesystem` capability probe and `backend install`. Assisted-by: Claude Code (Opus 4.7 1M) Signed-off-by: cdellacqua <carlo.dellacqua97@gmail.com>
1 parent e4dea2b commit fdf5630

2 files changed

Lines changed: 545 additions & 11 deletions

File tree

crates/lib/src/bootloader.rs

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -94,17 +94,29 @@ pub(crate) fn supports_bootupd(root: &Dir) -> Result<bool> {
9494
///
9595
/// Runs `bootupctl backend install --help` and looks for `--filesystem` in the
9696
/// output. When `deployment_path` is set the command runs inside a bwrap
97-
/// container so we probe the binary from the target image.
97+
/// container so we probe the binary from the target image; if bwrap cannot
98+
/// create a namespace (see <https://github.com/bootc-dev/bootc/issues/2111>)
99+
/// the wrapper transparently falls back to probing the buildroot's
100+
/// `bootupctl` instead.
101+
///
102+
/// Caveat for the fallback path: a buildroot `bootupctl` older than the
103+
/// target's can falsely report no `--filesystem` support and force the
104+
/// legacy `--device` invocation even when the target image would
105+
/// support `--filesystem`. The fallback only fires under
106+
/// qemu-user-mode emulation where the bwrap path is unusable anyway,
107+
/// so we accept the divergence.
98108
fn bootupd_supports_filesystem(rootfs: &Utf8Path, deployment_path: Option<&str>) -> Result<bool> {
99-
let help_args = ["bootupctl", "backend", "install", "--help"];
109+
let help_args = ["backend", "install", "--help"];
100110
let output = if let Some(deploy) = deployment_path {
101111
let target_root = rootfs.join(deploy);
102112
BwrapCmd::new(&target_root)
103113
.set_default_path()
104-
.run_get_string(help_args)?
114+
.with_fallback("bootupctl")
115+
.args(help_args.iter().copied())
116+
.run_get_string()?
105117
} else {
106118
Command::new("bootupctl")
107-
.args(&help_args[1..])
119+
.args(&help_args)
108120
.log_debug()
109121
.run_get_string()?
110122
};
@@ -201,9 +213,24 @@ pub(crate) fn install_via_bootupd(
201213

202214
tracing::debug!("Running bootupctl via bwrap in {}", target_root);
203215

204-
// Prepend "bootupctl" to the args for bwrap
205-
let mut bwrap_args = vec!["bootupctl"];
206-
bwrap_args.extend(bootupd_args);
216+
// If bwrap can't create a namespace (see `BwrapCmdWithFallback` /
217+
// https://github.com/bootc-dev/bootc/issues/2111) we re-run
218+
// bootupctl directly in the buildroot. Outside the chroot the
219+
// trailing positional rootfs arg(s) need the real rootfs path
220+
// instead of the chroot-relative "/", which is what
221+
// `rootfs_mount` is set to in the bwrap path. The arg-pair
222+
// mapping below makes that rewrite explicit at each position.
223+
//
224+
// The fallback also drops the bwrap binds (no `/boot` from
225+
// the target rootfs is bound over the buildroot's `/boot`);
226+
// it is sound because bootupctl resolves the boot directory
227+
// through the `--filesystem`/trailing-rootfs arg we pass it,
228+
// so it ends up at `<real_rootfs>/boot` regardless of what
229+
// `/boot` is on the host running it.
230+
let real_rootfs = rootfs.as_str();
231+
let arg_pairs = bootupd_args
232+
.iter()
233+
.map(|&a| (a, if a == "/" { real_rootfs } else { a }));
207234

208235
let mut cmd = BwrapCmd::new(&target_root)
209236
// Bind mount /boot from the physical target root so bootupctl can find
@@ -218,7 +245,10 @@ pub(crate) fn install_via_bootupd(
218245

219246
// The $PATH in the bwrap env is not complete enough for some images
220247
// so we inject a reasonable default.
221-
cmd.set_default_path().run(bwrap_args)
248+
cmd.set_default_path()
249+
.with_fallback("bootupctl")
250+
.arg_pairs(arg_pairs)
251+
.run()
222252
} else {
223253
// Running directly without chroot
224254
Command::new("bootupctl")

0 commit comments

Comments
 (0)