diff --git a/lib/src/bootloader.rs b/lib/src/bootloader.rs index f15199a30..d73577f59 100644 --- a/lib/src/bootloader.rs +++ b/lib/src/bootloader.rs @@ -1,4 +1,4 @@ -use anyhow::Result; +use anyhow::{anyhow, bail, Context, Result}; use camino::{Utf8Path, Utf8PathBuf}; use fn_error_context::context; @@ -51,3 +51,80 @@ pub(crate) fn install_via_bootupd( .verbose() .run() } + +#[context("Installing bootloader using zipl")] +pub(crate) fn install_via_zipl(device: &PartitionTable, boot_uuid: &str) -> Result<()> { + // Identify the target boot partition from UUID + let fs = crate::mount::inspect_filesystem_by_uuid(boot_uuid)?; + let boot_dir = Utf8Path::new(&fs.target); + let maj_min = fs.maj_min; + + // Ensure that the found partition is a part of the target device + let device_path = device.path(); + + let partitions = crate::blockdev::list_dev(device_path)? + .children + .with_context(|| format!("no partition found on {device_path}"))?; + let boot_part = partitions + .iter() + .find(|part| part.maj_min.as_deref() == Some(maj_min.as_str())) + .with_context(|| format!("partition device {maj_min} is not on {device_path}"))?; + let boot_part_offset = boot_part.start.unwrap_or(0); + + // Find exactly one BLS configuration under /boot/loader/entries + // TODO: utilize the BLS parser in ostree + let bls_dir = boot_dir.join("boot/loader/entries"); + let bls_entry = bls_dir + .read_dir_utf8()? + .try_fold(None, |acc, e| -> Result<_> { + let e = e?; + let name = Utf8Path::new(e.file_name()); + if let Some("conf") = name.extension() { + if acc.is_some() { + bail!("more than one BLS configurations under {bls_dir}"); + } + Ok(Some(e.path().to_owned())) + } else { + Ok(None) + } + })? + .with_context(|| format!("no BLS configuration under {bls_dir}"))?; + + let bls_path = bls_dir.join(bls_entry); + let bls_conf = + std::fs::read_to_string(&bls_path).with_context(|| format!("reading {bls_path}"))?; + + let mut kernel = None; + let mut initrd = None; + let mut options = None; + + for line in bls_conf.lines() { + match line.split_once(char::is_whitespace) { + Some(("linux", val)) => kernel = Some(val.trim().trim_start_matches('/')), + Some(("initrd", val)) => initrd = Some(val.trim().trim_start_matches('/')), + Some(("options", val)) => options = Some(val.trim()), + _ => (), + } + } + + let kernel = kernel.ok_or_else(|| anyhow!("missing 'linux' key in default BLS config"))?; + let initrd = initrd.ok_or_else(|| anyhow!("missing 'initrd' key in default BLS config"))?; + let options = options.ok_or_else(|| anyhow!("missing 'options' key in default BLS config"))?; + + let image = boot_dir.join(kernel).canonicalize_utf8()?; + let ramdisk = boot_dir.join(initrd).canonicalize_utf8()?; + + // Execute the zipl command to install bootloader + let zipl_desc = format!("running zipl to install bootloader on {device_path}"); + let zipl_task = Task::new(&zipl_desc, "zipl") + .args(["--target", boot_dir.as_str()]) + .args(["--image", image.as_str()]) + .args(["--ramdisk", ramdisk.as_str()]) + .args(["--parameters", options]) + .args(["--targetbase", device_path.as_str()]) + .args(["--targettype", "SCSI"]) + .args(["--targetblocksize", "512"]) + .args(["--targetoffset", &boot_part_offset.to_string()]) + .args(["--add-files", "--verbose"]); + zipl_task.verbose().run().context(zipl_desc) +} diff --git a/lib/src/install.rs b/lib/src/install.rs index 0a553c42e..fcb8c3b90 100644 --- a/lib/src/install.rs +++ b/lib/src/install.rs @@ -573,15 +573,9 @@ async fn initialize_ostree_root_from_self( crate::lsm::ensure_dir_labeled(rootfs_dir, "boot", None, 0o755.into(), sepolicy)?; } - // Default to avoiding grub2-mkconfig etc., but we need to use zipl on s390x. - // TODO: Lower this logic into ostree proper. - let bootloader = if cfg!(target_arch = "s390x") { - "zipl" - } else { - "none" - }; for (k, v) in [ - ("sysroot.bootloader", bootloader), + // Default to avoiding grub2-mkconfig etc. + ("sysroot.bootloader", "none"), // Always flip this one on because we need to support alongside installs // to systems without a separate boot partition. ("sysroot.bootprefix", "true"), @@ -1239,12 +1233,17 @@ async fn install_to_filesystem_impl(state: &State, rootfs: &mut RootSetup) -> Re }) .context("Writing aleph version")?; } + if cfg!(target_arch = "s390x") { + // TODO: Integrate s390x support into install_via_bootupd + crate::bootloader::install_via_zipl(&rootfs.device_info, boot_uuid)?; + } else { + crate::bootloader::install_via_bootupd( + &rootfs.device_info, + &rootfs.rootfs, + &state.config_opts, + )?; + } - crate::bootloader::install_via_bootupd( - &rootfs.device_info, - &rootfs.rootfs, - &state.config_opts, - )?; tracing::debug!("Installed bootloader"); // Finalize mounted filesystems @@ -1688,7 +1687,9 @@ fn test_gather_root_args() { // A basic filesystem using a UUID let inspect = Filesystem { source: "/dev/vda4".into(), + target: "/".into(), fstype: "xfs".into(), + maj_min: "252:4".into(), options: "rw".into(), uuid: Some("965eb3c7-5a3f-470d-aaa2-1bcf04334bc6".into()), }; diff --git a/lib/src/mount.rs b/lib/src/mount.rs index 3c789dcb4..f91bf31bd 100644 --- a/lib/src/mount.rs +++ b/lib/src/mount.rs @@ -12,6 +12,9 @@ use crate::task::Task; pub(crate) struct Filesystem { // Note if you add an entry to this list, you need to change the --output invocation below too pub(crate) source: String, + pub(crate) target: String, + #[serde(rename = "maj:min")] + pub(crate) maj_min: String, pub(crate) fstype: String, pub(crate) options: String, pub(crate) uuid: Option, @@ -29,7 +32,7 @@ fn run_findmnt(args: &[&str], path: &str) -> Result { "-J", "-v", // If you change this you probably also want to change the Filesystem struct above - "--output=SOURCE,FSTYPE,OPTIONS,UUID", + "--output=SOURCE,TARGET,MAJ:MIN,FSTYPE,OPTIONS,UUID", ]) .args(args) .arg(path) @@ -49,6 +52,12 @@ pub(crate) fn inspect_filesystem(path: &Utf8Path) -> Result { run_findmnt(&["--mountpoint"], path.as_str()) } +#[context("Inspecting filesystem by UUID {uuid}")] +/// Inspect a filesystem by partition UUID +pub(crate) fn inspect_filesystem_by_uuid(uuid: &str) -> Result { + run_findmnt(&["--source"], &(format!("UUID={uuid}"))) +} + /// Mount a device to the target path. pub(crate) fn mount(dev: &str, target: &Utf8Path) -> Result<()> { Task::new_and_run(