Skip to content

Commit 21b1edf

Browse files
author
Luca Bruno
authored
Merge pull request #28 from lucab/ups/local-rename-optional
OpenatDirExt: add helper for optional local renaming
2 parents fdd9da0 + 8b86208 commit 21b1edf

File tree

1 file changed

+55
-0
lines changed

1 file changed

+55
-0
lines changed

src/lib.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,15 @@ pub trait OpenatDirExt {
8080
/// Synchronize to disk the filesystem containing this directory.
8181
fn syncfs(&self) -> io::Result<()>;
8282

83+
/// If `oldpath` exists, rename it to `newpath`. Otherwise do nothing.
84+
///
85+
/// This returns `true` if the old path has been succesfully renamed, `false` otherwise.
86+
fn local_rename_optional<P: AsRef<Path>, R: AsRef<Path>>(
87+
&self,
88+
oldpath: P,
89+
newpath: R,
90+
) -> io::Result<bool>;
91+
8392
/// Copy a regular file. The semantics here are intended to match `std::fs::copy()`.
8493
/// If the target exists, it will be overwritten. The mode bits (permissions) will match, but
8594
/// owner/group will be derived from the current process. Extended attributes are not
@@ -308,6 +317,21 @@ impl OpenatDirExt for openat::Dir {
308317
}
309318
}
310319

320+
// NOTE(lucab): this isn't strictly an atomic operation, because
321+
// unfortunately `renameat` overloads `ENOENT` for multiple error cases.
322+
fn local_rename_optional<P: AsRef<Path>, R: AsRef<Path>>(
323+
&self,
324+
oldpath: P,
325+
newpath: R,
326+
) -> io::Result<bool> {
327+
if self.exists(oldpath.as_ref())? {
328+
self.local_rename(oldpath.as_ref(), newpath.as_ref())
329+
.and(Ok(true))
330+
} else {
331+
Ok(false)
332+
}
333+
}
334+
311335
fn new_file_writer<'a>(&'a self, mode: libc::mode_t) -> io::Result<FileWriter> {
312336
let (tmpf, name) = if let Some(tmpf) = self.new_unnamed_file(mode).ok() {
313337
(tmpf, None)
@@ -757,6 +781,37 @@ mod tests {
757781
Ok(())
758782
}
759783

784+
#[test]
785+
fn test_local_rename() {
786+
let td = tempfile::tempdir().unwrap();
787+
let d = openat::Dir::open(td.path()).unwrap();
788+
789+
{
790+
d.ensure_dir_all("src/foo", 0o755).unwrap();
791+
let renamed = d.local_rename_optional("src", "dst").unwrap();
792+
assert_eq!(renamed, true);
793+
assert_eq!(d.exists("src").unwrap(), false);
794+
assert_eq!(d.exists("dst/foo").unwrap(), true);
795+
let noent = d.local_rename_optional("src", "dst").unwrap();
796+
assert_eq!(noent, false);
797+
assert_eq!(d.remove_all("dst").unwrap(), true);
798+
}
799+
{
800+
let noent = d.local_rename_optional("missing", "dst").unwrap();
801+
assert_eq!(noent, false);
802+
}
803+
{
804+
d.ensure_dir_all("src/foo", 0o755).unwrap();
805+
let renamed = d.local_rename_optional("src", "dst").unwrap();
806+
assert_eq!(renamed, true);
807+
assert_eq!(d.exists("dst/foo").unwrap(), true);
808+
d.ensure_dir_all("src", 0o755).unwrap();
809+
let _ = d.local_rename_optional("src", "dst").unwrap_err();
810+
assert_eq!(d.exists("dst/foo").unwrap(), true);
811+
assert_eq!(d.remove_all("dst").unwrap(), true);
812+
}
813+
}
814+
760815
fn find_test_file(tempdir: &Path) -> Result<PathBuf> {
761816
for p in ["/proc/self/exe", "/usr/bin/bash"].iter() {
762817
let p = Path::new(p);

0 commit comments

Comments
 (0)