Skip to content

Commit f17e2a2

Browse files
authored
Merge pull request #52 from cgwalters/install-fs-prep2
install: Add and use `MountSpec` abstraction for `/boot`
2 parents c033f3a + 35eb47b commit f17e2a2

File tree

2 files changed

+87
-11
lines changed

2 files changed

+87
-11
lines changed

lib/src/bootloader.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ fn install_grub2_efi(efidir: &Dir, uuid: &str) -> Result<()> {
5151
pub(crate) fn install_via_bootupd(
5252
device: &Utf8Path,
5353
rootfs: &Utf8Path,
54-
boot_uuid: &uuid::Uuid,
54+
boot_uuid: &str,
5555
) -> Result<()> {
5656
Task::new_and_run(
5757
"Running bootupctl to install bootloader",

lib/src/install.rs

Lines changed: 86 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ use std::io::BufWriter;
44
use std::io::Write;
55
use std::process::Command;
66
use std::process::Stdio;
7+
use std::str::FromStr;
78
use std::sync::Arc;
89

9-
use anyhow::{Context, Result};
10+
use anyhow::{anyhow, Context, Result};
1011
use camino::Utf8Path;
1112
use camino::Utf8PathBuf;
1213
use cap_std::fs::Dir;
@@ -199,6 +200,68 @@ struct InstallAleph {
199200
kernel: String,
200201
}
201202

203+
/// A mount specification is a subset of a line in `/etc/fstab`.
204+
///
205+
/// There are 3 (ASCII) whitespace separated values:
206+
///
207+
/// SOURCE TARGET [OPTIONS]
208+
///
209+
/// Examples:
210+
/// - /dev/vda3 /boot ext4 ro
211+
/// - /dev/nvme0n1p4 /
212+
/// - /dev/sda2 /var/mnt xfs
213+
#[derive(Debug, Clone)]
214+
pub(crate) struct MountSpec {
215+
pub(crate) source: String,
216+
pub(crate) target: String,
217+
pub(crate) fstype: String,
218+
pub(crate) options: Option<String>,
219+
}
220+
221+
impl MountSpec {
222+
const AUTO: &'static str = "auto";
223+
224+
pub(crate) fn new(src: &str, target: &str) -> Self {
225+
MountSpec {
226+
source: src.to_string(),
227+
target: target.to_string(),
228+
fstype: Self::AUTO.to_string(),
229+
options: None,
230+
}
231+
}
232+
233+
pub(crate) fn to_fstab(&self) -> String {
234+
let options = self.options.as_deref().unwrap_or("defaults");
235+
format!(
236+
"{} {} {} {} 0 0",
237+
self.source, self.target, self.fstype, options
238+
)
239+
}
240+
}
241+
242+
impl FromStr for MountSpec {
243+
type Err = anyhow::Error;
244+
245+
fn from_str(s: &str) -> Result<Self> {
246+
let mut parts = s.split_ascii_whitespace().fuse();
247+
let source = parts.next().unwrap_or_default();
248+
if source.is_empty() {
249+
anyhow::bail!("Invalid empty mount specification");
250+
}
251+
let target = parts
252+
.next()
253+
.ok_or_else(|| anyhow!("Missing target in mount specification {s}"))?;
254+
let fstype = parts.next().unwrap_or(Self::AUTO);
255+
let options = parts.next().map(ToOwned::to_owned);
256+
Ok(Self {
257+
source: source.to_string(),
258+
fstype: fstype.to_string(),
259+
target: target.to_string(),
260+
options,
261+
})
262+
}
263+
}
264+
202265
fn sgdisk_partition(
203266
sgdisk: &mut Command,
204267
n: u32,
@@ -411,9 +474,7 @@ async fn initialize_ostree_root_from_self(
411474
.context("Opening etc/fstab")
412475
.map(BufWriter::new)?
413476
};
414-
let boot_uuid = &root_setup.boot_uuid;
415-
let bootfs_type_str = root_setup.bootfs_type.to_string();
416-
writeln!(f, "UUID={boot_uuid} /boot {bootfs_type_str} defaults 1 2")?;
477+
writeln!(f, "{}", root_setup.boot.to_fstab())?;
417478
f.flush()?;
418479

419480
let uname = cap_std_ext::rustix::process::uname();
@@ -481,11 +542,24 @@ struct RootSetup {
481542
device: Utf8PathBuf,
482543
rootfs: Utf8PathBuf,
483544
rootfs_fd: Dir,
484-
bootfs_type: Filesystem,
485-
boot_uuid: uuid::Uuid,
545+
boot: MountSpec,
486546
kargs: Vec<String>,
487547
}
488548

549+
impl RootSetup {
550+
/// Get the UUID= mount specifier for the /boot filesystem. At the current time this is
551+
/// required.
552+
fn get_boot_uuid(&self) -> Result<&str> {
553+
let bootsrc = &self.boot.source;
554+
if let Some((t, rest)) = bootsrc.split_once('=') {
555+
if t.eq_ignore_ascii_case("uuid") {
556+
return Ok(rest);
557+
}
558+
}
559+
anyhow::bail!("/boot is not specified via UUID= (this is currently required): {bootsrc}")
560+
}
561+
}
562+
489563
#[context("Creating rootfs")]
490564
fn install_create_rootfs(state: &State, opts: InstallBlockDeviceOpts) -> Result<RootSetup> {
491565
// Verify that the target is empty (if not already wiped in particular, but it's
@@ -622,7 +696,9 @@ fn install_create_rootfs(state: &State, opts: InstallBlockDeviceOpts) -> Result<
622696
let rootdev = &format!("{device}{ROOTPN}");
623697
let root_uuid = mkfs(rootdev, opts.filesystem, Some("root"), [])?;
624698
let rootarg = format!("root=UUID={root_uuid}");
625-
let bootarg = format!("boot=UUID={boot_uuid}");
699+
let bootsrc = format!("UUID={boot_uuid}");
700+
let bootarg = format!("boot={bootsrc}");
701+
let boot = MountSpec::new(bootsrc.as_str(), "/boot");
626702
let kargs = vec![rootarg, RW_KARG.to_string(), bootarg];
627703

628704
mount(rootdev, &rootfs)?;
@@ -651,8 +727,7 @@ fn install_create_rootfs(state: &State, opts: InstallBlockDeviceOpts) -> Result<
651727
device,
652728
rootfs,
653729
rootfs_fd,
654-
bootfs_type,
655-
boot_uuid,
730+
boot,
656731
kargs,
657732
})
658733
}
@@ -829,7 +904,8 @@ pub(crate) async fn install(opts: InstallOpts) -> Result<()> {
829904
.context("Writing aleph version")?;
830905
}
831906

832-
crate::bootloader::install_via_bootupd(&rootfs.device, &rootfs.rootfs, &rootfs.boot_uuid)?;
907+
let boot_uuid = rootfs.get_boot_uuid()?;
908+
crate::bootloader::install_via_bootupd(&rootfs.device, &rootfs.rootfs, boot_uuid)?;
833909

834910
// If Ignition is specified, enable it
835911
if let Some(ignition_file) = state.config_opts.ignition_file.as_deref() {

0 commit comments

Comments
 (0)