Implement RENAME_NO_REPLACE

RENAME_NO_REPLACE is a neat flag for renameat2 which disallows renaming
a source onto an existing target.

I originally implemented this flag in relibc, but that code is still
subject to TOCTOU because the sequence of checking if the target exists
and the actual rename isn't atomic.

The code for the flag isn't used anywhere yet, like frename, but it's
unit tested and works for now.
This commit is contained in:
Josh Megnauth
2025-12-13 22:18:05 -05:00
parent 5c94db6f1a
commit 66f4766c6b
2 changed files with 126 additions and 0 deletions
+29
View File
@@ -1339,6 +1339,35 @@ impl<'a, D: Disk> Transaction<'a, D> {
Ok(())
}
pub fn rename_node_no_replace(
&mut self,
orig_parent_ptr: TreePtr<Node>,
orig_name: &str,
new_parent_ptr: TreePtr<Node>,
new_name: &str,
) -> Result<()> {
let orig = self.find_node(orig_parent_ptr, orig_name)?;
// The target shouldn't exist.
if self.find_node(new_parent_ptr, new_name).is_ok() {
return Err(Error::new(EEXIST));
}
// The rest is the same as rename_node.
// Link original file to new name
self.check_name(&new_parent_ptr, new_name)?;
self.link_node(new_parent_ptr, new_name, orig.ptr())?;
// Remove original file
self.remove_node(
orig_parent_ptr,
orig_name,
orig.data().mode() & Node::MODE_TYPE,
)?;
Ok(())
}
fn check_name(&mut self, parent_ptr: &TreePtr<Node>, name: &str) -> Result<()> {
if name.contains(':') {
return Err(Error::new(EINVAL));