Skip to content

Commit

Permalink
efi: update the ESP by creating a tmpdir and RENAME_EXCHANGE
Browse files Browse the repository at this point in the history
See Timothée's comment coreos#454 (comment)
logic is like this, also need to consider `BOOT` update:
- `cp -a fedora fedora.tmp`
  - We start with a copy to make sure to keep all other files that we do not explicitly track in bootupd
- Update the content of `fedora.tmp` with the new binaries
- Exchange `fedora.tmp` -> `fedora`
- Remove now "old" `fedora.tmp`

Fixes coreos#454
  • Loading branch information
HuijingHei committed Jun 25, 2024
1 parent 6a4fc6f commit 3717639
Showing 1 changed file with 124 additions and 31 deletions.
155 changes: 124 additions & 31 deletions src/filetree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -280,12 +280,63 @@ pub(crate) fn syncfs(d: &openat::Dir) -> Result<()> {
rustix::fs::syncfs(d).map_err(Into::into)
}

fn copy_dir(src: &Path, dst: &Path) -> Result<()> {
let r = std::process::Command::new("cp")
.args(["-a"])
.arg(src)
.arg(dst)
.status()?;
if !r.success() {
anyhow::bail!("Failed to copy");
}
Ok(())
}

/// Convert path parent to temp, `fedora/foo` -> `fedora.tmp/foo`
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
fn tmpname_for_path<P: AsRef<Path>>(path: P) -> std::path::PathBuf {
fn tmp_for_path<P: AsRef<Path>>(path: P) -> std::path::PathBuf {
let path = path.as_ref();
let mut buf = path.file_name().expect("filename").to_os_string();
buf.push(TMP_PREFIX);
path.with_file_name(buf)
let mut parent = String::from("");
if let Some(p) = path.parent() {
parent = format!("{}.tmp", p.display());
}
let buf = path.file_name().expect("filename").to_os_string();
Path::new(parent.as_str()).join(buf)
}

/// Get diff based on sub dir
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
pub(crate) fn diff_with_prefix(diff: &FileTreeDiff, prefix: &str) -> Result<FileTreeDiff> {
let mut new_diff = FileTreeDiff {
additions: HashSet::new(),
removals: HashSet::new(),
changes: HashSet::new(),
};
for pathstr in diff.additions.iter() {
let path = Path::new(pathstr);
if let Some(parent) = path.parent() {
if parent.to_string_lossy().eq(prefix) {
new_diff.additions.insert(pathstr.to_string());
}
}
}
for pathstr in diff.changes.iter() {
let path = Path::new(pathstr);
if let Some(parent) = path.parent() {
if parent.to_string_lossy().eq(prefix) {
new_diff.changes.insert(pathstr.to_string());
}
}
}
for pathstr in diff.removals.iter() {
let path = Path::new(pathstr);
if let Some(parent) = path.parent() {
if parent.to_string_lossy().eq(prefix) {
new_diff.removals.insert(pathstr.to_string());
}
}
}
Ok(new_diff)
}

/// Given two directories, apply a diff generated from srcdir to destdir
Expand All @@ -302,40 +353,82 @@ pub(crate) fn apply_diff(
let opts = opts.unwrap_or(&default_opts);
cleanup_tmp(destdir).context("cleaning up temporary files")?;

// Write new and changed files
for pathstr in diff.additions.iter().chain(diff.changes.iter()) {
// Get updates based on dir
let mut updates = std::collections::HashSet::new();
for pathstr in diff
.additions
.iter()
.chain(diff.changes.iter())
.chain(diff.removals.iter())
{
let path = Path::new(pathstr);
if let Some(parent) = path.parent() {
destdir.ensure_dir_all(parent, DEFAULT_FILE_MODE)?;
updates.insert(parent.to_string_lossy().into_owned());
}
let destp = tmpname_for_path(path);
srcdir
.copy_file_at(path, destdir, destp.as_path())
.with_context(|| format!("writing {}", &pathstr))?;
}
// Ensure all of the new files are written persistently to disk
if !opts.skip_sync {
syncfs(destdir)?;
}
// Now move them all into place (TODO track interruption)
for path in diff.additions.iter().chain(diff.changes.iter()) {
let pathtmp = tmpname_for_path(path);
let efipath = destdir.recover_path()?;
for path in &updates {
let new_diff = diff_with_prefix(diff, path)?;
if !opts.skip_removals {
for path in new_diff.removals.iter() {
destdir
.remove_file_optional(path)
.with_context(|| format!("removing {path}"))?;
}
}
// Ensure all of the new files are written persistently to disk
if !opts.skip_sync {
syncfs(destdir)?;
}
// skip if there are no changes or additions
if new_diff.additions.len() == 0 && new_diff.changes.len() == 0 {
continue;
}
let src_path = efipath.join(&path);
let tmp_path = efipath.join(format!("{}.tmp", path));
if tmp_path.exists() {
std::fs::remove_dir_all(&tmp_path)?;
}
if let Some(parent) = tmp_path.parent() {
destdir.ensure_dir_all(parent, DEFAULT_FILE_MODE)?;
}
// in case there is additional with subdir
destdir.ensure_dir_all(&src_path, DEFAULT_FILE_MODE)?;
// copy original dir to tmp
copy_dir(&src_path, &tmp_path)
.with_context(|| format!("copying {:?} to {:?}", src_path, tmp_path))?;
assert!(destdir.exists(&tmp_path)?);

// Write new and changed files
for pathstr in new_diff.additions.iter().chain(new_diff.changes.iter()) {
let path = Path::new(pathstr);
let pathtmp = tmp_for_path(path);
if let Some(parent) = pathtmp.parent() {
destdir.ensure_dir_all(parent, DEFAULT_FILE_MODE)?;
}
destdir.remove_file_optional(&pathtmp)?;
srcdir
.copy_file_at(path, destdir, &pathtmp)
.with_context(|| format!("writing {:?} to {:?}", path, pathtmp))?;
}
// do local exchange
log::trace!(
"doing local exchange for {} and {}",
tmp_path.display(),
src_path.display()
);
destdir
.local_rename(&pathtmp, path)
.with_context(|| format!("renaming {path}"))?;
}
if !opts.skip_removals {
for path in diff.removals.iter() {
destdir
.remove_file_optional(path)
.with_context(|| format!("removing {path}"))?;
.local_exchange(&tmp_path, &src_path)
.with_context(|| format!("exchange for {:?} and {:?}", tmp_path, src_path))?;
// finally remove the temp dir
log::trace!("cleanup: {}", tmp_path.display());
std::fs::remove_dir_all(&tmp_path).context("clean up temp")?;
// A second full filesystem sync to narrow any races rather than
// waiting for writeback to kick in.
if !opts.skip_sync {
syncfs(destdir)?;
}
}
// A second full filesystem sync to narrow any races rather than
// waiting for writeback to kick in.
if !opts.skip_sync {
syncfs(destdir)?;
}

Ok(())
}
Expand Down

0 comments on commit 3717639

Please sign in to comment.