diff --git a/Cargo.lock b/Cargo.lock index f74813b23..2f1340ffa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -173,7 +173,7 @@ version = "0.1.0" source = "git+https://github.com/bootc-dev/bcvk?rev=f4dd05a74ccb54084093c9167317a14356ca53e4#f4dd05a74ccb54084093c9167317a14356ca53e4" dependencies = [ "camino", - "cap-std-ext 5.1.1", + "cap-std-ext 5.1.2", "color-eyre", "data-encoding", "libc", @@ -224,7 +224,7 @@ version = "0.1.0" dependencies = [ "anyhow", "bootc-kernel-cmdline", - "cap-std-ext 5.1.1", + "cap-std-ext 5.1.2", "cfsctl", "clap", "fn-error-context", @@ -242,7 +242,7 @@ dependencies = [ "bootc-internal-mount", "bootc-internal-utils", "camino", - "cap-std-ext 5.1.1", + "cap-std-ext 5.1.2", "fn-error-context", "indoc", "libc", @@ -261,7 +261,7 @@ dependencies = [ "anyhow", "bootc-internal-utils", "camino", - "cap-std-ext 5.1.1", + "cap-std-ext 5.1.2", "fn-error-context", "indoc", "libc", @@ -277,7 +277,7 @@ version = "1.15.1" dependencies = [ "anstream 1.0.0", "anyhow", - "cap-std-ext 5.1.1", + "cap-std-ext 5.1.2", "chrono", "owo-colors", "rustix", @@ -319,7 +319,7 @@ dependencies = [ "bootc-tmpfiles", "camino", "canon-json", - "cap-std-ext 5.1.1", + "cap-std-ext 5.1.2", "cfg-if", "cfsctl", "chrono", @@ -370,7 +370,7 @@ dependencies = [ "anyhow", "bootc-internal-utils", "camino", - "cap-std-ext 5.1.1", + "cap-std-ext 5.1.2", "fn-error-context", "hex", "indoc", @@ -388,7 +388,7 @@ dependencies = [ "anyhow", "bootc-internal-utils", "camino", - "cap-std-ext 5.1.1", + "cap-std-ext 5.1.2", "fn-error-context", "indoc", "rustix", @@ -523,9 +523,9 @@ dependencies = [ [[package]] name = "cap-std-ext" -version = "5.1.1" +version = "5.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f9d7cf114dea582f663f03f4c563d0fc5ca2c8fa4c496eb538d8f01981ea51" +checksum = "6c6c5ac70c4465b47c3a322330f87c2df31e42ac527497d94dc1f1d1cb8989f9" dependencies = [ "cap-primitives 4.0.2", "cap-tempfile 4.0.2", @@ -1216,7 +1216,7 @@ version = "0.1.0" dependencies = [ "anstream 1.0.0", "anyhow", - "cap-std-ext 5.1.1", + "cap-std-ext 5.1.2", "cfsctl", "fn-error-context", "hex", @@ -2079,7 +2079,7 @@ checksum = "784ff94e3f5a9b89659b8a4442104df6b5e7974c9b355c4f4ae6e9794af1ad2b" dependencies = [ "camino", "canon-json", - "cap-std-ext 5.1.1", + "cap-std-ext 5.1.2", "chrono", "flate2", "hex", @@ -2179,7 +2179,7 @@ dependencies = [ "bootc-internal-utils", "camino", "canon-json", - "cap-std-ext 5.1.1", + "cap-std-ext 5.1.2", "cfsctl", "chrono", "clap", @@ -3004,7 +3004,7 @@ dependencies = [ "bcvk-qemu", "bootc-kernel-cmdline", "camino", - "cap-std-ext 5.1.1", + "cap-std-ext 5.1.2", "clap", "data-encoding", "fn-error-context", diff --git a/Cargo.toml b/Cargo.toml index 217789e1b..0b4b16281 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,7 +35,7 @@ anstream = "1.0" anyhow = "1.0.82" camino = "1.1.6" canon-json = "0.2.1" -cap-std-ext = "5.1.1" +cap-std-ext = "5.1.2" cfg-if = "1.0" chrono = { version = "0.4.38", default-features = false } clap = "4.5.4" diff --git a/crates/blockdev/src/blockdev.rs b/crates/blockdev/src/blockdev.rs index 666faf1d2..44c23d34e 100644 --- a/crates/blockdev/src/blockdev.rs +++ b/crates/blockdev/src/blockdev.rs @@ -382,10 +382,39 @@ pub fn list_dev(dev: &Utf8Path) -> Result { .ok_or_else(|| anyhow!("no device output from lsblk for {dev}")) } +#[context("Finding block device for ZFS dataset {dataset}")] +fn list_dev_for_zfs_dataset(dataset: &str) -> Result { + let dataset = dataset.strip_prefix("ZFS=").unwrap_or(dataset); + let pool = dataset + .split('/') + .next() + .ok_or_else(|| anyhow!("Invalid ZFS dataset: {dataset}"))?; + + let output = Command::new("zpool") + .args(["list", "-H", "-v", "-P", pool]) + .run_get_string() + .with_context(|| format!("Querying ZFS pool {pool}"))?; + + for line in output.lines() { + if line.starts_with('\t') || line.starts_with(' ') { + let dev_path = line.trim_start().split('\t').next().unwrap_or("").trim(); + if dev_path.starts_with('/') { + return list_dev(Utf8Path::new(dev_path)); + } + } + } + + anyhow::bail!("Could not find a block device backing ZFS pool {pool}") +} + /// List the device containing the filesystem mounted at the given directory. pub fn list_dev_by_dir(dir: &Dir) -> Result { let fsinfo = bootc_mount::inspect_filesystem_of_dir(dir)?; - list_dev(&Utf8PathBuf::from(&fsinfo.source)) + let source = &fsinfo.source; + if fsinfo.fstype == "zfs" || source.starts_with("ZFS=") { + return list_dev_for_zfs_dataset(source); + } + list_dev(&Utf8PathBuf::from(source)) } pub struct LoopbackDevice { diff --git a/crates/lib/src/discoverable_partition_specification.rs b/crates/lib/src/discoverable_partition_specification.rs index b4ef6cb6b..a743d17cb 100644 --- a/crates/lib/src/discoverable_partition_specification.rs +++ b/crates/lib/src/discoverable_partition_specification.rs @@ -521,6 +521,8 @@ pub const fn this_arch_root() -> &'static str { ROOT_PPC64 } else if #[cfg(all(target_arch = "powerpc64", target_endian = "little"))] { ROOT_PPC64_LE + } else if #[cfg(target_arch = "riscv64")] { + ROOT_RISCV64 } else { compile_error!("Unsupported architecture") } diff --git a/crates/lib/src/podstorage.rs b/crates/lib/src/podstorage.rs index 7d826b2ce..b41584d08 100644 --- a/crates/lib/src/podstorage.rs +++ b/crates/lib/src/podstorage.rs @@ -19,7 +19,7 @@ use bootc_utils::{AsyncCommandRunExt, CommandRunExt, ExitStatusExt}; use camino::{Utf8Path, Utf8PathBuf}; use cap_std_ext::cap_std::fs::Dir; use cap_std_ext::cap_tempfile::TempDir; -use cap_std_ext::cmdext::CapStdExtCommandExt; +use cap_std_ext::cmdext::{CapStdExtCommandExt, CmdFds}; use cap_std_ext::dirext::CapStdExtDirExt; use cap_std_ext::{cap_std, cap_tempfile}; use fn_error_context::context; @@ -80,7 +80,12 @@ pub(crate) enum PullMode { #[allow(unsafe_code)] #[context("Binding storage roots")] -fn bind_storage_roots(cmd: &mut Command, storage_root: &Dir, run_root: &Dir) -> Result<()> { +pub(crate) fn bind_storage_roots( + cmd: &mut Command, + fds: &mut CmdFds, + storage_root: &Dir, + run_root: &Dir, +) -> Result<()> { // podman requires an absolute path, for two reasons right now: // - It writes the file paths into `db.sql`, a sqlite database for unknown reasons // - It forks helper binaries, so just giving it /proc/self/fd won't work as @@ -121,19 +126,16 @@ fn bind_storage_roots(cmd: &mut Command, storage_root: &Dir, run_root: &Dir) -> Ok(()) }) }; - cmd.take_fd_n(run_root, STORAGE_RUN_FD); + fds.take_fd_n(run_root, STORAGE_RUN_FD); Ok(()) } -// Initialize a `podman` subprocess with: -// - storage overridden to point to to storage_root -// - Authentication (auth.json) using the bootc/ostree owned auth -fn new_podman_cmd_in(sysroot: &Dir, storage_root: &Dir, run_root: &Dir) -> Result { - let mut cmd = Command::new("podman"); - bind_storage_roots(&mut cmd, storage_root, run_root)?; - let run_root = format!("/proc/self/fd/{STORAGE_RUN_FD}"); - cmd.args(["--root", STORAGE_ALIAS_DIR, "--runroot", run_root.as_str()]); - +/// Set up `REGISTRY_AUTH_FILE` on a command, passing the bootc/ostree +/// auth file via an anonymous tmpfile fd. +/// +/// If no bootc-owned auth is configured, an empty `{}` is passed to +/// prevent podman from falling back to user-owned auth paths. +pub(crate) fn setup_auth(cmd: &mut Command, fds: &mut CmdFds, sysroot: &Dir) -> Result<()> { let tmpd = &cap_std::fs::Dir::open_ambient_dir("/tmp", cap_std::ambient_authority())?; let mut tempfile = cap_tempfile::TempFile::new_anonymous(tmpd).map(std::io::BufWriter::new)?; @@ -154,9 +156,23 @@ fn new_podman_cmd_in(sysroot: &Dir, storage_root: &Dir, run_root: &Dir) -> Resul .into_std(); let fd: Arc = std::sync::Arc::new(tempfile.into()); let target_fd = fd.as_fd().as_raw_fd(); - cmd.take_fd_n(fd, target_fd); + fds.take_fd_n(fd, target_fd); cmd.env("REGISTRY_AUTH_FILE", format!("/proc/self/fd/{target_fd}")); + Ok(()) +} + +// Initialize a `podman` subprocess with: +// - storage overridden to point to to storage_root +// - Authentication (auth.json) using the bootc/ostree owned auth +fn new_podman_cmd_in(sysroot: &Dir, storage_root: &Dir, run_root: &Dir) -> Result { + let mut cmd = Command::new("podman"); + let mut fds = CmdFds::new(); + bind_storage_roots(&mut cmd, &mut fds, storage_root, run_root)?; + let run_root = format!("/proc/self/fd/{STORAGE_RUN_FD}"); + cmd.args(["--root", STORAGE_ALIAS_DIR, "--runroot", run_root.as_str()]); + setup_auth(&mut cmd, &mut fds, sysroot)?; + cmd.take_fds(fds); Ok(cmd) } @@ -435,7 +451,9 @@ impl CStorage { cmd.stdout(Stdio::null()); // An ephemeral place for the transient state; let temp_runroot = TempDir::new(cap_std::ambient_authority())?; - bind_storage_roots(&mut cmd, &self.storage_root, &temp_runroot)?; + let mut fds = CmdFds::new(); + bind_storage_roots(&mut cmd, &mut fds, &self.storage_root, &temp_runroot)?; + cmd.take_fds(fds); // The destination (target stateroot) + container storage dest let storage_dest = &format!( diff --git a/crates/ostree-ext/src/container/skopeo.rs b/crates/ostree-ext/src/container/skopeo.rs index eceaf0935..f70ae4920 100644 --- a/crates/ostree-ext/src/container/skopeo.rs +++ b/crates/ostree-ext/src/container/skopeo.rs @@ -2,7 +2,7 @@ use super::ImageReference; use anyhow::{Context, Result}; -use cap_std_ext::cmdext::CapStdExtCommandExt; +use cap_std_ext::cmdext::{CapStdExtCommandExt, CmdFds}; use containers_image_proxy::oci_spec::image as oci_image; use fn_error_context::context; use io_lifetimes::OwnedFd; @@ -80,7 +80,9 @@ pub async fn copy( cmd.arg("--digestfile"); cmd.arg(digestfile.path()); if let Some((add_fd, n)) = add_fd { - cmd.take_fd_n(add_fd, n); + let mut fds = CmdFds::new(); + fds.take_fd_n(add_fd, n); + cmd.take_fds(fds); } if let Some(authfile) = authfile { cmd.arg("--authfile"); diff --git a/crates/ostree-ext/src/tar/write.rs b/crates/ostree-ext/src/tar/write.rs index ec2e1b41f..7b4a9b2d8 100644 --- a/crates/ostree-ext/src/tar/write.rs +++ b/crates/ostree-ext/src/tar/write.rs @@ -14,7 +14,7 @@ use camino::{Utf8Component, Utf8Path, Utf8PathBuf}; use cap_std::io_lifetimes; use cap_std_ext::cap_std::fs::Dir; -use cap_std_ext::cmdext::CapStdExtCommandExt; +use cap_std_ext::cmdext::{CapStdExtCommandExt, CmdFds}; use cap_std_ext::{cap_std, cap_tempfile}; use containers_image_proxy::oci_spec::image as oci_image; use fn_error_context::context; @@ -423,7 +423,9 @@ pub async fn write_tar( .stdout(Stdio::piped()) .stderr(Stdio::piped()) .args(["commit"]); - c.take_fd_n(repofd.clone(), 3); + let mut fds = CmdFds::new(); + fds.take_fd_n(repofd.clone(), 3); + c.take_fds(fds); c.arg("--repo=/proc/self/fd/3"); if let Some(sepolicy) = sepolicy.as_ref() { c.arg("--selinux-policy"); diff --git a/hack/provision-derived.sh b/hack/provision-derived.sh index 4b4b342d6..9d5910aff 100755 --- a/hack/provision-derived.sh +++ b/hack/provision-derived.sh @@ -107,7 +107,23 @@ repo_gpgcheck=0 enabled=1 enabled_metadata=1 REPOEOF -dnf -y install bootupd-0.2.32.43.g38208d3 + +# This unfortunately has "older" versions with higher NEVRA: +# +# # dnf --disablerepo=* --enablerepo=copr:copr.fedorainfracloud.org:group_CoreOS:continuous repoquery bootupd 2> /dev/null +# bootupd-0:0.2.32.45.gb483a63-1.fc45.x86_64 +# bootupd-0:202501200321.0.2.25.65.ge296f82-1.fc42.src +# bootupd-0:202501200321.0.2.25.65.ge296f82-1.fc42.x86_64 +# bootupd-0:202501210627.0.2.25.67.gefe41b6-1.fc42.src +# +# So we need to be more selective, but also be dynamic to grab newer +# versions +# +# The subscription-manager plugin needs to be disabled because it +# likes to write warnings to stdout which corrupts the NEVRA output +# we're going for here... +bootupd_nevra=$(dnf --disableplugin=subscription-manager --disablerepo=* --enablerepo=copr:copr.fedorainfracloud.org:group_CoreOS:continuous repoquery --latest-limit 1 --arch "$(uname -m)" "bootupd-0.2.*") +dnf -y install ${bootupd_nevra} rm -f /etc/yum.repos.d/coreos-continuous.repo dnf clean all