Skip to content

Commit

Permalink
Use renameat2 on Linux to implement persist with no overwrite.
Browse files Browse the repository at this point in the history
Use `rustix::fs::renameat_with`, which corresponds to Linux's
`renameat2`, to implement the non-overlapping form of `persist`, on
Linux versions and filesystems where it's supported.
  • Loading branch information
sunfishcode committed Feb 1, 2022
1 parent 2b35123 commit 65a5922
Showing 1 changed file with 22 additions and 1 deletion.
23 changes: 22 additions & 1 deletion src/file/imp/unix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,30 @@ pub fn persist(old_path: &Path, new_path: &Path, overwrite: bool) -> io::Result<
if overwrite {
renameat(&cwd(), old_path, &cwd(), new_path)?;
} else {
// On Linux, use `renameat_with` to avoid overwriting an existing name,
// if the kernel and the filesystem support it.
#[cfg(any(target_os = "android", target_os = "linux"))]
{
use rustix::fs::{renameat_with, RenameFlags};
use rustix::io::Error;
use std::sync::atomic::{AtomicBool, Ordering::Relaxed};

static NOSYS: AtomicBool = AtomicBool::new(false);
if !NOSYS.load(Relaxed) {
match renameat_with(&cwd(), old_path, &cwd(), new_path, RenameFlags::NOREPLACE) {
Ok(()) => return Ok(()),
Err(Error::NOSYS) => NOSYS.store(true, Relaxed),
Err(Error::INVAL) => {}
Err(e) => return Err(e.into()),
}
}
}

// Otherwise use `linkat` to create the new filesysten name, which
// will fail is the name already exists, and then `unlinkat` to remove
// the old name.
linkat(&cwd(), old_path, &cwd(), new_path, AtFlags::empty())?;
// Ignore unlink errors. Can we do better?
// On recent linux, we can use renameat2 to do this atomically.
let _ = unlinkat(&cwd(), old_path, AtFlags::empty());
}
Ok(())
Expand Down

0 comments on commit 65a5922

Please sign in to comment.