Skip to content

Commit b0e43a8

Browse files
install/composefs: Use atomic writes
Use `atomic_write` from `cap_std` crate Get rid of `opeanat` crate Call `fsync` after write to disk Signed-off-by: Johan-Liebert1 <[email protected]>
1 parent 734f43d commit b0e43a8

File tree

3 files changed

+118
-71
lines changed

3 files changed

+118
-71
lines changed

crates/lib/Cargo.toml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,6 @@ tini = "1.3.0"
5656
comfy-table = "7.1.1"
5757
thiserror = { workspace = true }
5858
canon-json = { workspace = true }
59-
openat = "0.1.21"
60-
openat-ext = "0.2.3"
6159

6260
[dev-dependencies]
6361
similar-asserts = { workspace = true }

crates/lib/src/deploy.rs

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,17 @@ use ostree_ext::ostree::Deployment;
2222
use ostree_ext::ostree::{self, Sysroot};
2323
use ostree_ext::sysroot::SysrootLock;
2424
use ostree_ext::tokio_util::spawn_blocking_cancellable_flatten;
25+
use rustix::fs::{fsync, renameat_with, AtFlags, RenameFlags};
2526

2627
use crate::bls_config::{parse_bls_config, BLSConfig};
2728
use crate::install::{get_efi_uuid_source, get_user_config, BootType};
2829
use crate::progress_jsonl::{Event, ProgressWriter, SubTaskBytes, SubTaskStep};
2930
use crate::spec::ImageReference;
30-
use crate::spec::{BootOrder, HostSpec, BootEntry};
31+
use crate::spec::{BootEntry, BootOrder, HostSpec};
3132
use crate::status::{composefs_deployment_status, labels_of_config};
3233
use crate::store::Storage;
3334
use crate::utils::async_task_with_spinner;
3435

35-
use openat_ext::OpenatDirExt;
36-
3736
// TODO use https://github.com/ostreedev/ostree-rs-ext/pull/493/commits/afc1837ff383681b947de30c0cefc70080a4f87a
3837
const BASE_IMAGE_PREFIX: &str = "ostree/container/baseimage/bootc";
3938

@@ -743,7 +742,6 @@ pub(crate) async fn stage(
743742
Ok(())
744743
}
745744

746-
747745
#[context("Rolling back UKI")]
748746
pub(crate) fn rollback_composefs_uki(current: &BootEntry, rollback: &BootEntry) -> Result<()> {
749747
let user_cfg_name = "grub2/user.cfg.staged";
@@ -782,7 +780,8 @@ pub(crate) fn rollback_composefs_uki(current: &BootEntry, rollback: &BootEntry)
782780

783781
/// Filename for `loader/entries`
784782
const CURRENT_ENTRIES: &str = "entries";
785-
const ROLLBACK_ENTRIES: &str = "entries.staged";
783+
const STAGED_ENTRIES: &str = "entries.staged";
784+
const ROLLBACK_ENTRIES: &str = STAGED_ENTRIES;
786785

787786
#[context("Getting boot entries")]
788787
pub(crate) fn get_sorted_boot_entries(ascending: bool) -> Result<Vec<BLSConfig>> {
@@ -833,33 +832,50 @@ pub(crate) fn rollback_composefs_bls() -> Result<()> {
833832
let dir_path = PathBuf::from(format!("/sysroot/boot/loader/{ROLLBACK_ENTRIES}"));
834833
create_dir_all(&dir_path).with_context(|| format!("Failed to create dir: {dir_path:?}"))?;
835834

835+
let rollback_entries_dir =
836+
cap_std::fs::Dir::open_ambient_dir(&dir_path, cap_std::ambient_authority())
837+
.with_context(|| format!("Opening {dir_path:?}"))?;
838+
836839
// Write the BLS configs in there
837840
for cfg in all_configs {
838841
let file_name = format!("bootc-composefs-{}.conf", cfg.version);
839842

840-
let mut file = std::fs::OpenOptions::new()
841-
.create(true)
842-
.write(true)
843-
.open(dir_path.join(&file_name))
844-
.with_context(|| format!("Opening {file_name}"))?;
845-
846-
file.write_all(cfg.to_string().as_bytes())
843+
rollback_entries_dir
844+
.atomic_write(&file_name, cfg.to_string().as_bytes())
847845
.with_context(|| format!("Writing to {file_name}"))?;
848846
}
849847

848+
// Should we sync after every write?
849+
fsync(
850+
rollback_entries_dir
851+
.reopen_as_ownedfd()
852+
.with_context(|| format!("Reopening {dir_path:?} as owned fd"))?,
853+
)
854+
.with_context(|| format!("fsync {dir_path:?}"))?;
855+
850856
// Atomically exchange "entries" <-> "entries.rollback"
851-
let dir = openat::Dir::open("/sysroot/boot/loader").context("Opening loader dir")?;
857+
let dir = Dir::open_ambient_dir("/sysroot/boot/loader", cap_std::ambient_authority())
858+
.context("Opening loader dir")?;
852859

853860
tracing::debug!("Atomically exchanging for {ROLLBACK_ENTRIES} and {CURRENT_ENTRIES}");
854-
dir.local_exchange(ROLLBACK_ENTRIES, CURRENT_ENTRIES)
855-
.context("local exchange")?;
861+
renameat_with(
862+
&dir,
863+
ROLLBACK_ENTRIES,
864+
&dir,
865+
CURRENT_ENTRIES,
866+
RenameFlags::EXCHANGE,
867+
)
868+
.context("renameat")?;
856869

857870
tracing::debug!("Removing {ROLLBACK_ENTRIES}");
858-
dir.remove_all(ROLLBACK_ENTRIES)
859-
.context("Removing entries.rollback")?;
871+
rustix::fs::unlinkat(&dir, ROLLBACK_ENTRIES, AtFlags::REMOVEDIR).context("unlinkat")?;
860872

861873
tracing::debug!("Syncing to disk");
862-
dir.syncfs().context("syncfs")?;
874+
fsync(
875+
dir.reopen_as_ownedfd()
876+
.with_context(|| format!("Reopening /sysroot/boot/loader as owned fd"))?,
877+
)
878+
.context("fsync")?;
863879

864880
Ok(())
865881
}

crates/lib/src/install.rs

Lines changed: 84 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ mod osbuild;
1414
pub(crate) mod osconfig;
1515

1616
use std::collections::HashMap;
17-
use std::fmt::write;
1817
use std::fs::create_dir_all;
1918
use std::io::{Read, Write};
2019
use std::os::fd::{AsFd, AsRawFd};
@@ -1640,25 +1639,36 @@ pub(crate) fn setup_composefs_bls_boot(
16401639
let path = boot_dir.join(&id_hex);
16411640
create_dir_all(&path)?;
16421641

1643-
let vmlinuz_path = path.join("vmlinuz");
1644-
let initrd_path = path.join("initrd");
1642+
let entries_dir =
1643+
cap_std::fs::Dir::open_ambient_dir(&path, cap_std::ambient_authority())
1644+
.with_context(|| format!("Opening {path}"))?;
16451645

1646-
std::fs::write(
1647-
&vmlinuz_path,
1648-
read_file(&usr_lib_modules_vmlinuz.vmlinuz, &repo).context("Reading vmlinuz")?,
1649-
)
1650-
.context("Writing vmlinuz to path")?;
1646+
entries_dir
1647+
.atomic_write(
1648+
"vmlinuz",
1649+
read_file(&usr_lib_modules_vmlinuz.vmlinuz, &repo)
1650+
.context("Reading vmlinuz")?,
1651+
)
1652+
.context("Writing vmlinuz to path")?;
16511653

16521654
if let Some(initramfs) = &usr_lib_modules_vmlinuz.initramfs {
1653-
std::fs::write(
1654-
&initrd_path,
1655-
read_file(initramfs, &repo).context("Reading initramfs")?,
1656-
)
1657-
.context("Writing initrd to path")?;
1655+
entries_dir
1656+
.atomic_write(
1657+
"initrd",
1658+
read_file(initramfs, &repo).context("Reading initrd")?,
1659+
)
1660+
.context("Writing initrd to path")?;
16581661
} else {
16591662
anyhow::bail!("initramfs not found");
16601663
};
16611664

1665+
// Can't call fsync on O_PATH fds, so re-open it as a non O_PATH fd
1666+
let owned_fd = entries_dir
1667+
.reopen_as_ownedfd()
1668+
.context("Reopen as owned fd")?;
1669+
1670+
rustix::fs::fsync(owned_fd).context("fsync")?;
1671+
16621672
BLSConfig {
16631673
title: Some(id_hex.clone()),
16641674
version: 1,
@@ -1682,18 +1692,27 @@ pub(crate) fn setup_composefs_bls_boot(
16821692

16831693
create_dir_all(&entries_path).with_context(|| format!("Creating {:?}", entries_path))?;
16841694

1685-
std::fs::write(
1686-
entries_path.join(format!("bootc-composefs-{}.conf", bls_config.version)),
1695+
let loader_entries_dir =
1696+
cap_std::fs::Dir::open_ambient_dir(&entries_path, cap_std::ambient_authority())
1697+
.with_context(|| format!("Opening {entries_path}"))?;
1698+
1699+
loader_entries_dir.atomic_write(
1700+
format!("bootc-composefs-{}.conf", bls_config.version),
16871701
bls_config.to_string().as_bytes(),
16881702
)?;
16891703

16901704
if let Some(booted_bls) = booted_bls {
1691-
std::fs::write(
1692-
entries_path.join(format!("bootc-composefs-{}.conf", booted_bls.version)),
1705+
loader_entries_dir.atomic_write(
1706+
format!("bootc-composefs-{}.conf", booted_bls.version),
16931707
booted_bls.to_string().as_bytes(),
16941708
)?;
16951709
}
16961710

1711+
let owned_loader_entries_fd = loader_entries_dir
1712+
.reopen_as_ownedfd()
1713+
.context("Reopening as owned fd")?;
1714+
rustix::fs::fsync(owned_loader_entries_fd).context("fsync")?;
1715+
16971716
Ok(())
16981717
}
16991718

@@ -1801,11 +1820,23 @@ pub(crate) fn setup_composefs_uki_boot(
18011820
}
18021821

18031822
// Write the UKI to ESP
1804-
let efi_linux = mounted_esp.join("EFI/Linux");
1805-
create_dir_all(&efi_linux).context("Creating EFI/Linux")?;
1823+
let efi_linux_path = mounted_esp.join("EFI/Linux");
1824+
create_dir_all(&efi_linux_path).context("Creating EFI/Linux")?;
1825+
1826+
let efi_linux =
1827+
cap_std::fs::Dir::open_ambient_dir(&efi_linux_path, cap_std::ambient_authority())
1828+
.with_context(|| format!("Opening {efi_linux_path:?}"))?;
18061829

1807-
let final_uki_path = efi_linux.join(format!("{}.efi", id.to_hex()));
1808-
std::fs::write(final_uki_path, uki).context("Writing UKI to final path")?;
1830+
efi_linux
1831+
.atomic_write(format!("{}.efi", id.to_hex()), uki)
1832+
.context("Writing UKI")?;
1833+
1834+
rustix::fs::fsync(
1835+
efi_linux
1836+
.reopen_as_ownedfd()
1837+
.context("Reopening as owned fd")?,
1838+
)
1839+
.context("fsync")?;
18091840

18101841
boot_label
18111842
}
@@ -1830,24 +1861,24 @@ pub(crate) fn setup_composefs_uki_boot(
18301861
let efi_uuid_source = get_efi_uuid_source();
18311862

18321863
let user_cfg_name = if is_upgrade {
1833-
"grub2/user.cfg.staged"
1864+
"user.cfg.staged"
18341865
} else {
1835-
"grub2/user.cfg"
1866+
"user.cfg"
18361867
};
1837-
let user_cfg_path = boot_dir.join(user_cfg_name);
1868+
1869+
let grub_dir =
1870+
cap_std::fs::Dir::open_ambient_dir(boot_dir.join("grub2"), cap_std::ambient_authority())
1871+
.context("opening boot/grub2")?;
18381872

18391873
// Iterate over all available deployments, and generate a menuentry for each
18401874
//
18411875
// TODO: We might find a staged deployment here
18421876
if is_upgrade {
1843-
let mut usr_cfg = std::fs::OpenOptions::new()
1844-
.write(true)
1845-
.create(true)
1846-
.open(user_cfg_path)
1847-
.with_context(|| format!("Opening {user_cfg_name}"))?;
1877+
let mut buffer = vec![];
18481878

1849-
usr_cfg.write_all(efi_uuid_source.as_bytes())?;
1850-
usr_cfg.write_all(get_user_config(&boot_label, &id.to_hex()).as_bytes())?;
1879+
// Shouldn't really fail so no context here
1880+
buffer.write_all(efi_uuid_source.as_bytes())?;
1881+
buffer.write_all(get_user_config(&boot_label, &id.to_hex()).as_bytes())?;
18511882

18521883
// root_path here will be /sysroot
18531884
for entry in std::fs::read_dir(root_path.join(STATE_DIR_RELATIVE))? {
@@ -1857,39 +1888,41 @@ pub(crate) fn setup_composefs_uki_boot(
18571888
// SAFETY: Deployment file name shouldn't containg non UTF-8 chars
18581889
let depl_file_name = depl_file_name.to_string_lossy();
18591890

1860-
usr_cfg.write_all(get_user_config(&boot_label, &depl_file_name).as_bytes())?;
1891+
buffer.write_all(get_user_config(&boot_label, &depl_file_name).as_bytes())?;
18611892
}
18621893

1894+
grub_dir
1895+
.atomic_write(user_cfg_name, buffer)
1896+
.with_context(|| format!("Writing to {user_cfg_name}"))?;
1897+
1898+
rustix::fs::fsync(grub_dir.reopen_as_ownedfd()?).context("fsync")?;
1899+
18631900
return Ok(());
18641901
}
18651902

1866-
let efi_uuid_file_path = format!("grub2/{EFI_UUID_FILE}");
1867-
18681903
// Open grub2/efiuuid.cfg and write the EFI partition fs-UUID in there
18691904
// This will be sourced by grub2/user.cfg to be used for `--fs-uuid`
1870-
let mut efi_uuid_file = std::fs::OpenOptions::new()
1871-
.write(true)
1872-
.create(true)
1873-
.open(boot_dir.join(&efi_uuid_file_path))
1874-
.with_context(|| format!("Opening {efi_uuid_file_path}"))?;
1875-
18761905
let esp_uuid = Task::new("blkid for ESP UUID", "blkid")
18771906
.args(["-s", "UUID", "-o", "value", &esp_device])
18781907
.read()?;
18791908

1880-
efi_uuid_file
1881-
.write_all(format!("set EFI_PART_UUID=\"{}\"", esp_uuid.trim()).as_bytes())
1882-
.with_context(|| format!("Writing to {efi_uuid_file_path}"))?;
1909+
grub_dir.atomic_write(
1910+
EFI_UUID_FILE,
1911+
format!("set EFI_PART_UUID=\"{}\"", esp_uuid.trim()).as_bytes(),
1912+
)?;
18831913

18841914
// Write to grub2/user.cfg
1885-
let mut usr_cfg = std::fs::OpenOptions::new()
1886-
.write(true)
1887-
.create(true)
1888-
.open(user_cfg_path)
1889-
.with_context(|| format!("Opening {user_cfg_name}"))?;
1890-
1891-
usr_cfg.write_all(efi_uuid_source.as_bytes())?;
1892-
usr_cfg.write_all(get_user_config(&boot_label, &id.to_hex()).as_bytes())?;
1915+
let mut buffer = vec![];
1916+
1917+
// Shouldn't really fail so no context here
1918+
buffer.write_all(efi_uuid_source.as_bytes())?;
1919+
buffer.write_all(get_user_config(&boot_label, &id.to_hex()).as_bytes())?;
1920+
1921+
grub_dir
1922+
.atomic_write(user_cfg_name, buffer)
1923+
.with_context(|| format!("Writing to {user_cfg_name}"))?;
1924+
1925+
rustix::fs::fsync(grub_dir.reopen_as_ownedfd()?).context("fsync")?;
18931926

18941927
Ok(())
18951928
}

0 commit comments

Comments
 (0)