Skip to content

Commit

Permalink
install: Use zipl to install bootloader on s390x
Browse files Browse the repository at this point in the history
Running zipl via ostree does not work correctly on a loop deivce.
This patch changes bootc to execute zipl directly with appropriate
command line options for installation on a loop device.

Signed-off-by: Yohei Ueda <[email protected]>
  • Loading branch information
yoheiueda committed Jul 12, 2024
1 parent c004da1 commit a7510c6
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 10 deletions.
93 changes: 92 additions & 1 deletion lib/src/bootloader.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use anyhow::Result;
use anyhow::{anyhow, Context, Result};
use camino::Utf8Path;
use fn_error_context::context;

Expand Down Expand Up @@ -26,3 +26,94 @@ pub(crate) fn install_via_bootupd(
.verbose()
.run()
}

#[context("Installing bootloader using zipl")]
pub(crate) fn install_via_zipl(device: &Utf8Path, 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
crate::blockdev::list_dev(device)?
.children
.with_context(|| format!("no partition found on {device:?}"))?
.iter()
.find(|dev| dev.maj_min == Some(maj_min.clone()))
.with_context(|| format!("partition device {:?} is not on {:?}", maj_min, device))?;

// Get an offset of the target partition by reading /sys/dev/block/<major:minor>/start
let sysfs_start_path = Utf8Path::new("/sys/dev/block").join(&maj_min).join("start");
let boot_offset = match std::fs::read_to_string(sysfs_start_path.clone()) {
Ok(v) => v.trim().parse::<i64>().map_err(|e| anyhow!(e)),
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(0), // partition-less disk
Err(e) => Err(anyhow!(e)),
}
.with_context(|| format!("reading {sysfs_start_path:?}"))?;

// Find the default BLS conf under /boot/loader/entries
// TODO: utilize the BLS parser in ostree
let bls_dir = boot_dir.join("boot/loader/entries");
let entries = bls_dir
.read_dir_utf8()?
.try_fold(Vec::new(), |mut acc, e| -> Result<_> {
let e = e?;
let name = Utf8Path::new(e.file_name());
if let Some("conf") = name.extension() {
acc.push(e.path().to_owned());
}
Ok(acc)
})?;
let bls_name = entries
.iter()
.max_by(|a, b| {
// TODO: Remove this unsafe code when a Rust version of strverscmp is available
#[link(name = "c")]
extern "C" {
fn strverscmp(s1: *const libc::c_char, s2: *const libc::c_char) -> libc::c_int;
}
let cstr_a = std::ffi::CString::new(a.as_str()).unwrap();
let cstr_b = std::ffi::CString::new(b.as_str()).unwrap();
#[allow(unsafe_code)]
let ret = unsafe { strverscmp(cstr_a.as_ptr(), cstr_b.as_ptr()) };
ret.cmp(&0)
})
.with_context(|| format!("no BLS conf under {bls_dir:?}"))?;
let bls_path = bls_dir.join(bls_name);
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}");
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.as_str()])
.args(["--targettype", "SCSI"])
.args(["--targetblocksize", "512"])
.args(["--targetoffset", &boot_offset.to_string()])
.args(["--add-files", "--verbose"]);
zipl_task.verbose().run().context(zipl_desc)
}
17 changes: 8 additions & 9 deletions lib/src/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down Expand Up @@ -1240,7 +1234,12 @@ async fn install_to_filesystem_impl(state: &State, rootfs: &mut RootSetup) -> Re
.context("Writing aleph version")?;
}

crate::bootloader::install_via_bootupd(&rootfs.device, &rootfs.rootfs, &state.config_opts)?;
if cfg!(target_arch = "s390x") {
// TODO: Integrate s390x support into install_via_bootupd
crate::bootloader::install_via_zipl(&rootfs.device, boot_uuid)?;
} else {
crate::bootloader::install_via_bootupd(&rootfs.device, &rootfs.rootfs, &state.config_opts)?;
}
tracing::debug!("Installed bootloader");

// Finalize mounted filesystems
Expand Down

0 comments on commit a7510c6

Please sign in to comment.