Skip to content

Commit b4dbd1f

Browse files
authored
Merge pull request #948 from champtar/fsfreeze_thaw_cycle
Add fsfreeze_thaw_cycle() + small cleanups
2 parents b525379 + 2f64ffe commit b4dbd1f

File tree

7 files changed

+98
-61
lines changed

7 files changed

+98
-61
lines changed

src/backend/statefile.rs

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! On-disk saved state.
22
33
use crate::model::SavedState;
4+
use crate::util::SignalTerminationGuard;
45
use anyhow::{bail, Context, Result};
56
use fn_error_context::context;
67
use fs2::FileExt;
@@ -9,25 +10,6 @@ use std::fs::File;
910
use std::io::prelude::*;
1011
use std::path::Path;
1112

12-
/// Suppress SIGTERM while active
13-
// TODO: In theory we could record if we got SIGTERM and exit
14-
// on drop, but in practice we don't care since we're going to exit anyways.
15-
#[derive(Debug)]
16-
struct SignalTerminationGuard(signal_hook_registry::SigId);
17-
18-
impl SignalTerminationGuard {
19-
pub(crate) fn new() -> Result<Self> {
20-
let signal = unsafe { signal_hook_registry::register(libc::SIGTERM, || {})? };
21-
Ok(Self(signal))
22-
}
23-
}
24-
25-
impl Drop for SignalTerminationGuard {
26-
fn drop(&mut self) {
27-
signal_hook_registry::unregister(self.0);
28-
}
29-
}
30-
3113
impl SavedState {
3214
/// System-wide bootupd write lock (relative to sysroot).
3315
const WRITE_LOCK_PATH: &'static str = "run/bootupd-lock";

src/bootupd.rs

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use crate::coreos;
99
target_arch = "riscv64"
1010
))]
1111
use crate::efi;
12+
use crate::freezethaw::fsfreeze_thaw_cycle;
1213
use crate::model::{ComponentStatus, ComponentUpdatable, ContentMetadata, SavedState, Status};
1314
use crate::{ostreeutil, util};
1415
use anyhow::{anyhow, Context, Result};
@@ -17,7 +18,6 @@ use clap::crate_version;
1718
use fn_error_context::context;
1819
use libc::mode_t;
1920
use libc::{S_IRGRP, S_IROTH, S_IRUSR, S_IWUSR};
20-
use openat_ext::OpenatDirExt;
2121
use serde::{Deserialize, Serialize};
2222
use std::borrow::Cow;
2323
use std::collections::BTreeMap;
@@ -625,21 +625,14 @@ pub(crate) fn client_run_migrate_static_grub_config() -> Result<()> {
625625

626626
strip_grub_config_file(content, &dirfd, stripped_config)?;
627627

628-
// Sync changes to the filesystem (ignore failures)
629-
let _ = dirfd.syncfs();
630-
631-
// Atomically exchange the configs
628+
// Atomically replace the symlink
632629
dirfd
633-
.local_exchange(stripped_config, "grub.cfg")
634-
.context("Failed to exchange symlink with current GRUB config")?;
630+
.local_rename(stripped_config, "grub.cfg")
631+
.context("Failed to replace symlink with current GRUB config")?;
635632

636-
// Sync changes to the filesystem (ignore failures)
637-
let _ = dirfd.syncfs();
633+
fsfreeze_thaw_cycle(dirfd.open_file(".")?)?;
638634

639635
println!("GRUB config symlink successfully replaced with the current config");
640-
641-
// Remove the now unused symlink (optional cleanup, ignore any failures)
642-
_ = dirfd.remove_file(stripped_config);
643636
}
644637
};
645638

@@ -690,8 +683,10 @@ fn strip_grub_config_file(
690683
}
691684

692685
writer
693-
.flush()
694-
.context("Failed to write stripped GRUB config")?;
686+
.into_inner()
687+
.context("Failed to flush stripped GRUB config")?
688+
.sync_data()
689+
.context("Failed to sync stripped GRUB config")?;
695690

696691
Ok(())
697692
}

src/filetree.rs

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
* SPDX-License-Identifier: Apache-2.0
55
*/
66

7+
use crate::freezethaw::fsfreeze_thaw_cycle;
78
#[cfg(any(
89
target_arch = "x86_64",
910
target_arch = "aarch64",
@@ -330,23 +331,6 @@ pub(crate) struct ApplyUpdateOptions {
330331
pub(crate) skip_sync: bool,
331332
}
332333

333-
// syncfs() is a Linux-specific system call, which doesn't seem
334-
// to be bound in nix today. I found https://github.com/XuShaohua/nc
335-
// but that's a nontrivial dependency with not a lot of code review.
336-
// Let's just fork off a helper process for now.
337-
#[cfg(any(
338-
target_arch = "x86_64",
339-
target_arch = "aarch64",
340-
target_arch = "riscv64"
341-
))]
342-
pub(crate) fn syncfs(d: &openat::Dir) -> Result<()> {
343-
use rustix::fs::{Mode, OFlags};
344-
let d = unsafe { BorrowedFd::borrow_raw(d.as_raw_fd()) };
345-
let oflags = OFlags::RDONLY | OFlags::CLOEXEC | OFlags::DIRECTORY;
346-
let d = rustix::fs::openat(d, ".", oflags, Mode::empty())?;
347-
rustix::fs::syncfs(d).map_err(Into::into)
348-
}
349-
350334
/// Copy from src to dst at root dir
351335
#[cfg(any(
352336
target_arch = "x86_64",
@@ -475,7 +459,7 @@ pub(crate) fn apply_diff(
475459
}
476460
// Ensure all of the updates & changes are written persistently to disk
477461
if !opts.skip_sync {
478-
syncfs(destdir)?;
462+
destdir.syncfs()?;
479463
}
480464

481465
// finally remove the temp dir
@@ -486,7 +470,7 @@ pub(crate) fn apply_diff(
486470
// A second full filesystem sync to narrow any races rather than
487471
// waiting for writeback to kick in.
488472
if !opts.skip_sync {
489-
syncfs(destdir)?;
473+
fsfreeze_thaw_cycle(destdir.open_file(".")?)?;
490474
}
491475
Ok(())
492476
}

src/freezethaw.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
use rustix::fd::AsFd;
2+
use rustix::ffi as c;
3+
use rustix::io::Errno;
4+
use rustix::ioctl::opcode;
5+
use rustix::{io, ioctl};
6+
7+
use crate::util::SignalTerminationGuard;
8+
9+
fn ioctl_fifreeze<Fd: AsFd>(fd: Fd) -> io::Result<()> {
10+
// SAFETY: `FIFREEZE` is a no-argument opcode.
11+
// `FIFREEZE` is defined as `_IOWR('X', 119, int)`.
12+
unsafe {
13+
let ctl = ioctl::NoArg::<{ opcode::read_write::<c::c_int>(b'X', 119) }>::new();
14+
ioctl::ioctl(fd, ctl)
15+
}
16+
}
17+
18+
fn ioctl_fithaw<Fd: AsFd>(fd: Fd) -> io::Result<()> {
19+
// SAFETY: `FITHAW` is a no-argument opcode.
20+
// `FITHAW` is defined as `_IOWR('X', 120, int)`.
21+
unsafe {
22+
let ctl = ioctl::NoArg::<{ opcode::read_write::<c::c_int>(b'X', 120) }>::new();
23+
ioctl::ioctl(fd, ctl)
24+
}
25+
}
26+
27+
/// syncfs() doesn't flush the journal on XFS,
28+
/// and since GRUB2 can't read the XFS journal, the system
29+
/// could fail to boot.
30+
///
31+
/// http://marc.info/?l=linux-fsdevel&m=149520244919284&w=2
32+
/// https://github.com/ostreedev/ostree/pull/1049
33+
///
34+
/// This function always call syncfs() first, then calls
35+
/// `ioctl(FIFREEZE)` and `ioctl(FITHAW)`, ignoring `EOPNOTSUPP` and `EPERM`
36+
pub(crate) fn fsfreeze_thaw_cycle<Fd: AsFd>(fd: Fd) -> anyhow::Result<()> {
37+
rustix::fs::syncfs(&fd)?;
38+
39+
let _guard = SignalTerminationGuard::new()?;
40+
41+
let freeze = ioctl_fifreeze(&fd);
42+
match freeze {
43+
// Ignore permissions errors (tests)
44+
Err(Errno::PERM) => Ok(()),
45+
// Ignore unsupported FS
46+
Err(Errno::NOTSUP) => Ok(()),
47+
Ok(()) => Ok(ioctl_fithaw(fd)?),
48+
_ => Ok(freeze?),
49+
}
50+
}

src/grubconfigs.rs

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ use bootc_utils::CommandRunExt;
77
use fn_error_context::context;
88
use openat_ext::OpenatDirExt;
99

10+
use crate::freezethaw::fsfreeze_thaw_cycle;
11+
1012
/// The subdirectory of /boot we use
1113
const GRUB2DIR: &str = "grub2";
1214
const CONFIGDIR: &str = "/usr/lib/bootupd/grub2-static";
@@ -60,8 +62,9 @@ pub(crate) fn install(
6062
println!("Added {name}");
6163
}
6264

63-
bootdir
64-
.write_file_contents(format!("{GRUB2DIR}/grub.cfg"), 0o644, config.as_bytes())
65+
let grub2dir = bootdir.sub_dir(GRUB2DIR)?;
66+
grub2dir
67+
.write_file_contents("grub.cfg", 0o644, config.as_bytes())
6568
.context("Copying grub-static.cfg")?;
6669
println!("Installed: grub.cfg");
6770

@@ -74,15 +77,18 @@ pub(crate) fn install(
7477
.uuid
7578
.ok_or_else(|| anyhow::anyhow!("Failed to find UUID for boot"))?;
7679
let grub2_uuid_contents = format!("set BOOT_UUID=\"{bootfs_uuid}\"\n");
77-
let uuid_path = format!("{GRUB2DIR}/bootuuid.cfg");
78-
bootdir
79-
.write_file_contents(&uuid_path, 0o644, grub2_uuid_contents)
80+
let uuid_path = "bootuuid.cfg";
81+
grub2dir
82+
.write_file_contents(uuid_path, 0o644, grub2_uuid_contents)
8083
.context("Writing bootuuid.cfg")?;
84+
println!("Installed: bootuuid.cfg");
8185
Some(uuid_path)
8286
} else {
8387
None
8488
};
8589

90+
fsfreeze_thaw_cycle(grub2dir.open_file(".")?)?;
91+
8692
if let Some(vendordir) = installed_efi_vendor {
8793
log::debug!("vendordir={:?}", &vendordir);
8894
let vendor = PathBuf::from(vendordir);
@@ -96,13 +102,13 @@ pub(crate) fn install(
96102
.context("Copying static EFI")?;
97103
println!("Installed: {target:?}");
98104
if let Some(uuid_path) = uuid_path {
99-
// SAFETY: we always have a filename
100-
let filename = Path::new(&uuid_path).file_name().unwrap();
101-
let target = &vendor.join(filename);
102-
bootdir
105+
let target = &vendor.join(uuid_path);
106+
grub2dir
103107
.copy_file_at(uuid_path, &efidir, target)
104108
.context("Writing bootuuid.cfg to efi dir")?;
109+
println!("Installed: {target:?}");
105110
}
111+
fsfreeze_thaw_cycle(efidir.open_file(".")?)?;
106112
}
107113
}
108114

src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ mod efi;
3333
mod failpoints;
3434
mod filesystem;
3535
mod filetree;
36+
mod freezethaw;
3637
#[cfg(any(
3738
target_arch = "x86_64",
3839
target_arch = "aarch64",

src/util.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,3 +115,22 @@ pub fn running_in_container() -> bool {
115115
}
116116
false
117117
}
118+
119+
/// Suppress SIGTERM while active
120+
// TODO: In theory we could record if we got SIGTERM and exit
121+
// on drop, but in practice we don't care since we're going to exit anyways.
122+
#[derive(Debug)]
123+
pub(crate) struct SignalTerminationGuard(signal_hook_registry::SigId);
124+
125+
impl SignalTerminationGuard {
126+
pub(crate) fn new() -> Result<Self> {
127+
let signal = unsafe { signal_hook_registry::register(libc::SIGTERM, || {})? };
128+
Ok(Self(signal))
129+
}
130+
}
131+
132+
impl Drop for SignalTerminationGuard {
133+
fn drop(&mut self) {
134+
signal_hook_registry::unregister(self.0);
135+
}
136+
}

0 commit comments

Comments
 (0)