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 #454 (comment)
logic is like this:
- `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 #454
  • Loading branch information
HuijingHei committed Jun 25, 2024
1 parent 6a4fc6f commit 751dbe8
Showing 1 changed file with 155 additions and 31 deletions.
186 changes: 155 additions & 31 deletions src/filetree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -280,12 +280,62 @@ 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(())
}

#[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)
}

/// Convert diff sub dir name to temp
#[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 +352,81 @@ 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_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 Expand Up @@ -508,4 +599,37 @@ mod tests {
assert!(!a.join(relp).join("shim.x64").exists());
Ok(())
}

#[test]
fn test_apply_diff() -> Result<()> {
let tmpd = tempfile::tempdir()?;
let p = tmpd.path();
let pa = p.join("a");
let pb = p.join("b");
std::fs::create_dir(&pa)?;
std::fs::create_dir(&pb)?;
let a = openat::Dir::open(&pa)?;
let b = openat::Dir::open(&pb)?;
{
a.create_dir("foo", 0o755)?;
let mut bar = a.write_file("foo/bar", 0o644).expect("write foo/bar");
bar.write_all("foo".as_bytes())?;
}
{
b.create_dir("foo", 0o755)?;
let mut bar = b.write_file("foo/bar", 0o644).expect("write foo/bar");
bar.write_all("foo2".as_bytes())?;
}
let diff = run_diff(&a, &b)?;
assert_eq!(diff.count(), 1);
assert_eq!(diff.changes.len(), 1);
super::apply_diff(&b, &a, &diff, None)?;
assert_eq!(
String::from_utf8(std::fs::read(pa.join("foo/bar"))?)?,
"foo2"
);
let diff = run_diff(&a, &b)?;
assert_eq!(diff.count(), 0);
Ok(())
}
}

0 comments on commit 751dbe8

Please sign in to comment.