Skip to content

Commit 40f9692

Browse files
committed
efi: refactor mount state and use new mount API for ESP
Implementing code review changes
1 parent 4212eb8 commit 40f9692

2 files changed

Lines changed: 101 additions & 58 deletions

File tree

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ openssl = "^0.10"
4141
os-release = "0.1.0"
4242
regex = "1.12.2"
4343
rpm-rs = { package = "rpm", version = "0.16.1", default-features = false, optional = true }
44-
rustix = { version = "1.1.3", features = ["process", "fs"] }
44+
rustix = { version = "1.1.3", features = ["process", "fs", "mount"] }
4545
serde = { version = "^1.0", features = ["derive"] }
4646
serde_json = "^1.0"
4747
tempfile = "^3.24"

src/efi.rs

Lines changed: 100 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ use openat_ext::OpenatDirExt;
2121
use os_release::OsRelease;
2222
use rustix::{fd::AsFd, fd::BorrowedFd, fs::StatVfsMountFlags};
2323
use walkdir::WalkDir;
24+
#[cfg(target_os = "linux")]
25+
use rustix::mount::{
26+
fsconfig_create, fsconfig_set_path, fsmount, fsopen, move_mount, FsMountFlags, FsOpenFlags,
27+
MoveMountFlags, MountAttrFlags, UnmountFlags,
28+
};
2429
use widestring::U16CString;
2530

2631
use crate::bootupd::RootContext;
@@ -65,11 +70,36 @@ pub(crate) fn is_efi_booted() -> Result<bool> {
6570
.map_err(Into::into)
6671
}
6772

73+
#[cfg(target_os = "linux")]
74+
fn mount_esp(esp_device: &Path, target: &Path) -> Result<()> {
75+
use rustix::fs::CWD;
76+
77+
let fs_fd = fsopen("vfat", FsOpenFlags::empty()).context("fsopen vfat")?;
78+
fsconfig_set_path(fs_fd.as_fd(), "source", esp_device, CWD).context("fsconfig_set_path source")?;
79+
fsconfig_create(fs_fd.as_fd()).context("fsconfig_create")?;
80+
let mount_fd =
81+
fsmount(fs_fd.as_fd(), FsMountFlags::empty(), MountAttrFlags::empty()).context("fsmount")?;
82+
let target_dir = std::fs::File::open(target).context("open target dir for move_mount")?;
83+
let target_fd = unsafe { BorrowedFd::borrow_raw(target_dir.as_raw_fd()) };
84+
move_mount(
85+
mount_fd.as_fd(),
86+
"",
87+
target_fd,
88+
".",
89+
MoveMountFlags::MOVE_MOUNT_F_EMPTY_PATH,
90+
)
91+
.context("move_mount")?;
92+
Ok(())
93+
}
94+
95+
struct Mount {
96+
path: PathBuf,
97+
owned: bool,
98+
}
99+
68100
#[derive(Default)]
69101
pub(crate) struct Efi {
70-
mountpoint: RefCell<Option<PathBuf>>,
71-
/// Track whether we mounted the ESP ourselves (true) or found it pre-mounted (false)
72-
did_mount: RefCell<bool>,
102+
mountpoint: RefCell<Option<Mount>>,
73103
}
74104

75105
impl Efi {
@@ -90,19 +120,18 @@ impl Efi {
90120
break;
91121
}
92122
}
93-
94-
// Only borrow mutably if we found a mount point
95123
if let Some(mnt) = found_mount {
96124
log::debug!("Reusing existing mount point {mnt:?}");
97-
*self.mountpoint.borrow_mut() = Some(mnt.clone());
98-
*self.did_mount.borrow_mut() = false; // We didn't mount it
125+
*self.mountpoint.borrow_mut() = Some(Mount {
126+
path: mnt.clone(),
127+
owned: false,
128+
});
99129
Ok(Some(mnt))
100130
} else {
101131
Ok(None)
102132
}
103133
}
104134

105-
// Mount the passed esp_device, return mount point
106135
pub(crate) fn mount_esp_device(&self, root: &Path, esp_device: &Path) -> Result<PathBuf> {
107136
let mut mountpoint = None;
108137

@@ -111,8 +140,16 @@ impl Efi {
111140
if !mnt.exists() {
112141
continue;
113142
}
143+
#[cfg(target_os = "linux")]
144+
if mount_esp(esp_device, &mnt).is_ok() {
145+
log::debug!("Mounted at {mnt:?}");
146+
mountpoint = Some(mnt);
147+
break;
148+
}
149+
#[cfg(target_os = "linux")]
150+
log::trace!("Mount failed, falling back to mount(8)");
114151
std::process::Command::new("mount")
115-
.arg(&esp_device)
152+
.arg(esp_device)
116153
.arg(&mnt)
117154
.run_inherited()
118155
.with_context(|| format!("Failed to mount {:?}", esp_device))?;
@@ -121,17 +158,19 @@ impl Efi {
121158
break;
122159
}
123160
let mnt = mountpoint.ok_or_else(|| anyhow::anyhow!("No mount point found"))?;
124-
*self.mountpoint.borrow_mut() = Some(mnt.clone());
125-
*self.did_mount.borrow_mut() = true; // We mounted it ourselves
161+
*self.mountpoint.borrow_mut() = Some(Mount {
162+
path: mnt.clone(),
163+
owned: true,
164+
});
126165
Ok(mnt)
127166
}
128167

129168
// Firstly check if esp is already mounted, then mount the passed esp device
130169
pub(crate) fn ensure_mounted_esp(&self, root: &Path, esp_device: &Path) -> Result<PathBuf> {
131-
if let Some(mountpoint) = self.mountpoint.borrow().as_deref() {
132-
return Ok(mountpoint.to_owned());
170+
if let Some(mount) = self.mountpoint.borrow().as_ref() {
171+
return Ok(mount.path.clone());
133172
}
134-
let destdir = if let Some(destdir) = self.get_mounted_esp(Path::new(root))? {
173+
let destdir = if let Some(destdir) = self.get_mounted_esp(root)? {
135174
destdir
136175
} else {
137176
self.mount_esp_device(root, esp_device)?
@@ -141,14 +180,32 @@ impl Efi {
141180

142181
fn unmount(&self) -> Result<()> {
143182
// Only unmount if we mounted it ourselves
144-
if *self.did_mount.borrow() {
183+
let should_unmount = self
184+
.mountpoint
185+
.borrow()
186+
.as_ref()
187+
.map(|m| m.owned)
188+
.unwrap_or(false);
189+
if should_unmount {
145190
if let Some(mount) = self.mountpoint.borrow_mut().take() {
146-
Command::new("umount")
147-
.arg(&mount)
148-
.run_inherited()
149-
.with_context(|| format!("Failed to unmount {mount:?}"))?;
150-
log::trace!("Unmounted");
151-
*self.did_mount.borrow_mut() = false;
191+
#[cfg(target_os = "linux")]
192+
if rustix::mount::unmount(&mount.path, UnmountFlags::empty()).is_ok() {
193+
log::trace!("Unmounted (new mount API)");
194+
} else {
195+
Command::new("umount")
196+
.arg(&mount.path)
197+
.run_inherited()
198+
.with_context(|| format!("Failed to unmount {:?}", mount.path))?;
199+
log::trace!("Unmounted");
200+
}
201+
#[cfg(not(target_os = "linux"))]
202+
{
203+
Command::new("umount")
204+
.arg(&mount.path)
205+
.run_inherited()
206+
.with_context(|| format!("Failed to unmount {:?}", mount.path))?;
207+
log::trace!("Unmounted");
208+
}
152209
}
153210
}
154211
Ok(())
@@ -191,19 +248,17 @@ impl Efi {
191248
clear_efi_target(&product_name)?;
192249
create_efi_boot_entry(device, esp_part_num.trim(), &loader, &product_name)
193250
}
194-
195-
/// Shared helper to copy EFI components to a single ESP
196251
fn copy_efi_components_to_esp(
197252
&self,
198253
sysroot_dir: &openat::Dir,
254+
esp_dir: &openat::Dir,
199255
esp_path: &Path,
200256
efi_components: &[EFIComponent],
201257
) -> Result<()> {
202258
let dest_str = esp_path
203259
.to_str()
204-
.context("ESP path contains invalid UTF-8")?;
260+
.with_context(|| format!("Invalid UTF-8: {}", esp_path.display()))?;
205261

206-
// Copy each component
207262
for efi_comp in efi_components {
208263
log::info!(
209264
"Copying EFI component {} version {} to ESP at {}",
@@ -221,51 +276,41 @@ impl Efi {
221276
})?;
222277
}
223278

224-
// Sync filesystem
225-
let efidir =
226-
openat::Dir::open(&esp_path.join("EFI")).context("Opening EFI directory for sync")?;
227-
fsfreeze_thaw_cycle(efidir.open_file(".")?)?;
279+
// Sync the whole ESP filesystem
280+
fsfreeze_thaw_cycle(esp_dir.open_file(".")?)?;
228281

229282
Ok(())
230283
}
231284

232-
/// Copy from /usr/lib/efi to boot/ESP.
233-
fn package_mode_copy_to_boot_impl(&self) -> Result<()> {
234-
let sysroot = Path::new("/");
235-
let sysroot_path =
236-
Utf8Path::from_path(sysroot).context("Sysroot path is not valid UTF-8")?;
285+
/// Copy from /usr/lib/efi to boot/ESP. Caller provides sysroot (e.g. for recovery or tests).
286+
fn package_mode_copy_to_boot_impl(&self, sysroot: &Path) -> Result<()> {
287+
let sysroot_path = Utf8Path::from_path(sysroot)
288+
.with_context(|| format!("Invalid UTF-8: {}", sysroot.display()))?;
237289

238290
let efi_comps = match get_efi_component_from_usr(sysroot_path, EFILIB)? {
239291
Some(comps) if !comps.is_empty() => comps,
240-
_ => {
241-
log::debug!("No EFI components found in /usr/lib/efi");
242-
return Ok(());
243-
}
292+
_ => anyhow::bail!("No EFI components found in /usr/lib/efi"),
244293
};
245294

246-
let sysroot_dir = openat::Dir::open(sysroot).context("Opening sysroot for reading")?;
247-
248295
// First try to use an already mounted ESP
249296
let esp_path = if let Some(mounted_esp) = self.get_mounted_esp(sysroot)? {
250297
mounted_esp
251298
} else {
252-
// If not mounted, find ESP from devices
253299
let devices = blockdev::get_devices(sysroot)?;
254300
let Some(esp_devices) = blockdev::find_colocated_esps(&devices)? else {
255301
anyhow::bail!("No ESP found");
256302
};
257-
258-
let esp_device = esp_devices
259-
.first()
260-
.ok_or_else(|| anyhow::anyhow!("No ESP device found"))?;
303+
// find_colocated_esps returns Some only with a non-empty vec
304+
let esp_device = esp_devices.first().unwrap();
261305
self.ensure_mounted_esp(sysroot, Path::new(esp_device))?
262306
};
263307

264308
let esp_dir = openat::Dir::open(&esp_path)
265309
.with_context(|| format!("Opening ESP at {}", esp_path.display()))?;
266310
validate_esp_fstype(&esp_dir)?;
267311

268-
self.copy_efi_components_to_esp(&sysroot_dir, &esp_path, &efi_comps)?;
312+
let sysroot_dir = openat::Dir::open(sysroot).context("Opening sysroot for reading")?;
313+
self.copy_efi_components_to_esp(&sysroot_dir, &esp_dir, &esp_path, &efi_comps)?;
269314

270315
log::info!(
271316
"Successfully copied {} EFI component(s) to ESP at {}",
@@ -508,19 +553,16 @@ impl Component for Efi {
508553

509554
let efi_path = if let Some(efi_components) = efi_comps {
510555
// Use shared helper to copy components from /usr/lib/efi
511-
self.copy_efi_components_to_esp(&src_dir, &destpath, &efi_components)?;
556+
self.copy_efi_components_to_esp(&src_dir, destd, &destpath, &efi_components)?;
512557
EFILIB
513558
} else {
514559
let updates = component_updatedirname(self);
515560
let src = updates
516561
.to_str()
517-
.context("Include invalid UTF-8 characters in path")?;
518-
let dest = destpath.to_str().with_context(|| {
519-
format!(
520-
"Include invalid UTF-8 characters in dest {}",
521-
destpath.display()
522-
)
523-
})?;
562+
.with_context(|| format!("Invalid UTF-8: {}", updates.display()))?;
563+
let dest = destpath
564+
.to_str()
565+
.with_context(|| format!("Invalid UTF-8: {}", destpath.display()))?;
524566
filetree::copy_dir_with_args(&src_dir, src, dest, OPTIONS)?;
525567
&src.to_owned()
526568
};
@@ -715,8 +757,8 @@ impl Component for Efi {
715757
}
716758

717759
/// Package mode copy: Simple copy from /usr/lib/efi to boot/ESP.
718-
fn package_mode_copy_to_boot(&self) -> Result<()> {
719-
self.package_mode_copy_to_boot_impl()
760+
fn package_mode_copy_to_boot(&self, root: &Path) -> Result<()> {
761+
self.package_mode_copy_to_boot_impl(root)
720762
}
721763
}
722764

@@ -1177,8 +1219,9 @@ Boot0003* test";
11771219
assert_eq!(efi_comps[0].version, "15.8-3");
11781220

11791221
// Create Efi instance and copy components to ESP
1222+
let esp_dir = openat::Dir::open(&esp_path).context("Opening ESP dir for test")?;
11801223
let efi = Efi::default();
1181-
efi.copy_efi_components_to_esp(&sysroot_dir, &esp_path, &efi_comps)?;
1224+
efi.copy_efi_components_to_esp(&sysroot_dir, &esp_dir, &esp_path, &efi_comps)?;
11821225

11831226
// Expected path: /boot/efi/EFI/fedora/shimx64.efi (or shimaa64.efi, etc.)
11841227
let copied_shim_path = esp_path.join("EFI/fedora").join(SHIM);

0 commit comments

Comments
 (0)