RFC: renameat(): Add a RENAME_REMOVE flag to unlink hardlinks

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



If 'a' and 'b' are hardlinks, then the command `mv a b & mv b a`
can result in both being removed as mv needs to emulate the
move with an unlink of the source file.  This can only be done
without races in the kernel and so mv was recently changed
to not allow this operation at all.  mv could safely reintroduce
this feature by leveraging a new flag for renameat() for which
an illustrative/untested patch is attached.

thanks,
Pádraig.
>From 26d552617b00b3ffcd3b4646467cab5cb02f33ad Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?P=C3=A1draig=20Brady?= <P@xxxxxxxxxxxxxx>
Date: Fri, 21 Nov 2014 14:09:54 +0000
Subject: [PATCH] renameat: Add RENAME_REMOVE flag to unlink source if hardlink
 to dest

This operation can't be done in userspace with unlink without races,
as overlapping rename operations could remove both hardlinks.
---
 Documentation/filesystems/vfs.txt |  1 +
 fs/namei.c                        | 11 +++++++----
 include/uapi/linux/fs.h           |  1 +
 3 files changed, 9 insertions(+), 4 deletions(-)

diff --git a/Documentation/filesystems/vfs.txt b/Documentation/filesystems/vfs.txt
index 20bf204..2daa41a 100644
--- a/Documentation/filesystems/vfs.txt
+++ b/Documentation/filesystems/vfs.txt
@@ -430,6 +430,7 @@ otherwise noted.
 	(2) RENAME_EXCHANGE: exchange source and target.  Both must
 	exist; this is checked by the VFS.  Unlike plain rename,
 	source and target may be of different type.
+	(4) RENAME_REMOVE: unlink source even if a hardlink to dest.
 
   readlink: called by the readlink(2) system call. Only required if
 	you want to support reading symbolic links
diff --git a/fs/namei.c b/fs/namei.c
index db5fe86..caed596 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -4074,8 +4074,10 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry,
 	bool new_is_dir = false;
 	unsigned max_links = new_dir->i_sb->s_max_links;
 
-	if (source == target)
-		return 0;
+	if (source == target) {
+		if (old_dentry == new_dentry || !(flags & RENAME_REMOVE))
+			return 0;
+        }
 
 	error = may_delete(old_dir, old_dentry, is_dir);
 	if (error)
@@ -4210,10 +4212,11 @@ SYSCALL_DEFINE5(renameat2, int, olddfd, const char __user *, oldname,
 	bool should_retry = false;
 	int error;
 
-	if (flags & ~(RENAME_NOREPLACE | RENAME_EXCHANGE | RENAME_WHITEOUT))
+	if (flags & ~(RENAME_NOREPLACE | RENAME_EXCHANGE |
+		      RENAME_WHITEOUT | RENAME_REMOVE))
 		return -EINVAL;
 
-	if ((flags & (RENAME_NOREPLACE | RENAME_WHITEOUT)) &&
+	if ((flags & (RENAME_NOREPLACE | RENAME_WHITEOUT | RENAME_REMOVE)) &&
 	    (flags & RENAME_EXCHANGE))
 		return -EINVAL;
 
diff --git a/include/uapi/linux/fs.h b/include/uapi/linux/fs.h
index 3735fa0..601d901 100644
--- a/include/uapi/linux/fs.h
+++ b/include/uapi/linux/fs.h
@@ -38,6 +38,7 @@
 #define RENAME_NOREPLACE	(1 << 0)	/* Don't overwrite target */
 #define RENAME_EXCHANGE		(1 << 1)	/* Exchange source and dest */
 #define RENAME_WHITEOUT		(1 << 2)	/* Whiteout source */
+#define RENAME_REMOVE		(1 << 3)	/* Remove source hardlink */
 
 struct fstrim_range {
 	__u64 start;
-- 
2.1.0


[Index of Archives]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux