Skip to contentSkip to conversation

fs: add flag to linkat() for replacing destination

RFC PATCH In review Omar Sandoval 5 participants 22 messages Jan 28, 2020 – Feb 23, 2020 1 message not in the archive

Subsystems (29)
lore
Share & tools
lore
#1 lore raw

RFC PATCH 1/1 link.2: Document new AT_LINK_REPLACE flag

In reply to Omar Sandoval

From: Omar Sandoval <[email protected]>

---
 man2/link.2 | 191 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 191 insertions(+)
man2/link.2 +191 -0
index 649ba00c7..0097e3071 100644
@@ -174,6 +174,60 @@ like this:
 linkat(AT_FDCWD, "/proc/self/fd/<fd>", newdirfd,
        newname, AT_SYMLINK_FOLLOW);
 .EE
+.TP
+.BR AT_LINK_REPLACE " (since Linux 5.7)"
+If
+.I newpath
+exists, replace it atomically.
+There is no point at which another process attempting to access
+.I newpath
+will find it missing.
+If
+.I newpath
+exists but the operation fails,
+the original entry specified by
+.I newpath
+will remain in place.
+This does not guarantee data integrity;
+see EXAMPLE below for how to use this for crash-safe file replacement with
+.BR O_TMPFILE .
+.IP
+If
+.I newpath
+is replaced,
+any other hard links referring to the original file are unaffected.
+Open file descriptors for
+.I newpath
+are also unaffected.
+.IP
+.I newpath
+must not be a directory.
+.IP
+If the entry specified by
+.I newpath
+refers to the file specified by
+.I oldpath,
+.BR linkat ()
+does nothing and returns a success status.
+Note that this comparison does not follow mounts on
+.IR newpath .
+.IP
+Otherwise,
+.I newpath
+must not be a mount point in the local namespace.
+If it is a mount point in another namespace and the operation succeeds,
+all mounts are detached from
+.I newpath
+in all namespaces, as is the case for
+.BR rename (2),
+.BR rmdir (2),
+and
+.BR unlink (2).
+.IP
+If
+.I newpath
+refers to a symbolic link,
+the link will be replaced.
 .in
 .PP
 Before kernel 2.6.18, the
@@ -293,10 +347,34 @@ or
 .I newdirfd
 is not a valid file descriptor.
 .TP
+.B EBUSY
+.B AT_LINK_REPLACE
+was specified in
+.IR flags ,
+.I newpath
+does not refer to the file specified by
+.IR oldpath ,
+and
+.I newpath
+is in use by the system
+(for example, it is a mount point in the local namespace).
+.TP
 .B EINVAL
 An invalid flag value was specified in
 .IR flags .
 .TP
+.B EINVAL
+The filesystem does not support one of the flags in
+.IR flags .
+.TP
+.B EISDIR
+.B AT_LINK_REPLACE
+was specified in
+.I flags
+and
+.I newpath
+refers to an existing directory.
+.TP
 .B ENOENT
 .B AT_EMPTY_PATH
 was specified in
@@ -344,6 +422,31 @@ was specified in
 is an empty string, and
 .IR olddirfd
 refers to a directory.
+.TP
+.B EPERM
+.B AT_LINK_REPLACE
+was specified in
+.I flags
+and
+.I newpath
+refers to an immutable or append-only file
+or a file in an immutable or append-only directory.
+(See
+.BR ioctl_iflags (2).)
+.TP
+.BR EPERM " or " EACCES
+.B AT_LINK_REPLACE
+was specified in
+.IR flags ,
+the directory containing
+.I newpath
+has the sticky bit
+.RB ( S_ISVTX )
+set, and the process's effective UID is neither the UID of the file to
+be deleted nor that of the directory containing it, and
+the process is not privileged (Linux: does not have the
+.B CAP_FOWNER
+capability).
 .SH VERSIONS
 .BR linkat ()
 was added to Linux in kernel 2.6.16;
@@ -421,6 +524,94 @@ performs the link creation and dies before it can say so.
 Use
 .BR stat (2)
 to find out if the link got created.
+.SH EXAMPLE
+The following program demonstrates the use of
+.BR linkat ()
+with
+.B AT_LINK_REPLACE
+and
+.BR open (2)
+with
+.B O_TMPFILE
+for crash-safe file replacement.
+.SS Example output
+.in +4n
+.EX
+$ \fBecho bar > foo\fP
+$ \fB./replace foo\fP
+$ \fBcat foo\fP
+hello, world
+.EE
+.in
+.SS Program source (replace.c)
+.EX
+#define _GNU_SOURCE
+#include <fcntl.h>
+#include <libgen.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+int
+main(int argc, char *argv[])
+{
+	char *path, *dirc, *basec, *dir, *base;
+	int fd, dirfd;
+
+	if (argc != 2) {
+		fprintf(stderr, "usage: %s PATH\en", argv[0]);
+		exit(EXIT_FAILURE);
+	}
+
+	path = argv[1];
+
+	dirc = strdup(path);
+	basec = strdup(path);
+	if (!dirc || !basec) {
+		perror("strdup");
+		exit(EXIT_FAILURE);
+	}
+	dir = dirname(dirc);
+	base = basename(basec);
+
+	/* Open the parent directory. */
+	dirfd = open(dir, O_DIRECTORY | O_RDONLY);
+	if (dirfd == -1) {
+		perror("open");
+		exit(EXIT_FAILURE);
+	}
+
+	/* Open a temporary file, write data to it, and persist it. */
+	fd = open(dir, O_TMPFILE | O_RDWR, 0644);
+	if (fd == -1) {
+		perror("open");
+		exit(EXIT_FAILURE);
+	}
+	if (write(fd, "hello, world\en", 13) == -1) {
+		perror("write");
+		exit(EXIT_FAILURE);
+	}
+	if (fsync(fd) == -1) {
+		perror("fsync");
+		exit(EXIT_FAILURE);
+	}
+
+	/* Replace the original file and persist the directory. */
+	if (linkat(fd, "", dirfd, base, AT_EMPTY_PATH | AT_LINK_REPLACE) == -1) {
+		perror("linkat");
+		exit(EXIT_FAILURE);
+	}
+	if (fsync(dirfd) == -1) {
+		perror("fsync");
+		exit(EXIT_FAILURE);
+	}
+
+	exit(EXIT_SUCCESS);
+}
+.EE
 .SH SEE ALSO
 .BR ln (1),
 .BR open (2),
Signature · 1 line
2.25.0
#2 lore raw

RFC PATCH 1/1 generic: add smoke test for AT_LINK_REPLACE

In reply to Omar Sandoval

From: Omar Sandoval <[email protected]>

---
 common/rc             |  2 +-
 tests/generic/593     | 97 +++++++++++++++++++++++++++++++++++++++++++
 tests/generic/593.out |  6 +++
 tests/generic/group   |  1 +
 4 files changed, 105 insertions(+), 1 deletion(-)
 create mode 100755 tests/generic/593
 create mode 100644 tests/generic/593.out
common/rc +1 -1
index eeac1355..257f65a1 100644
@@ -2172,7 +2172,7 @@ _require_xfs_io_command()
 		;;
 	"flink")
 		local testlink=$TEST_DIR/$$.link.xfs_io
-		testio=`$XFS_IO_PROG -F -f -c "flink $testlink" $testfile 2>&1`
+		testio=`$XFS_IO_PROG -F -f -c "flink $param $testlink" $testfile 2>&1`
 		rm -f $testlink > /dev/null 2>&1
 		;;
 	"-T")
tests/generic/593 new file +97 -0

97 lines not on this page · raw message

tests/generic/593.out new file +6 -0
new file mode 100644
index 00000000..834c34bf
@@ -0,0 +1,6 @@
+QA output created by 593
+1
+flink: Is a directory
+flink: Is a directory
+flink: Is a directory
+flink: Device or resource busy
tests/generic/group +1 -0
index 6fe62505..0a87efca 100644
@@ -595,3 +595,4 @@
 590 auto prealloc preallocrw
 591 auto quick rw pipe splice
 592 auto quick encrypt
+593 auto quick hardlink
Signature · 1 line
2.25.0
#5 lore raw

RFC PATCH 1/1 xfs_io: add support for linkat() AT_LINK_REPLACE

In reply to Omar Sandoval

From: Omar Sandoval <[email protected]>

---
 io/link.c         | 24 ++++++++++++++++++++----
 man/man8/xfs_io.8 |  9 ++++++++-
 2 files changed, 28 insertions(+), 5 deletions(-)
io/link.c +20 -4
index f4f4b139..3fc3e24d 100644
@@ -12,6 +12,9 @@
 #ifndef AT_EMPTY_PATH
 #define AT_EMPTY_PATH	0x1000
 #endif
+#ifndef AT_LINK_REPLACE
+#define AT_LINK_REPLACE	0x10000
+#endif
 
 static cmdinfo_t flink_cmd;
 
@@ -22,6 +25,7 @@ flink_help(void)
 "\n"
 "link the open file descriptor to the supplied filename\n"
 "\n"
+" -f -- overwrite the target filename if it exists (AT_LINK_REPLACE)\n"
 "\n"));
 }
 
@@ -30,10 +34,22 @@ flink_f(
 	int		argc,
 	char		**argv)
 {
-	if (argc != 2)
+	int		flags = AT_EMPTY_PATH;
+	int		c;
+
+	while ((c = getopt(argc, argv, "f")) != EOF) {
+		switch (c) {
+		case 'f':
+			flags |= AT_LINK_REPLACE;
+			break;
+		default:
+			return command_usage(&flink_cmd);
+		}
+	}
+	if (optind != argc - 1)
 		return command_usage(&flink_cmd);
 
-	if (linkat(file->fd, "", AT_FDCWD, argv[1], AT_EMPTY_PATH) < 0) {
+	if (linkat(file->fd, "", AT_FDCWD, argv[optind], flags) < 0) {
 		perror("flink");
 		return 0;
 	}
@@ -46,9 +62,9 @@ flink_init(void)
 	flink_cmd.name = "flink";
 	flink_cmd.cfunc = flink_f;
 	flink_cmd.argmin = 1;
-	flink_cmd.argmax = 1;
+	flink_cmd.argmax = -1;
 	flink_cmd.flags = CMD_NOMAP_OK | CMD_FOREIGN_OK | CMD_FLAG_ONESHOT;
-	flink_cmd.args = _("filename");
+	flink_cmd.args = _("[-f] filename");
 	flink_cmd.oneline =
 		_("link the open file descriptor to the supplied filename");
 	flink_cmd.help = flink_help;
man/man8/xfs_io.8 +8 -1
index c69b295d..f79b3a59 100644
@@ -807,8 +807,15 @@ for the full list) is available via the
 .B help
 command.
 .TP
-.BI "flink " path
+.BI "flink [ \-f ]" " path"
 Link the currently open file descriptor into the filesystem namespace.
+.RS 1.0i
+.PD 0
+.TP 0.4i
+.B \-f
+overwrite the target path if it exists (AT_LINK_REPLACE).
+.PD
+.RE
 .TP
 .BR stat " [ " \-v "|" \-r " ]"
 Selected statistics from
Signature · 1 line
2.25.0
#0 lore raw

v4 cover fs: add flag to linkat() for replacing destination

From: Omar Sandoval <[email protected]>

Hello,

This series adds an AT_LINK_REPLACE flag to linkat() which allows atomically replacing the destination if it exists. This is a respin of an old series [1] that I was convinced to resend due to some recent interest [2][3].

Patch 1 adds a flags argument to i_ops->link() in preparation. Patch 2 adds the VFS support. Patch 3 fixes an inode leak in btrfs_link(), included in this series because it conflicts with patch 4. Patch 4 adds support for AT_LINK_REPLACE to Btrfs.

I've also included a man-page patch (with an example program), an xfs_io patch, and an fstest.

Some outstanding issues:

- The Btrfs implementation does a d_drop() on the replaced dentry. We probably want a d_replace() helper for filesystems to use.
- Should AT_LINK_REPLACE be limited to O_TMPFILE? In my opinion, the answer is no. After all, ln -f is not that exotic.
- Should AT_LINK_REPLACE guarantee data integrity? Again, I think the answer is no. That's more suited to something like Amir's AT_ATOMIC proposal [4].

Changes since v3:

- Rebased on v5.5.
- Added patches 1 and 3.
- Incorporated Al's feedback on various error cases in patch 2.
- Renamed the flag to AT_LINK_REPLACE.

Thanks!

1: https://lore.kernel.org/linux-fsdevel/[email protected]/
2: https://lore.kernel.org/linux-fsdevel/[email protected]/
3: https://lore.kernel.org/linux-fsdevel/[email protected]/
4: https://lore.kernel.org/linux-fsdevel/[email protected]/

Cc David Howells <[email protected]>
Cc Amir Goldstein <[email protected]>
Cc Xi Wang <[email protected]>

Omar Sandoval (4):
fs: add flags argument to i_op->link()
fs: add AT_LINK_REPLACE flag for linkat() which replaces the target
Btrfs: fix inode reference count leak in btrfs_link() error path
Btrfs: add support for linkat() AT_REPLACE

 fs/9p/vfs_inode.c          |   5 +-
 fs/9p/vfs_inode_dotl.c     |   5 +-
 fs/affs/affs.h             |   2 +-
 fs/affs/namei.c            |   6 +-
 fs/afs/dir.c               |   7 +-
 fs/bad_inode.c             |   2 +-
 fs/bfs/dir.c               |   7 +-
 fs/btrfs/inode.c           |  70 ++++++++++++++--
 fs/ceph/dir.c              |   5 +-
 fs/cifs/cifsfs.h           |   2 +-
 fs/cifs/link.c             |   5 +-
 fs/coda/dir.c              |   5 +-
 fs/ecryptfs/inode.c        |   7 +-
 fs/ext2/namei.c            |   5 +-
 fs/ext4/namei.c            |   7 +-
 fs/f2fs/namei.c            |   5 +-
 fs/fuse/dir.c              |   5 +-
 fs/gfs2/inode.c            |   5 +-
 fs/hfsplus/dir.c           |   5 +-
 fs/hostfs/hostfs_kern.c    |   5 +-
 fs/jffs2/dir.c             |   8 +-
 fs/jfs/namei.c             |   7 +-
 fs/libfs.c                 |   6 +-
 fs/minix/namei.c           |   5 +-
 fs/namei.c                 | 166 +++++++++++++++++++++++++++++--------
 fs/nfs/dir.c               |   6 +-
 fs/nfs/internal.h          |   2 +-
 fs/nfsd/vfs.c              |   2 +-
 fs/nilfs2/namei.c          |   5 +-
 fs/ocfs2/namei.c           |   6 +-
 fs/overlayfs/dir.c         |   5 +-
 fs/overlayfs/overlayfs.h   |   2 +-
 fs/reiserfs/namei.c        |   5 +-
 fs/sysv/namei.c            |   7 +-
 fs/ubifs/dir.c             |   5 +-
 fs/udf/namei.c             |   5 +-
 fs/ufs/namei.c             |   5 +-
 fs/xfs/xfs_iops.c          |   6 +-
 include/linux/fs.h         |   6 +-
 include/uapi/linux/fcntl.h |   1 +
 mm/shmem.c                 |   6 +-
 41 files changed, 341 insertions(+), 90 deletions(-)
Signature · 1 line
2.25.0
#10 lore raw

RFC PATCH v4 2/4 fs: add AT_LINK_REPLACE flag for linkat() which replaces the target

From: Omar Sandoval <[email protected]>

One of the most common uses of temporary files is the classic atomic replacement pattern, i.e.,

- write temporary file
- fsync temporary file
- rename temporary file over real file
- fsync parent directory

Now, we have O_TMPFILE, which gives us a much better way to create temporary files, but it's not possible to use it for this pattern.

This patch introduces an AT_LINK_REPLACE flag which allows linkat() to replace the target file. Now, the temporary file in the pattern above can be a proper O_TMPFILE. Even without O_TMPFILE, this is a new primitive which might be useful in other contexts.

The implementation on the VFS side mimics sys_renameat2().

---
 fs/ecryptfs/inode.c        |   2 +-
 fs/namei.c                 | 166 +++++++++++++++++++++++++++++--------
 fs/nfsd/vfs.c              |   2 +-
 fs/overlayfs/overlayfs.h   |   2 +-
 include/linux/fs.h         |   2 +-
 include/uapi/linux/fcntl.h |   1 +
 6 files changed, 135 insertions(+), 40 deletions(-)
fs/ecryptfs/inode.c +1 -1
index eeb351b220b2..2f36b7a61a2f 100644
@@ -440,7 +440,7 @@ static int ecryptfs_link(struct dentry *old_dentry, struct inode *dir,
 	dget(lower_new_dentry);
 	lower_dir_dentry = lock_parent(lower_new_dentry);
 	rc = vfs_link(lower_old_dentry, d_inode(lower_dir_dentry),
-		      lower_new_dentry, NULL);
+		      lower_new_dentry, NULL, 0);
 	if (rc || d_really_is_negative(lower_new_dentry))
 		goto out_lock;
 	rc = ecryptfs_interpose(lower_new_dentry, new_dentry, dir->i_sb);
fs/namei.c +130 -36

233 lines not on this page · raw message

fs/nfsd/vfs.c +1 -1
index c0dc491537a6..3f9291e76b99 100644
@@ -1598,7 +1598,7 @@ nfsd_link(struct svc_rqst *rqstp, struct svc_fh *ffhp,
 	err = nfserr_noent;
 	if (d_really_is_negative(dold))
 		goto out_dput;
-	host_err = vfs_link(dold, dirp, dnew, NULL);
+	host_err = vfs_link(dold, dirp, dnew, NULL, 0);
 	if (!host_err) {
 		err = nfserrno(commit_metadata(ffhp));
 		if (!err)
fs/overlayfs/overlayfs.h +1 -1
index f283b1d69a9e..b199fc03c891 100644
@@ -120,7 +120,7 @@ static inline int ovl_do_unlink(struct inode *dir, struct dentry *dentry)
 static inline int ovl_do_link(struct dentry *old_dentry, struct inode *dir,
 			      struct dentry *new_dentry)
 {
-	int err = vfs_link(old_dentry, dir, new_dentry, NULL);
+	int err = vfs_link(old_dentry, dir, new_dentry, NULL, 0);
 
 	pr_debug("link(%pd2, %pd2) = %i\n", old_dentry, new_dentry, err);
 	return err;
include/linux/fs.h +1 -1
index 3bdb71c97e8f..93eb90eb1fdb 100644
@@ -1712,7 +1712,7 @@ extern int vfs_create(struct inode *, struct dentry *, umode_t, bool);
 extern int vfs_mkdir(struct inode *, struct dentry *, umode_t);
 extern int vfs_mknod(struct inode *, struct dentry *, umode_t, dev_t);
 extern int vfs_symlink(struct inode *, struct dentry *, const char *);
-extern int vfs_link(struct dentry *, struct inode *, struct dentry *, struct inode **);
+extern int vfs_link(struct dentry *, struct inode *, struct dentry *, struct inode **, int);
 extern int vfs_rmdir(struct inode *, struct dentry *);
 extern int vfs_unlink(struct inode *, struct dentry *, struct inode **);
 extern int vfs_rename(struct inode *, struct dentry *, struct inode *, struct dentry *, struct inode **, unsigned int);
include/uapi/linux/fcntl.h +1 -0
index 1f97b33c840e..3704793cd5ab 100644
@@ -99,6 +99,7 @@
 #define AT_STATX_DONT_SYNC	0x4000	/* - Don't sync attributes with the server */
 
 #define AT_RECURSIVE		0x8000	/* Apply to the entire subtree */
+#define AT_LINK_REPLACE		0x10000	/* Replace link() target */
 
 
 #endif /* _UAPI_LINUX_FCNTL_H */
Signature · 1 line
2.25.0
#11 lore raw

RFC PATCH v4 3/4 Btrfs: fix inode reference count leak in btrfs_link() error path

In reply to Omar Sandoval

From: Omar Sandoval <[email protected]>

If btrfs_update_inode() or btrfs_orphan_del() fails in btrfs_link(), then we don't drop the reference we got with ihold(). This results in the "VFS: Busy inodes after unmount" crash.

The reference is needed for the new dentry, so get it right before we instantiate the dentry.

Fixes 79787eaab461 ("btrfs: replace many BUG_ONs with proper error handling")

[Although d_instantiate() was moved further from ihold() before that, in commit 08c422c27f85 ("Btrfs: call d_instantiate after all ops are setup")]

---
 fs/btrfs/inode.c | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)
fs/btrfs/inode.c +2 -4
index bc7709c4f6eb..8c9a114f48f6 100644
@@ -6801,7 +6801,6 @@ static int btrfs_link(struct dentry *old_dentry, struct inode *dir,
 	inc_nlink(inode);
 	inode_inc_iversion(inode);
 	inode->i_ctime = current_time(inode);
-	ihold(inode);
 	set_bit(BTRFS_INODE_COPY_EVERYTHING, &BTRFS_I(inode)->runtime_flags);
 
 	err = btrfs_add_nondir(trans, BTRFS_I(dir), dentry, BTRFS_I(inode),
@@ -6825,6 +6824,7 @@ static int btrfs_link(struct dentry *old_dentry, struct inode *dir,
 			if (err)
 				goto fail;
 		}
+		ihold(inode);
 		d_instantiate(dentry, inode);
 		ret = btrfs_log_new_name(trans, BTRFS_I(inode), NULL, parent,
 					 true, NULL);
@@ -6837,10 +6837,8 @@ static int btrfs_link(struct dentry *old_dentry, struct inode *dir,
 fail:
 	if (trans)
 		btrfs_end_transaction(trans);
-	if (drop_inode) {
+	if (drop_inode)
 		inode_dec_link_count(inode);
-		iput(inode);
-	}
 	btrfs_btree_balance_dirty(fs_info);
 	return err;
 }
Signature · 1 line
2.25.0
#7 lore raw

RFC PATCH v4 1/4 fs: add flags argument to i_op->link()

In reply to Omar Sandoval

From: Omar Sandoval <[email protected]>

In preparation for adding the AT_LINK_REPLACE flag, make ->link() take and check a flags argument.

---
 fs/9p/vfs_inode.c       | 5 ++++-
 fs/9p/vfs_inode_dotl.c  | 5 ++++-
 fs/affs/affs.h          | 2 +-
 fs/affs/namei.c         | 6 +++++-
 fs/afs/dir.c            | 7 +++++--
 fs/bad_inode.c          | 2 +-
 fs/bfs/dir.c            | 7 +++++--
 fs/btrfs/inode.c        | 5 ++++-
 fs/ceph/dir.c           | 5 ++++-
 fs/cifs/cifsfs.h        | 2 +-
 fs/cifs/link.c          | 5 ++++-
 fs/coda/dir.c           | 5 ++++-
 fs/ecryptfs/inode.c     | 5 ++++-
 fs/ext2/namei.c         | 5 ++++-
 fs/ext4/namei.c         | 7 +++++--
 fs/f2fs/namei.c         | 5 ++++-
 fs/fuse/dir.c           | 5 ++++-
 fs/gfs2/inode.c         | 5 ++++-
 fs/hfsplus/dir.c        | 5 ++++-
 fs/hostfs/hostfs_kern.c | 5 ++++-
 fs/jffs2/dir.c          | 8 ++++++--
 fs/jfs/namei.c          | 7 +++++--
 fs/libfs.c              | 6 +++++-
 fs/minix/namei.c        | 5 ++++-
 fs/namei.c              | 2 +-
 fs/nfs/dir.c            | 6 +++++-
 fs/nfs/internal.h       | 2 +-
 fs/nilfs2/namei.c       | 5 ++++-
 fs/ocfs2/namei.c        | 6 +++++-
 fs/overlayfs/dir.c      | 5 ++++-
 fs/reiserfs/namei.c     | 5 ++++-
 fs/sysv/namei.c         | 7 +++++--
 fs/ubifs/dir.c          | 5 ++++-
 fs/udf/namei.c          | 5 ++++-
 fs/ufs/namei.c          | 5 ++++-
 fs/xfs/xfs_iops.c       | 6 +++++-
 include/linux/fs.h      | 4 ++--
 mm/shmem.c              | 6 +++++-
 38 files changed, 148 insertions(+), 45 deletions(-)
fs/9p/vfs_inode.c +4 -1
index b82423a72f68..3cda7788122e 100644
@@ -1335,12 +1335,15 @@ v9fs_vfs_symlink(struct inode *dir, struct dentry *dentry, const char *symname)
 
 static int
 v9fs_vfs_link(struct dentry *old_dentry, struct inode *dir,
-	      struct dentry *dentry)
+	      struct dentry *dentry, int flags)
 {
 	int retval;
 	char name[1 + U32_MAX_DIGITS + 2]; /* sign + number + \n + \0 */
 	struct p9_fid *oldfid;
 
+	if (flags)
+		return -EINVAL;
+
 	p9_debug(P9_DEBUG_VFS, " %lu,%pd,%pd\n",
 		 dir->i_ino, dentry, old_dentry);
 
fs/9p/vfs_inode_dotl.c +4 -1
index 60328b21c5fb..636796582f68 100644
@@ -749,12 +749,15 @@ v9fs_vfs_symlink_dotl(struct inode *dir, struct dentry *dentry,
 
 static int
 v9fs_vfs_link_dotl(struct dentry *old_dentry, struct inode *dir,
-		struct dentry *dentry)
+		   struct dentry *dentry, int flags)
 {
 	int err;
 	struct p9_fid *dfid, *oldfid;
 	struct v9fs_session_info *v9ses;
 
+	if (flags)
+		return -EINVAL;
+
 	p9_debug(P9_DEBUG_VFS, "dir ino: %lu, old_name: %pd, new_name: %pd\n",
 		 dir->i_ino, old_dentry, dentry);
 
fs/affs/affs.h +1 -1
index a755bef7c4c7..98d70fd7ea47 100644
@@ -171,7 +171,7 @@ extern int	affs_create(struct inode *dir, struct dentry *dentry, umode_t mode, b
 extern int	affs_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode);
 extern int	affs_rmdir(struct inode *dir, struct dentry *dentry);
 extern int	affs_link(struct dentry *olddentry, struct inode *dir,
-			  struct dentry *dentry);
+			  struct dentry *dentry, int flags);
 extern int	affs_symlink(struct inode *dir, struct dentry *dentry,
 			     const char *symname);
 extern int	affs_rename2(struct inode *old_dir, struct dentry *old_dentry,
fs/affs/namei.c +5 -1
index 41c5749f4db7..198f2878145d 100644
@@ -388,10 +388,14 @@ affs_symlink(struct inode *dir, struct dentry *dentry, const char *symname)
 }
 
 int
-affs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *dentry)
+affs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *dentry,
+	  int flags)
 {
 	struct inode *inode = d_inode(old_dentry);
 
+	if (flags)
+		return -EINVAL;
+
 	pr_debug("%s(%lu, %lu, \"%pd\")\n", __func__, inode->i_ino, dir->i_ino,
 		 dentry);
 
fs/afs/dir.c +5 -2
index 5c794f4b051a..0a17d8d8c009 100644
@@ -34,7 +34,7 @@ static int afs_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode);
 static int afs_rmdir(struct inode *dir, struct dentry *dentry);
 static int afs_unlink(struct inode *dir, struct dentry *dentry);
 static int afs_link(struct dentry *from, struct inode *dir,
-		    struct dentry *dentry);
+		    struct dentry *dentry, int flags);
 static int afs_symlink(struct inode *dir, struct dentry *dentry,
 		       const char *content);
 static int afs_rename(struct inode *old_dir, struct dentry *old_dentry,
@@ -1641,7 +1641,7 @@ static int afs_create(struct inode *dir, struct dentry *dentry, umode_t mode,
  * create a hard link between files in an AFS filesystem
  */
 static int afs_link(struct dentry *from, struct inode *dir,
-		    struct dentry *dentry)
+		    struct dentry *dentry, int flags)
 {
 	struct afs_fs_cursor fc;
 	struct afs_status_cb *scb;
@@ -1650,6 +1650,9 @@ static int afs_link(struct dentry *from, struct inode *dir,
 	struct key *key;
 	int ret;
 
+	if (flags)
+		return -EINVAL;
+
 	_enter("{%llx:%llu},{%llx:%llu},{%pd}",
 	       vnode->fid.vid, vnode->fid.vnode,
 	       dvnode->fid.vid, dvnode->fid.vnode,
fs/bad_inode.c +1 -1
index 8035d2a44561..41caa98597d6 100644
@@ -39,7 +39,7 @@ static struct dentry *bad_inode_lookup(struct inode *dir,
 }
 
 static int bad_inode_link (struct dentry *old_dentry, struct inode *dir,
-		struct dentry *dentry)
+			   struct dentry *dentry, int flags)
 {
 	return -EIO;
 }
fs/bfs/dir.c +5 -2
index d8dfe3a0cb39..0b0ef8a59a73 100644
@@ -144,13 +144,16 @@ static struct dentry *bfs_lookup(struct inode *dir, struct dentry *dentry,
 	return d_splice_alias(inode, dentry);
 }
 
-static int bfs_link(struct dentry *old, struct inode *dir,
-						struct dentry *new)
+static int bfs_link(struct dentry *old, struct inode *dir, struct dentry *new,
+		    int flags)
 {
 	struct inode *inode = d_inode(old);
 	struct bfs_sb_info *info = BFS_SB(inode->i_sb);
 	int err;
 
+	if (flags)
+		return -EINVAL;
+
 	mutex_lock(&info->bfs_lock);
 	err = bfs_add_entry(dir, &new->d_name, inode->i_ino);
 	if (err) {
fs/btrfs/inode.c +4 -1
index c70baafb2a39..bc7709c4f6eb 100644
@@ -6759,7 +6759,7 @@ static int btrfs_create(struct inode *dir, struct dentry *dentry,
 }
 
 static int btrfs_link(struct dentry *old_dentry, struct inode *dir,
-		      struct dentry *dentry)
+		      struct dentry *dentry, int flags)
 {
 	struct btrfs_trans_handle *trans = NULL;
 	struct btrfs_root *root = BTRFS_I(dir)->root;
@@ -6769,6 +6769,9 @@ static int btrfs_link(struct dentry *old_dentry, struct inode *dir,
 	int err;
 	int drop_inode = 0;
 
+	if (flags)
+		return -EINVAL;
+
 	/* do not allow sys_link's with other subvols of the same device */
 	if (root->root_key.objectid != BTRFS_I(inode)->root->root_key.objectid)
 		return -EXDEV;
fs/ceph/dir.c +4 -1
index 2e4764fd1872..a1517be5405c 100644
@@ -999,13 +999,16 @@ static int ceph_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode)
 }
 
 static int ceph_link(struct dentry *old_dentry, struct inode *dir,
-		     struct dentry *dentry)
+		     struct dentry *dentry, int flags)
 {
 	struct ceph_fs_client *fsc = ceph_sb_to_client(dir->i_sb);
 	struct ceph_mds_client *mdsc = fsc->mdsc;
 	struct ceph_mds_request *req;
 	int err;
 
+	if (flags)
+		return -EINVAL;
+
 	if (ceph_snap(dir) != CEPH_NOSNAP)
 		return -EROFS;
 
fs/cifs/cifsfs.h +1 -1
index b59dc7478130..9360d28255ef 100644
@@ -69,7 +69,7 @@ extern int cifs_atomic_open(struct inode *, struct dentry *,
 extern struct dentry *cifs_lookup(struct inode *, struct dentry *,
 				  unsigned int);
 extern int cifs_unlink(struct inode *dir, struct dentry *dentry);
-extern int cifs_hardlink(struct dentry *, struct inode *, struct dentry *);
+extern int cifs_hardlink(struct dentry *, struct inode *, struct dentry *, int);
 extern int cifs_mknod(struct inode *, struct dentry *, umode_t, dev_t);
 extern int cifs_mkdir(struct inode *, struct dentry *, umode_t);
 extern int cifs_rmdir(struct inode *, struct dentry *);
fs/cifs/link.c +4 -1
index b736acd3917b..1471d4c095d7 100644
@@ -516,7 +516,7 @@ smb3_create_mf_symlink(unsigned int xid, struct cifs_tcon *tcon,
 
 int
 cifs_hardlink(struct dentry *old_file, struct inode *inode,
-	      struct dentry *direntry)
+	      struct dentry *direntry, int flags)
 {
 	int rc = -EACCES;
 	unsigned int xid;
@@ -528,6 +528,9 @@ cifs_hardlink(struct dentry *old_file, struct inode *inode,
 	struct TCP_Server_Info *server;
 	struct cifsInodeInfo *cifsInode;
 
+	if (flags)
+		return -EINVAL;
+
 	tlink = cifs_sb_tlink(cifs_sb);
 	if (IS_ERR(tlink))
 		return PTR_ERR(tlink);
fs/coda/dir.c +4 -1
index ca40c2556ba6..acdbc47948cf 100644
@@ -200,13 +200,16 @@ static int coda_mkdir(struct inode *dir, struct dentry *de, umode_t mode)
 
 /* try to make de an entry in dir_inodde linked to source_de */ 
 static int coda_link(struct dentry *source_de, struct inode *dir_inode, 
-	  struct dentry *de)
+		     struct dentry *de, int flags)
 {
 	struct inode *inode = d_inode(source_de);
         const char * name = de->d_name.name;
 	int len = de->d_name.len;
 	int error;
 
+	if (flags)
+		return -EINVAL;
+
 	if (is_root_inode(dir_inode) && coda_iscontrol(name, len))
 		return -EPERM;
 
fs/ecryptfs/inode.c +4 -1
index e23752d9a79f..eeb351b220b2 100644
@@ -422,7 +422,7 @@ static struct dentry *ecryptfs_lookup(struct inode *ecryptfs_dir_inode,
 }
 
 static int ecryptfs_link(struct dentry *old_dentry, struct inode *dir,
-			 struct dentry *new_dentry)
+			 struct dentry *new_dentry, int flags)
 {
 	struct dentry *lower_old_dentry;
 	struct dentry *lower_new_dentry;
@@ -430,6 +430,9 @@ static int ecryptfs_link(struct dentry *old_dentry, struct inode *dir,
 	u64 file_size_save;
 	int rc;
 
+	if (flags)
+		return -EINVAL;
+
 	file_size_save = i_size_read(d_inode(old_dentry));
 	lower_old_dentry = ecryptfs_dentry_to_lower(old_dentry);
 	lower_new_dentry = ecryptfs_dentry_to_lower(new_dentry);
fs/ext2/namei.c +4 -1
index ccfbbf59e2fc..9417a96f6ea8 100644
@@ -196,11 +196,14 @@ static int ext2_symlink (struct inode * dir, struct dentry * dentry,
 }
 
 static int ext2_link (struct dentry * old_dentry, struct inode * dir,
-	struct dentry *dentry)
+		      struct dentry *dentry, int flags)
 {
 	struct inode *inode = d_inode(old_dentry);
 	int err;
 
+	if (flags)
+		return -EINVAL;
+
 	err = dquot_initialize(dir);
 	if (err)
 		return err;
fs/ext4/namei.c +5 -2
index 1cb42d940784..80e27fd907d0 100644
@@ -3366,13 +3366,16 @@ static int ext4_symlink(struct inode *dir,
 	return err;
 }
 
-static int ext4_link(struct dentry *old_dentry,
-		     struct inode *dir, struct dentry *dentry)
+static int ext4_link(struct dentry *old_dentry, struct inode *dir,
+		     struct dentry *dentry, int flags)
 {
 	handle_t *handle;
 	struct inode *inode = d_inode(old_dentry);
 	int err, retries = 0;
 
+	if (flags)
+		return -EINVAL;
+
 	if (inode->i_nlink >= EXT4_LINK_MAX)
 		return -EMLINK;
 
fs/f2fs/namei.c +4 -1
index a1c507b0b4ac..f7ca6fbf5aaf 100644
@@ -312,12 +312,15 @@ static int f2fs_create(struct inode *dir, struct dentry *dentry, umode_t mode,
 }
 
 static int f2fs_link(struct dentry *old_dentry, struct inode *dir,
-		struct dentry *dentry)
+		     struct dentry *dentry, int flags)
 {
 	struct inode *inode = d_inode(old_dentry);
 	struct f2fs_sb_info *sbi = F2FS_I_SB(dir);
 	int err;
 
+	if (flags)
+		return -EINVAL;
+
 	if (unlikely(f2fs_cp_error(sbi)))
 		return -EIO;
 	if (!f2fs_is_checkpoint_ready(sbi))
fs/fuse/dir.c +4 -1
index ee190119f45c..ae0cf00fd9ec 100644
@@ -842,7 +842,7 @@ static int fuse_rename2(struct inode *olddir, struct dentry *oldent,
 }
 
 static int fuse_link(struct dentry *entry, struct inode *newdir,
-		     struct dentry *newent)
+		     struct dentry *newent, int flags)
 {
 	int err;
 	struct fuse_link_in inarg;
@@ -850,6 +850,9 @@ static int fuse_link(struct dentry *entry, struct inode *newdir,
 	struct fuse_conn *fc = get_fuse_conn(inode);
 	FUSE_ARGS(args);
 
+	if (flags)
+		return -EINVAL;
+
 	memset(&inarg, 0, sizeof(inarg));
 	inarg.oldnodeid = get_node_id(inode);
 	args.opcode = FUSE_LINK;
fs/gfs2/inode.c +4 -1
index dafef10b91f1..f2f97321b659 100644
@@ -891,7 +891,7 @@ static struct dentry *gfs2_lookup(struct inode *dir, struct dentry *dentry,
  */
 
 static int gfs2_link(struct dentry *old_dentry, struct inode *dir,
-		     struct dentry *dentry)
+		     struct dentry *dentry, int flags)
 {
 	struct gfs2_inode *dip = GFS2_I(dir);
 	struct gfs2_sbd *sdp = GFS2_SB(dir);
@@ -902,6 +902,9 @@ static int gfs2_link(struct dentry *old_dentry, struct inode *dir,
 	struct gfs2_diradd da = { .bh = NULL, .save_loc = 1, };
 	int error;
 
+	if (flags)
+		return -EINVAL;
+
 	if (S_ISDIR(inode->i_mode))
 		return -EPERM;
 
fs/hfsplus/dir.c +4 -1
index 29a9dcfbe81f..16cf85e8e51d 100644
@@ -298,7 +298,7 @@ static int hfsplus_dir_release(struct inode *inode, struct file *file)
 }
 
 static int hfsplus_link(struct dentry *src_dentry, struct inode *dst_dir,
-			struct dentry *dst_dentry)
+			struct dentry *dst_dentry, int flags)
 {
 	struct hfsplus_sb_info *sbi = HFSPLUS_SB(dst_dir->i_sb);
 	struct inode *inode = d_inode(src_dentry);
@@ -308,6 +308,9 @@ static int hfsplus_link(struct dentry *src_dentry, struct inode *dst_dir,
 	u32 cnid, id;
 	int res;
 
+	if (flags)
+		return -EINVAL;
+
 	if (HFSPLUS_IS_RSRC(inode))
 		return -EPERM;
 	if (!S_ISREG(inode->i_mode))
fs/hostfs/hostfs_kern.c +4 -1
index 5a7eb0c79839..f2bd0e42075b 100644
@@ -622,11 +622,14 @@ static struct dentry *hostfs_lookup(struct inode *ino, struct dentry *dentry,
 }
 
 static int hostfs_link(struct dentry *to, struct inode *ino,
-		       struct dentry *from)
+		       struct dentry *from, int flags)
 {
 	char *from_name, *to_name;
 	int err;
 
+	if (flags)
+		return -EOPNOTSUPP;
+
 	if ((from_name = dentry_name(from)) == NULL)
 		return -ENOMEM;
 	to_name = dentry_name(to);
fs/jffs2/dir.c +6 -2
index f20cff1194bb..c4ec9202ca8e 100644
@@ -28,7 +28,7 @@ static int jffs2_create (struct inode *,struct dentry *,umode_t,
 			 bool);
 static struct dentry *jffs2_lookup (struct inode *,struct dentry *,
 				    unsigned int);
-static int jffs2_link (struct dentry *,struct inode *,struct dentry *);
+static int jffs2_link (struct dentry *,struct inode *,struct dentry *, int);
 static int jffs2_unlink (struct inode *,struct dentry *);
 static int jffs2_symlink (struct inode *,struct dentry *,const char *);
 static int jffs2_mkdir (struct inode *,struct dentry *,umode_t);
@@ -240,7 +240,8 @@ static int jffs2_unlink(struct inode *dir_i, struct dentry *dentry)
 /***********************************************************************/
 
 
-static int jffs2_link (struct dentry *old_dentry, struct inode *dir_i, struct dentry *dentry)
+static int jffs2_link (struct dentry *old_dentry, struct inode *dir_i,
+		       struct dentry *dentry, int flags)
 {
 	struct jffs2_sb_info *c = JFFS2_SB_INFO(old_dentry->d_sb);
 	struct jffs2_inode_info *f = JFFS2_INODE_INFO(d_inode(old_dentry));
@@ -249,6 +250,9 @@ static int jffs2_link (struct dentry *old_dentry, struct inode *dir_i, struct de
 	uint8_t type;
 	uint32_t now;
 
+	if (flags)
+		return -EINVAL;
+
 	/* Don't let people make hard links to bad inodes. */
 	if (!f->inocache)
 		return -EIO;
fs/jfs/namei.c +5 -2
index 7a55d14cc1af..45447fca1fdb 100644
@@ -781,8 +781,8 @@ void jfs_free_zero_link(struct inode *ip)
  * EXDEV: target object and new link are on different file systems and
  * implementation does not support links between file systems [XPG4.2].
  */
-static int jfs_link(struct dentry *old_dentry,
-	     struct inode *dir, struct dentry *dentry)
+static int jfs_link(struct dentry *old_dentry, struct inode *dir,
+		    struct dentry *dentry, int flags)
 {
 	int rc;
 	tid_t tid;
@@ -792,6 +792,9 @@ static int jfs_link(struct dentry *old_dentry,
 	struct btstack btstack;
 	struct inode *iplist[2];
 
+	if (flags)
+		return -EINVAL;
+
 	jfs_info("jfs_link: %pd %pd", old_dentry, dentry);
 
 	rc = dquot_initialize(dir);
fs/libfs.c +5 -1
index 1463b038ffc4..1894d6b202f2 100644
@@ -318,10 +318,14 @@ int simple_open(struct inode *inode, struct file *file)
 }
 EXPORT_SYMBOL(simple_open);
 
-int simple_link(struct dentry *old_dentry, struct inode *dir, struct dentry *dentry)
+int simple_link(struct dentry *old_dentry, struct inode *dir,
+		struct dentry *dentry, int flags)
 {
 	struct inode *inode = d_inode(old_dentry);
 
+	if (flags)
+		return -EINVAL;
+
 	inode->i_ctime = dir->i_ctime = dir->i_mtime = current_time(inode);
 	inc_nlink(inode);
 	ihold(inode);
fs/minix/namei.c +4 -1
index 1a6084d2b02e..6dc1c4fd4dd6 100644
@@ -99,10 +99,13 @@ static int minix_symlink(struct inode * dir, struct dentry *dentry,
 }
 
 static int minix_link(struct dentry * old_dentry, struct inode * dir,
-	struct dentry *dentry)
+		      struct dentry *dentry, int flags)
 {
 	struct inode *inode = d_inode(old_dentry);
 
+	if (flags)
+		return -EINVAL;
+
 	inode->i_ctime = current_time(inode);
 	inode_inc_link_count(inode);
 	ihold(inode);
fs/namei.c +1 -1
index 4fb61e0754ed..9d690df17aed 100644
@@ -4181,7 +4181,7 @@ int vfs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_de
 	else {
 		error = try_break_deleg(inode, delegated_inode);
 		if (!error)
-			error = dir->i_op->link(old_dentry, dir, new_dentry);
+			error = dir->i_op->link(old_dentry, dir, new_dentry, 0);
 	}
 
 	if (!error && (inode->i_state & I_LINKABLE)) {
fs/nfs/dir.c +5 -1
index e180033e35cf..2497d0287043 100644
@@ -1991,11 +1991,15 @@ int nfs_symlink(struct inode *dir, struct dentry *dentry, const char *symname)
 EXPORT_SYMBOL_GPL(nfs_symlink);
 
 int
-nfs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *dentry)
+nfs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *dentry,
+	 int flags)
 {
 	struct inode *inode = d_inode(old_dentry);
 	int error;
 
+	if (flags)
+		return -EINVAL;
+
 	dfprintk(VFS, "NFS: link(%pd2 -> %pd2)\n",
 		old_dentry, dentry);
 
fs/nfs/internal.h +1 -1
index 24a65da58aa9..4f446465ed7b 100644
@@ -356,7 +356,7 @@ int nfs_mkdir(struct inode *, struct dentry *, umode_t);
 int nfs_rmdir(struct inode *, struct dentry *);
 int nfs_unlink(struct inode *, struct dentry *);
 int nfs_symlink(struct inode *, struct dentry *, const char *);
-int nfs_link(struct dentry *, struct inode *, struct dentry *);
+int nfs_link(struct dentry *, struct inode *, struct dentry *, int);
 int nfs_mknod(struct inode *, struct dentry *, umode_t, dev_t);
 int nfs_rename(struct inode *, struct dentry *,
 	       struct inode *, struct dentry *, unsigned int);
fs/nilfs2/namei.c +4 -1
index 9fe6d4ab74f0..bcbca3f9d947 100644
@@ -174,12 +174,15 @@ static int nilfs_symlink(struct inode *dir, struct dentry *dentry,
 }
 
 static int nilfs_link(struct dentry *old_dentry, struct inode *dir,
-		      struct dentry *dentry)
+		      struct dentry *dentry, int flags)
 {
 	struct inode *inode = d_inode(old_dentry);
 	struct nilfs_transaction_info ti;
 	int err;
 
+	if (flags)
+		return -EINVAL;
+
 	err = nilfs_transaction_begin(dir->i_sb, &ti, 1);
 	if (err)
 		return err;
fs/ocfs2/namei.c +5 -1
index 8ea51cf27b97..c228e7bcb51b 100644
@@ -672,7 +672,8 @@ static int ocfs2_create(struct inode *dir,
 
 static int ocfs2_link(struct dentry *old_dentry,
 		      struct inode *dir,
-		      struct dentry *dentry)
+		      struct dentry *dentry,
+		      int flags)
 {
 	handle_t *handle;
 	struct inode *inode = d_inode(old_dentry);
@@ -687,6 +688,9 @@ static int ocfs2_link(struct dentry *old_dentry,
 	sigset_t oldset;
 	u64 old_de_ino;
 
+	if (flags)
+		return -EINVAL;
+
 	trace_ocfs2_link((unsigned long long)OCFS2_I(inode)->ip_blkno,
 			 old_dentry->d_name.len, old_dentry->d_name.name,
 			 dentry->d_name.len, dentry->d_name.name);
fs/overlayfs/dir.c +4 -1
index 29abdb1d3b5c..923d1c93f570 100644
@@ -661,11 +661,14 @@ static int ovl_set_link_redirect(struct dentry *dentry)
 }
 
 static int ovl_link(struct dentry *old, struct inode *newdir,
-		    struct dentry *new)
+		    struct dentry *new, int flags)
 {
 	int err;
 	struct inode *inode;
 
+	if (flags)
+		return -EINVAL;
+
 	err = ovl_want_write(old);
 	if (err)
 		goto out;
fs/reiserfs/namei.c +4 -1
index 959a066b7bb0..4ff94931f350 100644
@@ -1195,7 +1195,7 @@ static int reiserfs_symlink(struct inode *parent_dir,
 }
 
 static int reiserfs_link(struct dentry *old_dentry, struct inode *dir,
-			 struct dentry *dentry)
+			 struct dentry *dentry, int flags)
 {
 	int retval;
 	struct inode *inode = d_inode(old_dentry);
@@ -1208,6 +1208,9 @@ static int reiserfs_link(struct dentry *old_dentry, struct inode *dir,
 	    JOURNAL_PER_BALANCE_CNT * 3 +
 	    2 * REISERFS_QUOTA_TRANS_BLOCKS(dir->i_sb);
 
+	if (flags)
+		return -EINVAL;
+
 	retval = dquot_initialize(dir);
 	if (retval)
 		return retval;
fs/sysv/namei.c +5 -2
index ea2414b385ec..a202feb6ad89 100644
@@ -96,11 +96,14 @@ static int sysv_symlink(struct inode * dir, struct dentry * dentry,
 	goto out;
 }
 
-static int sysv_link(struct dentry * old_dentry, struct inode * dir, 
-	struct dentry * dentry)
+static int sysv_link(struct dentry *old_dentry, struct inode *dir,
+		     struct dentry *dentry, int flags)
 {
 	struct inode *inode = d_inode(old_dentry);
 
+	if (flags)
+		return -EINVAL;
+
 	inode->i_ctime = current_time(inode);
 	inode_inc_link_count(inode);
 	ihold(inode);
fs/ubifs/dir.c +4 -1
index 0b98e3c8b461..1fc180266338 100644
@@ -683,7 +683,7 @@ static void unlock_2_inodes(struct inode *inode1, struct inode *inode2)
 }
 
 static int ubifs_link(struct dentry *old_dentry, struct inode *dir,
-		      struct dentry *dentry)
+		      struct dentry *dentry, int flags)
 {
 	struct ubifs_info *c = dir->i_sb->s_fs_info;
 	struct inode *inode = d_inode(old_dentry);
@@ -694,6 +694,9 @@ static int ubifs_link(struct dentry *old_dentry, struct inode *dir,
 				.dirtied_ino_d = ALIGN(ui->data_len, 8) };
 	struct fscrypt_name nm;
 
+	if (flags)
+		return -EINVAL;
+
 	/*
 	 * Budget request settings: new direntry, changing the target inode,
 	 * changing the parent inode.
fs/udf/namei.c +4 -1
index 77b6d89b9bcd..263c9daf6aad 100644
@@ -1028,13 +1028,16 @@ static int udf_symlink(struct inode *dir, struct dentry *dentry,
 }
 
 static int udf_link(struct dentry *old_dentry, struct inode *dir,
-		    struct dentry *dentry)
+		    struct dentry *dentry, int flags)
 {
 	struct inode *inode = d_inode(old_dentry);
 	struct udf_fileident_bh fibh;
 	struct fileIdentDesc cfi, *fi;
 	int err;
 
+	if (flags)
+		return -EINVAL;
+
 	fi = udf_add_entry(dir, dentry, &fibh, &cfi, &err);
 	if (!fi) {
 		return err;
fs/ufs/namei.c +4 -1
index 9ef40f100415..7f8d2e4eac8c 100644
@@ -146,11 +146,14 @@ static int ufs_symlink (struct inode * dir, struct dentry * dentry,
 }
 
 static int ufs_link (struct dentry * old_dentry, struct inode * dir,
-	struct dentry *dentry)
+		     struct dentry *dentry, int flags)
 {
 	struct inode *inode = d_inode(old_dentry);
 	int error;
 
+	if (flags)
+		return -EINVAL;
+
 	inode->i_ctime = current_time(inode);
 	inode_inc_link_count(inode);
 	ihold(inode);
fs/xfs/xfs_iops.c +5 -1
index 8afe69ca188b..62ecb726fa41 100644
@@ -311,12 +311,16 @@ STATIC int
 xfs_vn_link(
 	struct dentry	*old_dentry,
 	struct inode	*dir,
-	struct dentry	*dentry)
+	struct dentry	*dentry,
+	int	flags)
 {
 	struct inode	*inode = d_inode(old_dentry);
 	struct xfs_name	name;
 	int		error;
 
+	if (flags)
+		return -EINVAL;
+
 	error = xfs_dentry_mode_to_name(&name, dentry, inode->i_mode);
 	if (unlikely(error))
 		return error;
include/linux/fs.h +2 -2
index 98e0349adb52..3bdb71c97e8f 100644
@@ -1869,7 +1869,7 @@ struct inode_operations {
 	int (*readlink) (struct dentry *, char __user *,int);
 
 	int (*create) (struct inode *,struct dentry *, umode_t, bool);
-	int (*link) (struct dentry *,struct inode *,struct dentry *);
+	int (*link) (struct dentry *,struct inode *,struct dentry *, int);
 	int (*unlink) (struct inode *,struct dentry *);
 	int (*symlink) (struct inode *,struct dentry *,const char *);
 	int (*mkdir) (struct inode *,struct dentry *,umode_t);
@@ -3298,7 +3298,7 @@ extern int simple_setattr(struct dentry *, struct iattr *);
 extern int simple_getattr(const struct path *, struct kstat *, u32, unsigned int);
 extern int simple_statfs(struct dentry *, struct kstatfs *);
 extern int simple_open(struct inode *inode, struct file *file);
-extern int simple_link(struct dentry *, struct inode *, struct dentry *);
+extern int simple_link(struct dentry *, struct inode *, struct dentry *, int);
 extern int simple_unlink(struct inode *, struct dentry *);
 extern int simple_rmdir(struct inode *, struct dentry *);
 extern int simple_rename(struct inode *, struct dentry *,
mm/shmem.c +5 -1
index 8793e8cc1a48..ff7c976e19b9 100644
@@ -2948,11 +2948,15 @@ static int shmem_create(struct inode *dir, struct dentry *dentry, umode_t mode,
 /*
  * Link a file..
  */
-static int shmem_link(struct dentry *old_dentry, struct inode *dir, struct dentry *dentry)
+static int shmem_link(struct dentry *old_dentry, struct inode *dir,
+		      struct dentry *dentry, int flags)
 {
 	struct inode *inode = d_inode(old_dentry);
 	int ret = 0;
 
+	if (flags)
+		return -EINVAL;
+
 	/*
 	 * No ordinary (disk based) filesystem counts links as inodes;
 	 * but each new link needs a new dentry, pinning lowmem, and
Signature · 1 line
2.25.0
#12 lore raw

RFC PATCH v4 4/4 Btrfs: add support for linkat() AT_REPLACE

In reply to Omar Sandoval

From: Omar Sandoval <[email protected]>

The implementation is fairly straightforward and looks a lot like btrfs_rename(). The only tricky bit is that instead of playing games with the dcache, we simply drop the dentry for it to be instantiated on the next lookup. This can be improved in the future.

---
 fs/btrfs/inode.c | 63 +++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 59 insertions(+), 4 deletions(-)
fs/btrfs/inode.c +59 -4
index 8c9a114f48f6..b489671d1b5d 100644
@@ -6762,14 +6762,16 @@ static int btrfs_link(struct dentry *old_dentry, struct inode *dir,
 		      struct dentry *dentry, int flags)
 {
 	struct btrfs_trans_handle *trans = NULL;
+	unsigned int trans_num_items;
 	struct btrfs_root *root = BTRFS_I(dir)->root;
 	struct inode *inode = d_inode(old_dentry);
+	struct inode *new_inode = d_inode(dentry);
 	struct btrfs_fs_info *fs_info = btrfs_sb(inode->i_sb);
 	u64 index;
 	int err;
 	int drop_inode = 0;
 
-	if (flags)
+	if (flags & ~AT_LINK_REPLACE)
 		return -EINVAL;
 
 	/* do not allow sys_link's with other subvols of the same device */
@@ -6779,17 +6781,50 @@ static int btrfs_link(struct dentry *old_dentry, struct inode *dir,
 	if (inode->i_nlink >= BTRFS_LINK_MAX)
 		return -EMLINK;
 
+	/* check for collisions, even if the name isn't there */
+	err = btrfs_check_dir_item_collision(root, dir->i_ino,
+					     dentry->d_name.name,
+					     dentry->d_name.len);
+	if (err) {
+		if (err == -EEXIST) {
+			if (WARN_ON(!new_inode))
+				return err;
+		} else {
+			return err;
+		}
+	}
+
+	/*
+	 * we're using link to replace one file with another. Start IO on it now
+	 * so we don't add too much work to the end of the transaction
+	 */
+	if (new_inode && S_ISREG(inode->i_mode) && new_inode->i_size)
+		filemap_flush(inode->i_mapping);
+
 	err = btrfs_set_inode_index(BTRFS_I(dir), &index);
 	if (err)
 		goto fail;
 
 	/*
+	 * For the source:
 	 * 2 items for inode and inode ref
 	 * 2 items for dir items
 	 * 1 item for parent inode
 	 * 1 item for orphan item deletion if O_TMPFILE
+	 *
+	 * For the target:
+	 * 1 for the possible orphan item
+	 * 1 for the dir item
+	 * 1 for the dir index
+	 * 1 for the inode ref
+	 * 1 for the inode
 	 */
-	trans = btrfs_start_transaction(root, inode->i_nlink ? 5 : 6);
+	trans_num_items = 5;
+	if (!inode->i_nlink)
+		trans_num_items++;
+	if (new_inode)
+		trans_num_items += 5;
+	trans = btrfs_start_transaction(root, trans_num_items);
 	if (IS_ERR(trans)) {
 		err = PTR_ERR(trans);
 		trans = NULL;
@@ -6801,6 +6836,22 @@ static int btrfs_link(struct dentry *old_dentry, struct inode *dir,
 	inc_nlink(inode);
 	inode_inc_iversion(inode);
 	inode->i_ctime = current_time(inode);
+
+	if (new_inode) {
+		inode_inc_iversion(new_inode);
+		new_inode->i_ctime = current_time(new_inode);
+		err = btrfs_unlink_inode(trans, root, BTRFS_I(dir),
+					 BTRFS_I(new_inode),
+					 dentry->d_name.name,
+					 dentry->d_name.len);
+		if (!err && new_inode->i_nlink == 0)
+			err = btrfs_orphan_add(trans, BTRFS_I(new_inode));
+		if (err) {
+			btrfs_abort_transaction(trans, err);
+			goto fail;
+		}
+	}
+
 	set_bit(BTRFS_INODE_COPY_EVERYTHING, &BTRFS_I(inode)->runtime_flags);
 
 	err = btrfs_add_nondir(trans, BTRFS_I(dir), dentry, BTRFS_I(inode),
@@ -6824,8 +6875,12 @@ static int btrfs_link(struct dentry *old_dentry, struct inode *dir,
 			if (err)
 				goto fail;
 		}
-		ihold(inode);
-		d_instantiate(dentry, inode);
+		if (new_inode) {
+			d_drop(dentry);
+		} else {
+			ihold(inode);
+			d_instantiate(dentry, inode);
+		}
 		ret = btrfs_log_new_name(trans, BTRFS_I(inode), NULL, parent,
 					 true, NULL);
 		if (ret == BTRFS_NEED_TRANS_COMMIT) {
Signature · 1 line
2.25.0
#3 lore raw

Re: [RFC PATCH xfstests] generic: add smoke test for AT_LINK_REPLACE

In reply to Omar Sandoval
Quoted 4 lines · Omar Sandoval

From: Omar Sandoval <[email protected]>

---

I think you'd better to send this patch to/cc [email protected] to get review or merge.

Thanks,
Zorro

Quoted 148 lines
 common/rc             |  2 +-
 tests/generic/593     | 97 +++++++++++++++++++++++++++++++++++++++++++
 tests/generic/593.out |  6 +++
 tests/generic/group   |  1 +
 4 files changed, 105 insertions(+), 1 deletion(-)
 create mode 100755 tests/generic/593
 create mode 100644 tests/generic/593.out
common/rc +1 -1
index eeac1355..257f65a1 100644
@@ -2172,7 +2172,7 @@ _require_xfs_io_command()
 		;;
 	"flink")
 		local testlink=$TEST_DIR/$$.link.xfs_io
-		testio=`$XFS_IO_PROG -F -f -c "flink $testlink" $testfile 2>&1`
+		testio=`$XFS_IO_PROG -F -f -c "flink $param $testlink" $testfile 2>&1`
 		rm -f $testlink > /dev/null 2>&1
 		;;
 	"-T")
tests/generic/593 new file +97 -0
new file mode 100755
index 00000000..8a9fee02
@@ -0,0 +1,97 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2020 Facebook.  All Rights Reserved.
+#
+# FS QA Test 593
+#
+# Smoke test linkat() with AT_LINK_REPLACE.
+#
+seq=`basename $0`
+seqres=$RESULT_DIR/$seq
+echo "QA output created by $seq"
+
+here=`pwd`
+tmp=/tmp/$$
+status=1	# failure is the default!
+trap "_cleanup; exit \$status" 0 1 2 3 15
+
+_cleanup()
+{
+	cd /
+	rm -f $tmp.*
+}
+
+# get standard environment, filters and checks
+. ./common/rc
+. ./common/filter
+
+# remove previous $seqres.full before test
+rm -f $seqres.full
+
+_supported_fs generic
+_supported_os Linux
+_require_test
+_require_xfs_io_command "-T"
+_require_xfs_io_command "flink" "-f"
+
+same_file() {
+	[[ "$(stat -c '%d %i' "$1")" = "$(stat -c '%d %i' "$2")" ]]
+}
+
+touch "$TEST_DIR/$seq.src"
+touch "$TEST_DIR/$seq.tgt"
+$XFS_IO_PROG -c "flink -f $TEST_DIR/$seq.tgt" "$TEST_DIR/$seq.src"
+same_file "$TEST_DIR/$seq.src" "$TEST_DIR/$seq.tgt" ||
+	echo "Target was not replaced"
+
+# Linking to the same file should be a noop.
+$XFS_IO_PROG -c "flink -f $TEST_DIR/$seq.src" "$TEST_DIR/$seq.src"
+$XFS_IO_PROG -c "flink -f $TEST_DIR/$seq.tgt" "$TEST_DIR/$seq.src"
+same_file "$TEST_DIR/$seq.src" "$TEST_DIR/$seq.tgt" || echo "Target changed?"
+
+# Should work with O_TMPFILE.
+$XFS_IO_PROG -c "flink -f $TEST_DIR/$seq.tgt" -T "$TEST_DIR"
+stat -c '%h' "$TEST_DIR/$seq.tgt"
+same_file "$TEST_DIR/$seq.src" "$TEST_DIR/$seq.tgt" &&
+	echo "Target was not replaced"
+
+# It's okay if the target doesn't exist.
+$XFS_IO_PROG -c "flink -f $TEST_DIR/$seq.tgt2" "$TEST_DIR/$seq.src"
+same_file "$TEST_DIR/$seq.src" "$TEST_DIR/$seq.tgt2" ||
+	echo "Target was not created"
+
+# Can't replace directories.
+mkdir "$TEST_DIR/$seq.dir"
+$XFS_IO_PROG -c "flink -f $TEST_DIR/$seq.dir" "$TEST_DIR/$seq.src"
+cd "$TEST_DIR/$seq.dir"
+$XFS_IO_PROG -c "flink -f ." "$TEST_DIR/$seq.src"
+$XFS_IO_PROG -c "flink -f .." "$TEST_DIR/$seq.src"
+cd - &> /dev/null
+
+# Can't replace local mount points.
+touch "$TEST_DIR/$seq.mnt"
+$MOUNT_PROG --bind "$TEST_DIR/$seq.mnt" "$TEST_DIR/$seq.mnt"
+$XFS_IO_PROG -c "flink -f $TEST_DIR/$seq.mnt" "$TEST_DIR/$seq.src"
+
+# Can replace mount points in other namespaces, though.
+unshare -m \
+	bash -c "$UMOUNT_PROG $TEST_DIR/$seq.mnt; $XFS_IO_PROG -c \"flink -f $TEST_DIR/$seq.mnt\" $TEST_DIR/$seq.src"
+if $UMOUNT_PROG "$TEST_DIR/$seq.mnt" &> /dev/null; then
+	echo "Mount point was not detached"
+fi
+same_file "$TEST_DIR/$seq.src" "$TEST_DIR/$seq.mnt" ||
+	echo "Mount point was not replaced"
+
+# Should replace symlinks, not follow them.
+touch "$TEST_DIR/$seq.symtgt"
+ln -s "$TEST_DIR/$seq.symtgt" "$TEST_DIR/$seq.sym"
+$XFS_IO_PROG -c "flink -f $TEST_DIR/$seq.sym" "$TEST_DIR/$seq.src"
+same_file "$TEST_DIR/$seq.src" "$TEST_DIR/$seq.sym" ||
+	echo "Symlink was not replaced"
+same_file "$TEST_DIR/$seq.src" "$TEST_DIR/$seq.symtgt" &&
+	echo "Symlink target was replaced"
+
+rm -rf "$TEST_DIR/$seq."*
+
+status=0
+exit
tests/generic/593.out new file +6 -0
new file mode 100644
index 00000000..834c34bf
@@ -0,0 +1,6 @@
+QA output created by 593
+1
+flink: Is a directory
+flink: Is a directory
+flink: Is a directory
+flink: Device or resource busy
tests/generic/group +1 -0
index 6fe62505..0a87efca 100644
@@ -595,3 +595,4 @@
 590 auto prealloc preallocrw
 591 auto quick rw pipe splice
 592 auto quick encrypt
+593 auto quick hardlink
Signature · 1 line
2.25.0
#14 lore raw

RFC PATCH 1/1 generic: add smoke test for AT_LINK_REPLACE

In reply to a message not in the archive

From: Omar Sandoval <[email protected]>

---
 common/rc             |  2 +-
 tests/generic/593     | 97 +++++++++++++++++++++++++++++++++++++++++++
 tests/generic/593.out |  6 +++
 tests/generic/group   |  1 +
 4 files changed, 105 insertions(+), 1 deletion(-)
 create mode 100755 tests/generic/593
 create mode 100644 tests/generic/593.out
common/rc +1 -1
index eeac1355..257f65a1 100644
@@ -2172,7 +2172,7 @@ _require_xfs_io_command()
 		;;
 	"flink")
 		local testlink=$TEST_DIR/$$.link.xfs_io
-		testio=`$XFS_IO_PROG -F -f -c "flink $testlink" $testfile 2>&1`
+		testio=`$XFS_IO_PROG -F -f -c "flink $param $testlink" $testfile 2>&1`
 		rm -f $testlink > /dev/null 2>&1
 		;;
 	"-T")
tests/generic/593 new file +97 -0

97 lines not on this page · raw message

tests/generic/593.out new file +6 -0
new file mode 100644
index 00000000..834c34bf
@@ -0,0 +1,6 @@
+QA output created by 593
+1
+flink: Is a directory
+flink: Is a directory
+flink: Is a directory
+flink: Device or resource busy
tests/generic/group +1 -0
index 6fe62505..0a87efca 100644
@@ -595,3 +595,4 @@
 590 auto prealloc preallocrw
 591 auto quick rw pipe splice
 592 auto quick encrypt
+593 auto quick hardlink
Signature · 1 line
2.25.0
#22 lore raw

RFC PATCH 1/1 generic: add smoke test for AT_LINK_REPLACE

In reply to Omar Sandoval

From: Omar Sandoval <[email protected]>

---
 common/rc             |  2 +-
 tests/generic/593     | 97 +++++++++++++++++++++++++++++++++++++++++++
 tests/generic/593.out |  6 +++
 tests/generic/group   |  1 +
 4 files changed, 105 insertions(+), 1 deletion(-)
 create mode 100755 tests/generic/593
 create mode 100644 tests/generic/593.out
common/rc +1 -1
index eeac1355..257f65a1 100644
@@ -2172,7 +2172,7 @@ _require_xfs_io_command()
 		;;
 	"flink")
 		local testlink=$TEST_DIR/$$.link.xfs_io
-		testio=`$XFS_IO_PROG -F -f -c "flink $testlink" $testfile 2>&1`
+		testio=`$XFS_IO_PROG -F -f -c "flink $param $testlink" $testfile 2>&1`
 		rm -f $testlink > /dev/null 2>&1
 		;;
 	"-T")
tests/generic/593 new file +97 -0

97 lines not on this page · raw message

tests/generic/593.out new file +6 -0
new file mode 100644
index 00000000..834c34bf
@@ -0,0 +1,6 @@
+QA output created by 593
+1
+flink: Is a directory
+flink: Is a directory
+flink: Is a directory
+flink: Device or resource busy
tests/generic/group +1 -0
index 6fe62505..0a87efca 100644
@@ -595,3 +595,4 @@
 590 auto prealloc preallocrw
 591 auto quick rw pipe splice
 592 auto quick encrypt
+593 auto quick hardlink
Signature · 1 line
2.25.0
#15 lore raw

RFC PATCH 1/1 link.2: Document new AT_LINK_REPLACE flag

In reply to a message not in the archive

From: Omar Sandoval <[email protected]>

---
 man2/link.2 | 191 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 191 insertions(+)
man2/link.2 +191 -0
index 649ba00c7..0097e3071 100644
@@ -174,6 +174,60 @@ like this:
 linkat(AT_FDCWD, "/proc/self/fd/<fd>", newdirfd,
        newname, AT_SYMLINK_FOLLOW);
 .EE
+.TP
+.BR AT_LINK_REPLACE " (since Linux 5.7)"
+If
+.I newpath
+exists, replace it atomically.
+There is no point at which another process attempting to access
+.I newpath
+will find it missing.
+If
+.I newpath
+exists but the operation fails,
+the original entry specified by
+.I newpath
+will remain in place.
+This does not guarantee data integrity;
+see EXAMPLE below for how to use this for crash-safe file replacement with
+.BR O_TMPFILE .
+.IP
+If
+.I newpath
+is replaced,
+any other hard links referring to the original file are unaffected.
+Open file descriptors for
+.I newpath
+are also unaffected.
+.IP
+.I newpath
+must not be a directory.
+.IP
+If the entry specified by
+.I newpath
+refers to the file specified by
+.I oldpath,
+.BR linkat ()
+does nothing and returns a success status.
+Note that this comparison does not follow mounts on
+.IR newpath .
+.IP
+Otherwise,
+.I newpath
+must not be a mount point in the local namespace.
+If it is a mount point in another namespace and the operation succeeds,
+all mounts are detached from
+.I newpath
+in all namespaces, as is the case for
+.BR rename (2),
+.BR rmdir (2),
+and
+.BR unlink (2).
+.IP
+If
+.I newpath
+refers to a symbolic link,
+the link will be replaced.
 .in
 .PP
 Before kernel 2.6.18, the
@@ -293,10 +347,34 @@ or
 .I newdirfd
 is not a valid file descriptor.
 .TP
+.B EBUSY
+.B AT_LINK_REPLACE
+was specified in
+.IR flags ,
+.I newpath
+does not refer to the file specified by
+.IR oldpath ,
+and
+.I newpath
+is in use by the system
+(for example, it is a mount point in the local namespace).
+.TP
 .B EINVAL
 An invalid flag value was specified in
 .IR flags .
 .TP
+.B EINVAL
+The filesystem does not support one of the flags in
+.IR flags .
+.TP
+.B EISDIR
+.B AT_LINK_REPLACE
+was specified in
+.I flags
+and
+.I newpath
+refers to an existing directory.
+.TP
 .B ENOENT
 .B AT_EMPTY_PATH
 was specified in
@@ -344,6 +422,31 @@ was specified in
 is an empty string, and
 .IR olddirfd
 refers to a directory.
+.TP
+.B EPERM
+.B AT_LINK_REPLACE
+was specified in
+.I flags
+and
+.I newpath
+refers to an immutable or append-only file
+or a file in an immutable or append-only directory.
+(See
+.BR ioctl_iflags (2).)
+.TP
+.BR EPERM " or " EACCES
+.B AT_LINK_REPLACE
+was specified in
+.IR flags ,
+the directory containing
+.I newpath
+has the sticky bit
+.RB ( S_ISVTX )
+set, and the process's effective UID is neither the UID of the file to
+be deleted nor that of the directory containing it, and
+the process is not privileged (Linux: does not have the
+.B CAP_FOWNER
+capability).
 .SH VERSIONS
 .BR linkat ()
 was added to Linux in kernel 2.6.16;
@@ -421,6 +524,94 @@ performs the link creation and dies before it can say so.
 Use
 .BR stat (2)
 to find out if the link got created.
+.SH EXAMPLE
+The following program demonstrates the use of
+.BR linkat ()
+with
+.B AT_LINK_REPLACE
+and
+.BR open (2)
+with
+.B O_TMPFILE
+for crash-safe file replacement.
+.SS Example output
+.in +4n
+.EX
+$ \fBecho bar > foo\fP
+$ \fB./replace foo\fP
+$ \fBcat foo\fP
+hello, world
+.EE
+.in
+.SS Program source (replace.c)
+.EX
+#define _GNU_SOURCE
+#include <fcntl.h>
+#include <libgen.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+int
+main(int argc, char *argv[])
+{
+	char *path, *dirc, *basec, *dir, *base;
+	int fd, dirfd;
+
+	if (argc != 2) {
+		fprintf(stderr, "usage: %s PATH\en", argv[0]);
+		exit(EXIT_FAILURE);
+	}
+
+	path = argv[1];
+
+	dirc = strdup(path);
+	basec = strdup(path);
+	if (!dirc || !basec) {
+		perror("strdup");
+		exit(EXIT_FAILURE);
+	}
+	dir = dirname(dirc);
+	base = basename(basec);
+
+	/* Open the parent directory. */
+	dirfd = open(dir, O_DIRECTORY | O_RDONLY);
+	if (dirfd == -1) {
+		perror("open");
+		exit(EXIT_FAILURE);
+	}
+
+	/* Open a temporary file, write data to it, and persist it. */
+	fd = open(dir, O_TMPFILE | O_RDWR, 0644);
+	if (fd == -1) {
+		perror("open");
+		exit(EXIT_FAILURE);
+	}
+	if (write(fd, "hello, world\en", 13) == -1) {
+		perror("write");
+		exit(EXIT_FAILURE);
+	}
+	if (fsync(fd) == -1) {
+		perror("fsync");
+		exit(EXIT_FAILURE);
+	}
+
+	/* Replace the original file and persist the directory. */
+	if (linkat(fd, "", dirfd, base, AT_EMPTY_PATH | AT_LINK_REPLACE) == -1) {
+		perror("linkat");
+		exit(EXIT_FAILURE);
+	}
+	if (fsync(dirfd) == -1) {
+		perror("fsync");
+		exit(EXIT_FAILURE);
+	}
+
+	exit(EXIT_SUCCESS);
+}
+.EE
 .SH SEE ALSO
 .BR ln (1),
 .BR open (2),
Signature · 1 line
2.25.0
#16 lore raw

RFC PATCH 1/1 xfs_io: add support for linkat() AT_LINK_REPLACE

In reply to a message not in the archive

From: Omar Sandoval <[email protected]>

---
 io/link.c         | 24 ++++++++++++++++++++----
 man/man8/xfs_io.8 |  9 ++++++++-
 2 files changed, 28 insertions(+), 5 deletions(-)
io/link.c +20 -4
index f4f4b139..3fc3e24d 100644
@@ -12,6 +12,9 @@
 #ifndef AT_EMPTY_PATH
 #define AT_EMPTY_PATH	0x1000
 #endif
+#ifndef AT_LINK_REPLACE
+#define AT_LINK_REPLACE	0x10000
+#endif
 
 static cmdinfo_t flink_cmd;
 
@@ -22,6 +25,7 @@ flink_help(void)
 "\n"
 "link the open file descriptor to the supplied filename\n"
 "\n"
+" -f -- overwrite the target filename if it exists (AT_LINK_REPLACE)\n"
 "\n"));
 }
 
@@ -30,10 +34,22 @@ flink_f(
 	int		argc,
 	char		**argv)
 {
-	if (argc != 2)
+	int		flags = AT_EMPTY_PATH;
+	int		c;
+
+	while ((c = getopt(argc, argv, "f")) != EOF) {
+		switch (c) {
+		case 'f':
+			flags |= AT_LINK_REPLACE;
+			break;
+		default:
+			return command_usage(&flink_cmd);
+		}
+	}
+	if (optind != argc - 1)
 		return command_usage(&flink_cmd);
 
-	if (linkat(file->fd, "", AT_FDCWD, argv[1], AT_EMPTY_PATH) < 0) {
+	if (linkat(file->fd, "", AT_FDCWD, argv[optind], flags) < 0) {
 		perror("flink");
 		return 0;
 	}
@@ -46,9 +62,9 @@ flink_init(void)
 	flink_cmd.name = "flink";
 	flink_cmd.cfunc = flink_f;
 	flink_cmd.argmin = 1;
-	flink_cmd.argmax = 1;
+	flink_cmd.argmax = -1;
 	flink_cmd.flags = CMD_NOMAP_OK | CMD_FOREIGN_OK | CMD_FLAG_ONESHOT;
-	flink_cmd.args = _("filename");
+	flink_cmd.args = _("[-f] filename");
 	flink_cmd.oneline =
 		_("link the open file descriptor to the supplied filename");
 	flink_cmd.help = flink_help;
man/man8/xfs_io.8 +8 -1
index c69b295d..f79b3a59 100644
@@ -807,8 +807,15 @@ for the full list) is available via the
 .B help
 command.
 .TP
-.BI "flink " path
+.BI "flink [ \-f ]" " path"
 Link the currently open file descriptor into the filesystem namespace.
+.RS 1.0i
+.PD 0
+.TP 0.4i
+.B \-f
+overwrite the target path if it exists (AT_LINK_REPLACE).
+.PD
+.RE
 .TP
 .BR stat " [ " \-v "|" \-r " ]"
 Selected statistics from
Signature · 1 line
2.25.0
#17 lore raw

v4 cover fs: add flag to linkat() for replacing destination

In reply to a message not in the archive

From: Omar Sandoval <[email protected]>

[Sorry if you got this twice, I botched the Cc's on the previous
submission.]

Hello,

This series adds an AT_LINK_REPLACE flag to linkat() which allows atomically replacing the destination if it exists. This is a respin of an old series [1] that I was convinced to resend due to some recent interest [2][3].

Patch 1 adds a flags argument to i_ops->link() in preparation. Patch 2 adds the VFS support. Patch 3 fixes an inode leak in btrfs_link(), included in this series because it conflicts with patch 4. Patch 4 adds support for AT_LINK_REPLACE to Btrfs.

I've also included a man-page patch (with an example program), an xfs_io patch, and an fstest.

Some outstanding issues:

- The Btrfs implementation does a d_drop() on the replaced dentry. We probably want a d_replace() helper for filesystems to use.
- Should AT_LINK_REPLACE be limited to O_TMPFILE? In my opinion, the answer is no. After all, ln -f is not that exotic.
- Should AT_LINK_REPLACE guarantee data integrity? Again, I think the answer is no. That's more suited to something like Amir's AT_ATOMIC proposal [4].

Changes since v3:

- Rebased on v5.5.
- Added patches 1 and 3.
- Incorporated Al's feedback on various error cases in patch 2.
- Renamed the flag to AT_LINK_REPLACE.

Thanks!

1: https://lore.kernel.org/linux-fsdevel/[email protected]/
2: https://lore.kernel.org/linux-fsdevel/3326.1579019665-S6HVgzuS8uM4Awkfq6JHfwNdhmdF6hFW@public.gmane.org/
3: https://lore.kernel.org/linux-fsdevel/364531.1579265357-S6HVgzuS8uM4Awkfq6JHfwNdhmdF6hFW@public.gmane.org/
4: https://lore.kernel.org/linux-fsdevel/20190527172655.9287-1-amir73il-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org/

Omar Sandoval (4):
fs: add flags argument to i_op->link()
fs: add AT_LINK_REPLACE flag for linkat() which replaces the target
Btrfs: fix inode reference count leak in btrfs_link() error path
Btrfs: add support for linkat() AT_REPLACE

 fs/9p/vfs_inode.c          |   5 +-
 fs/9p/vfs_inode_dotl.c     |   5 +-
 fs/affs/affs.h             |   2 +-
 fs/affs/namei.c            |   6 +-
 fs/afs/dir.c               |   7 +-
 fs/bad_inode.c             |   2 +-
 fs/bfs/dir.c               |   7 +-
 fs/btrfs/inode.c           |  70 ++++++++++++++--
 fs/ceph/dir.c              |   5 +-
 fs/cifs/cifsfs.h           |   2 +-
 fs/cifs/link.c             |   5 +-
 fs/coda/dir.c              |   5 +-
 fs/ecryptfs/inode.c        |   7 +-
 fs/ext2/namei.c            |   5 +-
 fs/ext4/namei.c            |   7 +-
 fs/f2fs/namei.c            |   5 +-
 fs/fuse/dir.c              |   5 +-
 fs/gfs2/inode.c            |   5 +-
 fs/hfsplus/dir.c           |   5 +-
 fs/hostfs/hostfs_kern.c    |   5 +-
 fs/jffs2/dir.c             |   8 +-
 fs/jfs/namei.c             |   7 +-
 fs/libfs.c                 |   6 +-
 fs/minix/namei.c           |   5 +-
 fs/namei.c                 | 166 +++++++++++++++++++++++++++++--------
 fs/nfs/dir.c               |   6 +-
 fs/nfs/internal.h          |   2 +-
 fs/nfsd/vfs.c              |   2 +-
 fs/nilfs2/namei.c          |   5 +-
 fs/ocfs2/namei.c           |   6 +-
 fs/overlayfs/dir.c         |   5 +-
 fs/overlayfs/overlayfs.h   |   2 +-
 fs/reiserfs/namei.c        |   5 +-
 fs/sysv/namei.c            |   7 +-
 fs/ubifs/dir.c             |   5 +-
 fs/udf/namei.c             |   5 +-
 fs/ufs/namei.c             |   5 +-
 fs/xfs/xfs_iops.c          |   6 +-
 include/linux/fs.h         |   6 +-
 include/uapi/linux/fcntl.h |   1 +
 mm/shmem.c                 |   6 +-
 41 files changed, 341 insertions(+), 90 deletions(-)
Signature · 1 line
2.25.0
#18 lore raw

RFC PATCH v4 1/4 fs: add flags argument to i_op->link()

In reply to a message not in the archive

From: Omar Sandoval <[email protected]>

In preparation for adding the AT_LINK_REPLACE flag, make ->link() take and check a flags argument.

---
 fs/9p/vfs_inode.c       | 5 ++++-
 fs/9p/vfs_inode_dotl.c  | 5 ++++-
 fs/affs/affs.h          | 2 +-
 fs/affs/namei.c         | 6 +++++-
 fs/afs/dir.c            | 7 +++++--
 fs/bad_inode.c          | 2 +-
 fs/bfs/dir.c            | 7 +++++--
 fs/btrfs/inode.c        | 5 ++++-
 fs/ceph/dir.c           | 5 ++++-
 fs/cifs/cifsfs.h        | 2 +-
 fs/cifs/link.c          | 5 ++++-
 fs/coda/dir.c           | 5 ++++-
 fs/ecryptfs/inode.c     | 5 ++++-
 fs/ext2/namei.c         | 5 ++++-
 fs/ext4/namei.c         | 7 +++++--
 fs/f2fs/namei.c         | 5 ++++-
 fs/fuse/dir.c           | 5 ++++-
 fs/gfs2/inode.c         | 5 ++++-
 fs/hfsplus/dir.c        | 5 ++++-
 fs/hostfs/hostfs_kern.c | 5 ++++-
 fs/jffs2/dir.c          | 8 ++++++--
 fs/jfs/namei.c          | 7 +++++--
 fs/libfs.c              | 6 +++++-
 fs/minix/namei.c        | 5 ++++-
 fs/namei.c              | 2 +-
 fs/nfs/dir.c            | 6 +++++-
 fs/nfs/internal.h       | 2 +-
 fs/nilfs2/namei.c       | 5 ++++-
 fs/ocfs2/namei.c        | 6 +++++-
 fs/overlayfs/dir.c      | 5 ++++-
 fs/reiserfs/namei.c     | 5 ++++-
 fs/sysv/namei.c         | 7 +++++--
 fs/ubifs/dir.c          | 5 ++++-
 fs/udf/namei.c          | 5 ++++-
 fs/ufs/namei.c          | 5 ++++-
 fs/xfs/xfs_iops.c       | 6 +++++-
 include/linux/fs.h      | 4 ++--
 mm/shmem.c              | 6 +++++-
 38 files changed, 148 insertions(+), 45 deletions(-)
fs/9p/vfs_inode.c +4 -1
index b82423a72f68..3cda7788122e 100644
@@ -1335,12 +1335,15 @@ v9fs_vfs_symlink(struct inode *dir, struct dentry *dentry, const char *symname)
 
 static int
 v9fs_vfs_link(struct dentry *old_dentry, struct inode *dir,
-	      struct dentry *dentry)
+	      struct dentry *dentry, int flags)
 {
 	int retval;
 	char name[1 + U32_MAX_DIGITS + 2]; /* sign + number + \n + \0 */
 	struct p9_fid *oldfid;
 
+	if (flags)
+		return -EINVAL;
+
 	p9_debug(P9_DEBUG_VFS, " %lu,%pd,%pd\n",
 		 dir->i_ino, dentry, old_dentry);
 
fs/9p/vfs_inode_dotl.c +4 -1
index 60328b21c5fb..636796582f68 100644
@@ -749,12 +749,15 @@ v9fs_vfs_symlink_dotl(struct inode *dir, struct dentry *dentry,
 
 static int
 v9fs_vfs_link_dotl(struct dentry *old_dentry, struct inode *dir,
-		struct dentry *dentry)
+		   struct dentry *dentry, int flags)
 {
 	int err;
 	struct p9_fid *dfid, *oldfid;
 	struct v9fs_session_info *v9ses;
 
+	if (flags)
+		return -EINVAL;
+
 	p9_debug(P9_DEBUG_VFS, "dir ino: %lu, old_name: %pd, new_name: %pd\n",
 		 dir->i_ino, old_dentry, dentry);
 
fs/affs/affs.h +1 -1
index a755bef7c4c7..98d70fd7ea47 100644
@@ -171,7 +171,7 @@ extern int	affs_create(struct inode *dir, struct dentry *dentry, umode_t mode, b
 extern int	affs_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode);
 extern int	affs_rmdir(struct inode *dir, struct dentry *dentry);
 extern int	affs_link(struct dentry *olddentry, struct inode *dir,
-			  struct dentry *dentry);
+			  struct dentry *dentry, int flags);
 extern int	affs_symlink(struct inode *dir, struct dentry *dentry,
 			     const char *symname);
 extern int	affs_rename2(struct inode *old_dir, struct dentry *old_dentry,
fs/affs/namei.c +5 -1
index 41c5749f4db7..198f2878145d 100644
@@ -388,10 +388,14 @@ affs_symlink(struct inode *dir, struct dentry *dentry, const char *symname)
 }
 
 int
-affs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *dentry)
+affs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *dentry,
+	  int flags)
 {
 	struct inode *inode = d_inode(old_dentry);
 
+	if (flags)
+		return -EINVAL;
+
 	pr_debug("%s(%lu, %lu, \"%pd\")\n", __func__, inode->i_ino, dir->i_ino,
 		 dentry);
 
fs/afs/dir.c +5 -2
index 5c794f4b051a..0a17d8d8c009 100644
@@ -34,7 +34,7 @@ static int afs_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode);
 static int afs_rmdir(struct inode *dir, struct dentry *dentry);
 static int afs_unlink(struct inode *dir, struct dentry *dentry);
 static int afs_link(struct dentry *from, struct inode *dir,
-		    struct dentry *dentry);
+		    struct dentry *dentry, int flags);
 static int afs_symlink(struct inode *dir, struct dentry *dentry,
 		       const char *content);
 static int afs_rename(struct inode *old_dir, struct dentry *old_dentry,
@@ -1641,7 +1641,7 @@ static int afs_create(struct inode *dir, struct dentry *dentry, umode_t mode,
  * create a hard link between files in an AFS filesystem
  */
 static int afs_link(struct dentry *from, struct inode *dir,
-		    struct dentry *dentry)
+		    struct dentry *dentry, int flags)
 {
 	struct afs_fs_cursor fc;
 	struct afs_status_cb *scb;
@@ -1650,6 +1650,9 @@ static int afs_link(struct dentry *from, struct inode *dir,
 	struct key *key;
 	int ret;
 
+	if (flags)
+		return -EINVAL;
+
 	_enter("{%llx:%llu},{%llx:%llu},{%pd}",
 	       vnode->fid.vid, vnode->fid.vnode,
 	       dvnode->fid.vid, dvnode->fid.vnode,
fs/bad_inode.c +1 -1
index 8035d2a44561..41caa98597d6 100644
@@ -39,7 +39,7 @@ static struct dentry *bad_inode_lookup(struct inode *dir,
 }
 
 static int bad_inode_link (struct dentry *old_dentry, struct inode *dir,
-		struct dentry *dentry)
+			   struct dentry *dentry, int flags)
 {
 	return -EIO;
 }
fs/bfs/dir.c +5 -2
index d8dfe3a0cb39..0b0ef8a59a73 100644
@@ -144,13 +144,16 @@ static struct dentry *bfs_lookup(struct inode *dir, struct dentry *dentry,
 	return d_splice_alias(inode, dentry);
 }
 
-static int bfs_link(struct dentry *old, struct inode *dir,
-						struct dentry *new)
+static int bfs_link(struct dentry *old, struct inode *dir, struct dentry *new,
+		    int flags)
 {
 	struct inode *inode = d_inode(old);
 	struct bfs_sb_info *info = BFS_SB(inode->i_sb);
 	int err;
 
+	if (flags)
+		return -EINVAL;
+
 	mutex_lock(&info->bfs_lock);
 	err = bfs_add_entry(dir, &new->d_name, inode->i_ino);
 	if (err) {
fs/btrfs/inode.c +4 -1
index c70baafb2a39..bc7709c4f6eb 100644
@@ -6759,7 +6759,7 @@ static int btrfs_create(struct inode *dir, struct dentry *dentry,
 }
 
 static int btrfs_link(struct dentry *old_dentry, struct inode *dir,
-		      struct dentry *dentry)
+		      struct dentry *dentry, int flags)
 {
 	struct btrfs_trans_handle *trans = NULL;
 	struct btrfs_root *root = BTRFS_I(dir)->root;
@@ -6769,6 +6769,9 @@ static int btrfs_link(struct dentry *old_dentry, struct inode *dir,
 	int err;
 	int drop_inode = 0;
 
+	if (flags)
+		return -EINVAL;
+
 	/* do not allow sys_link's with other subvols of the same device */
 	if (root->root_key.objectid != BTRFS_I(inode)->root->root_key.objectid)
 		return -EXDEV;
fs/ceph/dir.c +4 -1
index 2e4764fd1872..a1517be5405c 100644
@@ -999,13 +999,16 @@ static int ceph_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode)
 }
 
 static int ceph_link(struct dentry *old_dentry, struct inode *dir,
-		     struct dentry *dentry)
+		     struct dentry *dentry, int flags)
 {
 	struct ceph_fs_client *fsc = ceph_sb_to_client(dir->i_sb);
 	struct ceph_mds_client *mdsc = fsc->mdsc;
 	struct ceph_mds_request *req;
 	int err;
 
+	if (flags)
+		return -EINVAL;
+
 	if (ceph_snap(dir) != CEPH_NOSNAP)
 		return -EROFS;
 
fs/cifs/cifsfs.h +1 -1
index b59dc7478130..9360d28255ef 100644
@@ -69,7 +69,7 @@ extern int cifs_atomic_open(struct inode *, struct dentry *,
 extern struct dentry *cifs_lookup(struct inode *, struct dentry *,
 				  unsigned int);
 extern int cifs_unlink(struct inode *dir, struct dentry *dentry);
-extern int cifs_hardlink(struct dentry *, struct inode *, struct dentry *);
+extern int cifs_hardlink(struct dentry *, struct inode *, struct dentry *, int);
 extern int cifs_mknod(struct inode *, struct dentry *, umode_t, dev_t);
 extern int cifs_mkdir(struct inode *, struct dentry *, umode_t);
 extern int cifs_rmdir(struct inode *, struct dentry *);
fs/cifs/link.c +4 -1
index b736acd3917b..1471d4c095d7 100644
@@ -516,7 +516,7 @@ smb3_create_mf_symlink(unsigned int xid, struct cifs_tcon *tcon,
 
 int
 cifs_hardlink(struct dentry *old_file, struct inode *inode,
-	      struct dentry *direntry)
+	      struct dentry *direntry, int flags)
 {
 	int rc = -EACCES;
 	unsigned int xid;
@@ -528,6 +528,9 @@ cifs_hardlink(struct dentry *old_file, struct inode *inode,
 	struct TCP_Server_Info *server;
 	struct cifsInodeInfo *cifsInode;
 
+	if (flags)
+		return -EINVAL;
+
 	tlink = cifs_sb_tlink(cifs_sb);
 	if (IS_ERR(tlink))
 		return PTR_ERR(tlink);
fs/coda/dir.c +4 -1
index ca40c2556ba6..acdbc47948cf 100644
@@ -200,13 +200,16 @@ static int coda_mkdir(struct inode *dir, struct dentry *de, umode_t mode)
 
 /* try to make de an entry in dir_inodde linked to source_de */ 
 static int coda_link(struct dentry *source_de, struct inode *dir_inode, 
-	  struct dentry *de)
+		     struct dentry *de, int flags)
 {
 	struct inode *inode = d_inode(source_de);
         const char * name = de->d_name.name;
 	int len = de->d_name.len;
 	int error;
 
+	if (flags)
+		return -EINVAL;
+
 	if (is_root_inode(dir_inode) && coda_iscontrol(name, len))
 		return -EPERM;
 
fs/ecryptfs/inode.c +4 -1
index e23752d9a79f..eeb351b220b2 100644
@@ -422,7 +422,7 @@ static struct dentry *ecryptfs_lookup(struct inode *ecryptfs_dir_inode,
 }
 
 static int ecryptfs_link(struct dentry *old_dentry, struct inode *dir,
-			 struct dentry *new_dentry)
+			 struct dentry *new_dentry, int flags)
 {
 	struct dentry *lower_old_dentry;
 	struct dentry *lower_new_dentry;
@@ -430,6 +430,9 @@ static int ecryptfs_link(struct dentry *old_dentry, struct inode *dir,
 	u64 file_size_save;
 	int rc;
 
+	if (flags)
+		return -EINVAL;
+
 	file_size_save = i_size_read(d_inode(old_dentry));
 	lower_old_dentry = ecryptfs_dentry_to_lower(old_dentry);
 	lower_new_dentry = ecryptfs_dentry_to_lower(new_dentry);
fs/ext2/namei.c +4 -1
index ccfbbf59e2fc..9417a96f6ea8 100644
@@ -196,11 +196,14 @@ static int ext2_symlink (struct inode * dir, struct dentry * dentry,
 }
 
 static int ext2_link (struct dentry * old_dentry, struct inode * dir,
-	struct dentry *dentry)
+		      struct dentry *dentry, int flags)
 {
 	struct inode *inode = d_inode(old_dentry);
 	int err;
 
+	if (flags)
+		return -EINVAL;
+
 	err = dquot_initialize(dir);
 	if (err)
 		return err;
fs/ext4/namei.c +5 -2
index 1cb42d940784..80e27fd907d0 100644
@@ -3366,13 +3366,16 @@ static int ext4_symlink(struct inode *dir,
 	return err;
 }
 
-static int ext4_link(struct dentry *old_dentry,
-		     struct inode *dir, struct dentry *dentry)
+static int ext4_link(struct dentry *old_dentry, struct inode *dir,
+		     struct dentry *dentry, int flags)
 {
 	handle_t *handle;
 	struct inode *inode = d_inode(old_dentry);
 	int err, retries = 0;
 
+	if (flags)
+		return -EINVAL;
+
 	if (inode->i_nlink >= EXT4_LINK_MAX)
 		return -EMLINK;
 
fs/f2fs/namei.c +4 -1
index a1c507b0b4ac..f7ca6fbf5aaf 100644
@@ -312,12 +312,15 @@ static int f2fs_create(struct inode *dir, struct dentry *dentry, umode_t mode,
 }
 
 static int f2fs_link(struct dentry *old_dentry, struct inode *dir,
-		struct dentry *dentry)
+		     struct dentry *dentry, int flags)
 {
 	struct inode *inode = d_inode(old_dentry);
 	struct f2fs_sb_info *sbi = F2FS_I_SB(dir);
 	int err;
 
+	if (flags)
+		return -EINVAL;
+
 	if (unlikely(f2fs_cp_error(sbi)))
 		return -EIO;
 	if (!f2fs_is_checkpoint_ready(sbi))
fs/fuse/dir.c +4 -1
index ee190119f45c..ae0cf00fd9ec 100644
@@ -842,7 +842,7 @@ static int fuse_rename2(struct inode *olddir, struct dentry *oldent,
 }
 
 static int fuse_link(struct dentry *entry, struct inode *newdir,
-		     struct dentry *newent)
+		     struct dentry *newent, int flags)
 {
 	int err;
 	struct fuse_link_in inarg;
@@ -850,6 +850,9 @@ static int fuse_link(struct dentry *entry, struct inode *newdir,
 	struct fuse_conn *fc = get_fuse_conn(inode);
 	FUSE_ARGS(args);
 
+	if (flags)
+		return -EINVAL;
+
 	memset(&inarg, 0, sizeof(inarg));
 	inarg.oldnodeid = get_node_id(inode);
 	args.opcode = FUSE_LINK;
fs/gfs2/inode.c +4 -1
index dafef10b91f1..f2f97321b659 100644
@@ -891,7 +891,7 @@ static struct dentry *gfs2_lookup(struct inode *dir, struct dentry *dentry,
  */
 
 static int gfs2_link(struct dentry *old_dentry, struct inode *dir,
-		     struct dentry *dentry)
+		     struct dentry *dentry, int flags)
 {
 	struct gfs2_inode *dip = GFS2_I(dir);
 	struct gfs2_sbd *sdp = GFS2_SB(dir);
@@ -902,6 +902,9 @@ static int gfs2_link(struct dentry *old_dentry, struct inode *dir,
 	struct gfs2_diradd da = { .bh = NULL, .save_loc = 1, };
 	int error;
 
+	if (flags)
+		return -EINVAL;
+
 	if (S_ISDIR(inode->i_mode))
 		return -EPERM;
 
fs/hfsplus/dir.c +4 -1
index 29a9dcfbe81f..16cf85e8e51d 100644
@@ -298,7 +298,7 @@ static int hfsplus_dir_release(struct inode *inode, struct file *file)
 }
 
 static int hfsplus_link(struct dentry *src_dentry, struct inode *dst_dir,
-			struct dentry *dst_dentry)
+			struct dentry *dst_dentry, int flags)
 {
 	struct hfsplus_sb_info *sbi = HFSPLUS_SB(dst_dir->i_sb);
 	struct inode *inode = d_inode(src_dentry);
@@ -308,6 +308,9 @@ static int hfsplus_link(struct dentry *src_dentry, struct inode *dst_dir,
 	u32 cnid, id;
 	int res;
 
+	if (flags)
+		return -EINVAL;
+
 	if (HFSPLUS_IS_RSRC(inode))
 		return -EPERM;
 	if (!S_ISREG(inode->i_mode))
fs/hostfs/hostfs_kern.c +4 -1
index 5a7eb0c79839..f2bd0e42075b 100644
@@ -622,11 +622,14 @@ static struct dentry *hostfs_lookup(struct inode *ino, struct dentry *dentry,
 }
 
 static int hostfs_link(struct dentry *to, struct inode *ino,
-		       struct dentry *from)
+		       struct dentry *from, int flags)
 {
 	char *from_name, *to_name;
 	int err;
 
+	if (flags)
+		return -EOPNOTSUPP;
+
 	if ((from_name = dentry_name(from)) == NULL)
 		return -ENOMEM;
 	to_name = dentry_name(to);
fs/jffs2/dir.c +6 -2
index f20cff1194bb..c4ec9202ca8e 100644
@@ -28,7 +28,7 @@ static int jffs2_create (struct inode *,struct dentry *,umode_t,
 			 bool);
 static struct dentry *jffs2_lookup (struct inode *,struct dentry *,
 				    unsigned int);
-static int jffs2_link (struct dentry *,struct inode *,struct dentry *);
+static int jffs2_link (struct dentry *,struct inode *,struct dentry *, int);
 static int jffs2_unlink (struct inode *,struct dentry *);
 static int jffs2_symlink (struct inode *,struct dentry *,const char *);
 static int jffs2_mkdir (struct inode *,struct dentry *,umode_t);
@@ -240,7 +240,8 @@ static int jffs2_unlink(struct inode *dir_i, struct dentry *dentry)
 /***********************************************************************/
 
 
-static int jffs2_link (struct dentry *old_dentry, struct inode *dir_i, struct dentry *dentry)
+static int jffs2_link (struct dentry *old_dentry, struct inode *dir_i,
+		       struct dentry *dentry, int flags)
 {
 	struct jffs2_sb_info *c = JFFS2_SB_INFO(old_dentry->d_sb);
 	struct jffs2_inode_info *f = JFFS2_INODE_INFO(d_inode(old_dentry));
@@ -249,6 +250,9 @@ static int jffs2_link (struct dentry *old_dentry, struct inode *dir_i, struct de
 	uint8_t type;
 	uint32_t now;
 
+	if (flags)
+		return -EINVAL;
+
 	/* Don't let people make hard links to bad inodes. */
 	if (!f->inocache)
 		return -EIO;
fs/jfs/namei.c +5 -2
index 7a55d14cc1af..45447fca1fdb 100644
@@ -781,8 +781,8 @@ void jfs_free_zero_link(struct inode *ip)
  * EXDEV: target object and new link are on different file systems and
  * implementation does not support links between file systems [XPG4.2].
  */
-static int jfs_link(struct dentry *old_dentry,
-	     struct inode *dir, struct dentry *dentry)
+static int jfs_link(struct dentry *old_dentry, struct inode *dir,
+		    struct dentry *dentry, int flags)
 {
 	int rc;
 	tid_t tid;
@@ -792,6 +792,9 @@ static int jfs_link(struct dentry *old_dentry,
 	struct btstack btstack;
 	struct inode *iplist[2];
 
+	if (flags)
+		return -EINVAL;
+
 	jfs_info("jfs_link: %pd %pd", old_dentry, dentry);
 
 	rc = dquot_initialize(dir);
fs/libfs.c +5 -1
index 1463b038ffc4..1894d6b202f2 100644
@@ -318,10 +318,14 @@ int simple_open(struct inode *inode, struct file *file)
 }
 EXPORT_SYMBOL(simple_open);
 
-int simple_link(struct dentry *old_dentry, struct inode *dir, struct dentry *dentry)
+int simple_link(struct dentry *old_dentry, struct inode *dir,
+		struct dentry *dentry, int flags)
 {
 	struct inode *inode = d_inode(old_dentry);
 
+	if (flags)
+		return -EINVAL;
+
 	inode->i_ctime = dir->i_ctime = dir->i_mtime = current_time(inode);
 	inc_nlink(inode);
 	ihold(inode);
fs/minix/namei.c +4 -1
index 1a6084d2b02e..6dc1c4fd4dd6 100644
@@ -99,10 +99,13 @@ static int minix_symlink(struct inode * dir, struct dentry *dentry,
 }
 
 static int minix_link(struct dentry * old_dentry, struct inode * dir,
-	struct dentry *dentry)
+		      struct dentry *dentry, int flags)
 {
 	struct inode *inode = d_inode(old_dentry);
 
+	if (flags)
+		return -EINVAL;
+
 	inode->i_ctime = current_time(inode);
 	inode_inc_link_count(inode);
 	ihold(inode);
fs/namei.c +1 -1
index 4fb61e0754ed..9d690df17aed 100644
@@ -4181,7 +4181,7 @@ int vfs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_de
 	else {
 		error = try_break_deleg(inode, delegated_inode);
 		if (!error)
-			error = dir->i_op->link(old_dentry, dir, new_dentry);
+			error = dir->i_op->link(old_dentry, dir, new_dentry, 0);
 	}
 
 	if (!error && (inode->i_state & I_LINKABLE)) {
fs/nfs/dir.c +5 -1
index e180033e35cf..2497d0287043 100644
@@ -1991,11 +1991,15 @@ int nfs_symlink(struct inode *dir, struct dentry *dentry, const char *symname)
 EXPORT_SYMBOL_GPL(nfs_symlink);
 
 int
-nfs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *dentry)
+nfs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *dentry,
+	 int flags)
 {
 	struct inode *inode = d_inode(old_dentry);
 	int error;
 
+	if (flags)
+		return -EINVAL;
+
 	dfprintk(VFS, "NFS: link(%pd2 -> %pd2)\n",
 		old_dentry, dentry);
 
fs/nfs/internal.h +1 -1
index 24a65da58aa9..4f446465ed7b 100644
@@ -356,7 +356,7 @@ int nfs_mkdir(struct inode *, struct dentry *, umode_t);
 int nfs_rmdir(struct inode *, struct dentry *);
 int nfs_unlink(struct inode *, struct dentry *);
 int nfs_symlink(struct inode *, struct dentry *, const char *);
-int nfs_link(struct dentry *, struct inode *, struct dentry *);
+int nfs_link(struct dentry *, struct inode *, struct dentry *, int);
 int nfs_mknod(struct inode *, struct dentry *, umode_t, dev_t);
 int nfs_rename(struct inode *, struct dentry *,
 	       struct inode *, struct dentry *, unsigned int);
fs/nilfs2/namei.c +4 -1
index 9fe6d4ab74f0..bcbca3f9d947 100644
@@ -174,12 +174,15 @@ static int nilfs_symlink(struct inode *dir, struct dentry *dentry,
 }
 
 static int nilfs_link(struct dentry *old_dentry, struct inode *dir,
-		      struct dentry *dentry)
+		      struct dentry *dentry, int flags)
 {
 	struct inode *inode = d_inode(old_dentry);
 	struct nilfs_transaction_info ti;
 	int err;
 
+	if (flags)
+		return -EINVAL;
+
 	err = nilfs_transaction_begin(dir->i_sb, &ti, 1);
 	if (err)
 		return err;
fs/ocfs2/namei.c +5 -1
index 8ea51cf27b97..c228e7bcb51b 100644
@@ -672,7 +672,8 @@ static int ocfs2_create(struct inode *dir,
 
 static int ocfs2_link(struct dentry *old_dentry,
 		      struct inode *dir,
-		      struct dentry *dentry)
+		      struct dentry *dentry,
+		      int flags)
 {
 	handle_t *handle;
 	struct inode *inode = d_inode(old_dentry);
@@ -687,6 +688,9 @@ static int ocfs2_link(struct dentry *old_dentry,
 	sigset_t oldset;
 	u64 old_de_ino;
 
+	if (flags)
+		return -EINVAL;
+
 	trace_ocfs2_link((unsigned long long)OCFS2_I(inode)->ip_blkno,
 			 old_dentry->d_name.len, old_dentry->d_name.name,
 			 dentry->d_name.len, dentry->d_name.name);
fs/overlayfs/dir.c +4 -1
index 29abdb1d3b5c..923d1c93f570 100644
@@ -661,11 +661,14 @@ static int ovl_set_link_redirect(struct dentry *dentry)
 }
 
 static int ovl_link(struct dentry *old, struct inode *newdir,
-		    struct dentry *new)
+		    struct dentry *new, int flags)
 {
 	int err;
 	struct inode *inode;
 
+	if (flags)
+		return -EINVAL;
+
 	err = ovl_want_write(old);
 	if (err)
 		goto out;
fs/reiserfs/namei.c +4 -1
index 959a066b7bb0..4ff94931f350 100644
@@ -1195,7 +1195,7 @@ static int reiserfs_symlink(struct inode *parent_dir,
 }
 
 static int reiserfs_link(struct dentry *old_dentry, struct inode *dir,
-			 struct dentry *dentry)
+			 struct dentry *dentry, int flags)
 {
 	int retval;
 	struct inode *inode = d_inode(old_dentry);
@@ -1208,6 +1208,9 @@ static int reiserfs_link(struct dentry *old_dentry, struct inode *dir,
 	    JOURNAL_PER_BALANCE_CNT * 3 +
 	    2 * REISERFS_QUOTA_TRANS_BLOCKS(dir->i_sb);
 
+	if (flags)
+		return -EINVAL;
+
 	retval = dquot_initialize(dir);
 	if (retval)
 		return retval;
fs/sysv/namei.c +5 -2
index ea2414b385ec..a202feb6ad89 100644
@@ -96,11 +96,14 @@ static int sysv_symlink(struct inode * dir, struct dentry * dentry,
 	goto out;
 }
 
-static int sysv_link(struct dentry * old_dentry, struct inode * dir, 
-	struct dentry * dentry)
+static int sysv_link(struct dentry *old_dentry, struct inode *dir,
+		     struct dentry *dentry, int flags)
 {
 	struct inode *inode = d_inode(old_dentry);
 
+	if (flags)
+		return -EINVAL;
+
 	inode->i_ctime = current_time(inode);
 	inode_inc_link_count(inode);
 	ihold(inode);
fs/ubifs/dir.c +4 -1
index 0b98e3c8b461..1fc180266338 100644
@@ -683,7 +683,7 @@ static void unlock_2_inodes(struct inode *inode1, struct inode *inode2)
 }
 
 static int ubifs_link(struct dentry *old_dentry, struct inode *dir,
-		      struct dentry *dentry)
+		      struct dentry *dentry, int flags)
 {
 	struct ubifs_info *c = dir->i_sb->s_fs_info;
 	struct inode *inode = d_inode(old_dentry);
@@ -694,6 +694,9 @@ static int ubifs_link(struct dentry *old_dentry, struct inode *dir,
 				.dirtied_ino_d = ALIGN(ui->data_len, 8) };
 	struct fscrypt_name nm;
 
+	if (flags)
+		return -EINVAL;
+
 	/*
 	 * Budget request settings: new direntry, changing the target inode,
 	 * changing the parent inode.
fs/udf/namei.c +4 -1
index 77b6d89b9bcd..263c9daf6aad 100644
@@ -1028,13 +1028,16 @@ static int udf_symlink(struct inode *dir, struct dentry *dentry,
 }
 
 static int udf_link(struct dentry *old_dentry, struct inode *dir,
-		    struct dentry *dentry)
+		    struct dentry *dentry, int flags)
 {
 	struct inode *inode = d_inode(old_dentry);
 	struct udf_fileident_bh fibh;
 	struct fileIdentDesc cfi, *fi;
 	int err;
 
+	if (flags)
+		return -EINVAL;
+
 	fi = udf_add_entry(dir, dentry, &fibh, &cfi, &err);
 	if (!fi) {
 		return err;
fs/ufs/namei.c +4 -1
index 9ef40f100415..7f8d2e4eac8c 100644
@@ -146,11 +146,14 @@ static int ufs_symlink (struct inode * dir, struct dentry * dentry,
 }
 
 static int ufs_link (struct dentry * old_dentry, struct inode * dir,
-	struct dentry *dentry)
+		     struct dentry *dentry, int flags)
 {
 	struct inode *inode = d_inode(old_dentry);
 	int error;
 
+	if (flags)
+		return -EINVAL;
+
 	inode->i_ctime = current_time(inode);
 	inode_inc_link_count(inode);
 	ihold(inode);
fs/xfs/xfs_iops.c +5 -1
index 8afe69ca188b..62ecb726fa41 100644
@@ -311,12 +311,16 @@ STATIC int
 xfs_vn_link(
 	struct dentry	*old_dentry,
 	struct inode	*dir,
-	struct dentry	*dentry)
+	struct dentry	*dentry,
+	int	flags)
 {
 	struct inode	*inode = d_inode(old_dentry);
 	struct xfs_name	name;
 	int		error;
 
+	if (flags)
+		return -EINVAL;
+
 	error = xfs_dentry_mode_to_name(&name, dentry, inode->i_mode);
 	if (unlikely(error))
 		return error;
include/linux/fs.h +2 -2
index 98e0349adb52..3bdb71c97e8f 100644
@@ -1869,7 +1869,7 @@ struct inode_operations {
 	int (*readlink) (struct dentry *, char __user *,int);
 
 	int (*create) (struct inode *,struct dentry *, umode_t, bool);
-	int (*link) (struct dentry *,struct inode *,struct dentry *);
+	int (*link) (struct dentry *,struct inode *,struct dentry *, int);
 	int (*unlink) (struct inode *,struct dentry *);
 	int (*symlink) (struct inode *,struct dentry *,const char *);
 	int (*mkdir) (struct inode *,struct dentry *,umode_t);
@@ -3298,7 +3298,7 @@ extern int simple_setattr(struct dentry *, struct iattr *);
 extern int simple_getattr(const struct path *, struct kstat *, u32, unsigned int);
 extern int simple_statfs(struct dentry *, struct kstatfs *);
 extern int simple_open(struct inode *inode, struct file *file);
-extern int simple_link(struct dentry *, struct inode *, struct dentry *);
+extern int simple_link(struct dentry *, struct inode *, struct dentry *, int);
 extern int simple_unlink(struct inode *, struct dentry *);
 extern int simple_rmdir(struct inode *, struct dentry *);
 extern int simple_rename(struct inode *, struct dentry *,
mm/shmem.c +5 -1
index 8793e8cc1a48..ff7c976e19b9 100644
@@ -2948,11 +2948,15 @@ static int shmem_create(struct inode *dir, struct dentry *dentry, umode_t mode,
 /*
  * Link a file..
  */
-static int shmem_link(struct dentry *old_dentry, struct inode *dir, struct dentry *dentry)
+static int shmem_link(struct dentry *old_dentry, struct inode *dir,
+		      struct dentry *dentry, int flags)
 {
 	struct inode *inode = d_inode(old_dentry);
 	int ret = 0;
 
+	if (flags)
+		return -EINVAL;
+
 	/*
 	 * No ordinary (disk based) filesystem counts links as inodes;
 	 * but each new link needs a new dentry, pinning lowmem, and
Signature · 1 line
2.25.0
#19 lore raw

RFC PATCH v4 2/4 fs: add AT_LINK_REPLACE flag for linkat() which replaces the target

In reply to a message not in the archive

From: Omar Sandoval <[email protected]>

One of the most common uses of temporary files is the classic atomic replacement pattern, i.e.,

- write temporary file
- fsync temporary file
- rename temporary file over real file
- fsync parent directory

Now, we have O_TMPFILE, which gives us a much better way to create temporary files, but it's not possible to use it for this pattern.

This patch introduces an AT_LINK_REPLACE flag which allows linkat() to replace the target file. Now, the temporary file in the pattern above can be a proper O_TMPFILE. Even without O_TMPFILE, this is a new primitive which might be useful in other contexts.

The implementation on the VFS side mimics sys_renameat2().

---
 fs/ecryptfs/inode.c        |   2 +-
 fs/namei.c                 | 166 +++++++++++++++++++++++++++++--------
 fs/nfsd/vfs.c              |   2 +-
 fs/overlayfs/overlayfs.h   |   2 +-
 include/linux/fs.h         |   2 +-
 include/uapi/linux/fcntl.h |   1 +
 6 files changed, 135 insertions(+), 40 deletions(-)
fs/ecryptfs/inode.c +1 -1
index eeb351b220b2..2f36b7a61a2f 100644
@@ -440,7 +440,7 @@ static int ecryptfs_link(struct dentry *old_dentry, struct inode *dir,
 	dget(lower_new_dentry);
 	lower_dir_dentry = lock_parent(lower_new_dentry);
 	rc = vfs_link(lower_old_dentry, d_inode(lower_dir_dentry),
-		      lower_new_dentry, NULL);
+		      lower_new_dentry, NULL, 0);
 	if (rc || d_really_is_negative(lower_new_dentry))
 		goto out_lock;
 	rc = ecryptfs_interpose(lower_new_dentry, new_dentry, dir->i_sb);
fs/namei.c +130 -36

233 lines not on this page · raw message

fs/nfsd/vfs.c +1 -1
index c0dc491537a6..3f9291e76b99 100644
@@ -1598,7 +1598,7 @@ nfsd_link(struct svc_rqst *rqstp, struct svc_fh *ffhp,
 	err = nfserr_noent;
 	if (d_really_is_negative(dold))
 		goto out_dput;
-	host_err = vfs_link(dold, dirp, dnew, NULL);
+	host_err = vfs_link(dold, dirp, dnew, NULL, 0);
 	if (!host_err) {
 		err = nfserrno(commit_metadata(ffhp));
 		if (!err)
fs/overlayfs/overlayfs.h +1 -1
index f283b1d69a9e..b199fc03c891 100644
@@ -120,7 +120,7 @@ static inline int ovl_do_unlink(struct inode *dir, struct dentry *dentry)
 static inline int ovl_do_link(struct dentry *old_dentry, struct inode *dir,
 			      struct dentry *new_dentry)
 {
-	int err = vfs_link(old_dentry, dir, new_dentry, NULL);
+	int err = vfs_link(old_dentry, dir, new_dentry, NULL, 0);
 
 	pr_debug("link(%pd2, %pd2) = %i\n", old_dentry, new_dentry, err);
 	return err;
include/linux/fs.h +1 -1
index 3bdb71c97e8f..93eb90eb1fdb 100644
@@ -1712,7 +1712,7 @@ extern int vfs_create(struct inode *, struct dentry *, umode_t, bool);
 extern int vfs_mkdir(struct inode *, struct dentry *, umode_t);
 extern int vfs_mknod(struct inode *, struct dentry *, umode_t, dev_t);
 extern int vfs_symlink(struct inode *, struct dentry *, const char *);
-extern int vfs_link(struct dentry *, struct inode *, struct dentry *, struct inode **);
+extern int vfs_link(struct dentry *, struct inode *, struct dentry *, struct inode **, int);
 extern int vfs_rmdir(struct inode *, struct dentry *);
 extern int vfs_unlink(struct inode *, struct dentry *, struct inode **);
 extern int vfs_rename(struct inode *, struct dentry *, struct inode *, struct dentry *, struct inode **, unsigned int);
include/uapi/linux/fcntl.h +1 -0
index 1f97b33c840e..3704793cd5ab 100644
@@ -99,6 +99,7 @@
 #define AT_STATX_DONT_SYNC	0x4000	/* - Don't sync attributes with the server */
 
 #define AT_RECURSIVE		0x8000	/* Apply to the entire subtree */
+#define AT_LINK_REPLACE		0x10000	/* Replace link() target */
 
 
 #endif /* _UAPI_LINUX_FCNTL_H */
Signature · 1 line
2.25.0
#20 lore raw

RFC PATCH v4 3/4 Btrfs: fix inode reference count leak in btrfs_link() error path

In reply to a message not in the archive

From: Omar Sandoval <[email protected]>

If btrfs_update_inode() or btrfs_orphan_del() fails in btrfs_link(), then we don't drop the reference we got with ihold(). This results in the "VFS: Busy inodes after unmount" crash.

The reference is needed for the new dentry, so get it right before we instantiate the dentry.

Fixes 79787eaab461 ("btrfs: replace many BUG_ONs with proper error handling")

[Although d_instantiate() was moved further from ihold() before that, in commit 08c422c27f85 ("Btrfs: call d_instantiate after all ops are setup")]

---
 fs/btrfs/inode.c | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)
fs/btrfs/inode.c +2 -4
index bc7709c4f6eb..8c9a114f48f6 100644
@@ -6801,7 +6801,6 @@ static int btrfs_link(struct dentry *old_dentry, struct inode *dir,
 	inc_nlink(inode);
 	inode_inc_iversion(inode);
 	inode->i_ctime = current_time(inode);
-	ihold(inode);
 	set_bit(BTRFS_INODE_COPY_EVERYTHING, &BTRFS_I(inode)->runtime_flags);
 
 	err = btrfs_add_nondir(trans, BTRFS_I(dir), dentry, BTRFS_I(inode),
@@ -6825,6 +6824,7 @@ static int btrfs_link(struct dentry *old_dentry, struct inode *dir,
 			if (err)
 				goto fail;
 		}
+		ihold(inode);
 		d_instantiate(dentry, inode);
 		ret = btrfs_log_new_name(trans, BTRFS_I(inode), NULL, parent,
 					 true, NULL);
@@ -6837,10 +6837,8 @@ static int btrfs_link(struct dentry *old_dentry, struct inode *dir,
 fail:
 	if (trans)
 		btrfs_end_transaction(trans);
-	if (drop_inode) {
+	if (drop_inode)
 		inode_dec_link_count(inode);
-		iput(inode);
-	}
 	btrfs_btree_balance_dirty(fs_info);
 	return err;
 }
Signature · 1 line
2.25.0
#21 lore raw

RFC PATCH v4 4/4 Btrfs: add support for linkat() AT_REPLACE

In reply to a message not in the archive

From: Omar Sandoval <[email protected]>

The implementation is fairly straightforward and looks a lot like btrfs_rename(). The only tricky bit is that instead of playing games with the dcache, we simply drop the dentry for it to be instantiated on the next lookup. This can be improved in the future.

---
 fs/btrfs/inode.c | 63 +++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 59 insertions(+), 4 deletions(-)
fs/btrfs/inode.c +59 -4
index 8c9a114f48f6..b489671d1b5d 100644
@@ -6762,14 +6762,16 @@ static int btrfs_link(struct dentry *old_dentry, struct inode *dir,
 		      struct dentry *dentry, int flags)
 {
 	struct btrfs_trans_handle *trans = NULL;
+	unsigned int trans_num_items;
 	struct btrfs_root *root = BTRFS_I(dir)->root;
 	struct inode *inode = d_inode(old_dentry);
+	struct inode *new_inode = d_inode(dentry);
 	struct btrfs_fs_info *fs_info = btrfs_sb(inode->i_sb);
 	u64 index;
 	int err;
 	int drop_inode = 0;
 
-	if (flags)
+	if (flags & ~AT_LINK_REPLACE)
 		return -EINVAL;
 
 	/* do not allow sys_link's with other subvols of the same device */
@@ -6779,17 +6781,50 @@ static int btrfs_link(struct dentry *old_dentry, struct inode *dir,
 	if (inode->i_nlink >= BTRFS_LINK_MAX)
 		return -EMLINK;
 
+	/* check for collisions, even if the name isn't there */
+	err = btrfs_check_dir_item_collision(root, dir->i_ino,
+					     dentry->d_name.name,
+					     dentry->d_name.len);
+	if (err) {
+		if (err == -EEXIST) {
+			if (WARN_ON(!new_inode))
+				return err;
+		} else {
+			return err;
+		}
+	}
+
+	/*
+	 * we're using link to replace one file with another. Start IO on it now
+	 * so we don't add too much work to the end of the transaction
+	 */
+	if (new_inode && S_ISREG(inode->i_mode) && new_inode->i_size)
+		filemap_flush(inode->i_mapping);
+
 	err = btrfs_set_inode_index(BTRFS_I(dir), &index);
 	if (err)
 		goto fail;
 
 	/*
+	 * For the source:
 	 * 2 items for inode and inode ref
 	 * 2 items for dir items
 	 * 1 item for parent inode
 	 * 1 item for orphan item deletion if O_TMPFILE
+	 *
+	 * For the target:
+	 * 1 for the possible orphan item
+	 * 1 for the dir item
+	 * 1 for the dir index
+	 * 1 for the inode ref
+	 * 1 for the inode
 	 */
-	trans = btrfs_start_transaction(root, inode->i_nlink ? 5 : 6);
+	trans_num_items = 5;
+	if (!inode->i_nlink)
+		trans_num_items++;
+	if (new_inode)
+		trans_num_items += 5;
+	trans = btrfs_start_transaction(root, trans_num_items);
 	if (IS_ERR(trans)) {
 		err = PTR_ERR(trans);
 		trans = NULL;
@@ -6801,6 +6836,22 @@ static int btrfs_link(struct dentry *old_dentry, struct inode *dir,
 	inc_nlink(inode);
 	inode_inc_iversion(inode);
 	inode->i_ctime = current_time(inode);
+
+	if (new_inode) {
+		inode_inc_iversion(new_inode);
+		new_inode->i_ctime = current_time(new_inode);
+		err = btrfs_unlink_inode(trans, root, BTRFS_I(dir),
+					 BTRFS_I(new_inode),
+					 dentry->d_name.name,
+					 dentry->d_name.len);
+		if (!err && new_inode->i_nlink == 0)
+			err = btrfs_orphan_add(trans, BTRFS_I(new_inode));
+		if (err) {
+			btrfs_abort_transaction(trans, err);
+			goto fail;
+		}
+	}
+
 	set_bit(BTRFS_INODE_COPY_EVERYTHING, &BTRFS_I(inode)->runtime_flags);
 
 	err = btrfs_add_nondir(trans, BTRFS_I(dir), dentry, BTRFS_I(inode),
@@ -6824,8 +6875,12 @@ static int btrfs_link(struct dentry *old_dentry, struct inode *dir,
 			if (err)
 				goto fail;
 		}
-		ihold(inode);
-		d_instantiate(dentry, inode);
+		if (new_inode) {
+			d_drop(dentry);
+		} else {
+			ihold(inode);
+			d_instantiate(dentry, inode);
+		}
 		ret = btrfs_log_new_name(trans, BTRFS_I(inode), NULL, parent,
 					 true, NULL);
 		if (ret == BTRFS_NEED_TRANS_COMMIT) {
Signature · 1 line
2.25.0
#6 lore raw

Re: [RFC PATCH xfsprogs] xfs_io: add support for linkat() AT_LINK_REPLACE

In reply to Omar Sandoval
Quoted 4 lines · Omar Sandoval

From: Omar Sandoval <[email protected]>

---

And this patch would be better to send to/cc [email protected] to get xfsprogs maintainers/developers review.

Quoted 87 lines
 io/link.c         | 24 ++++++++++++++++++++----
 man/man8/xfs_io.8 |  9 ++++++++-
 2 files changed, 28 insertions(+), 5 deletions(-)
io/link.c +20 -4
index f4f4b139..3fc3e24d 100644
@@ -12,6 +12,9 @@
 #ifndef AT_EMPTY_PATH
 #define AT_EMPTY_PATH	0x1000
 #endif
+#ifndef AT_LINK_REPLACE
+#define AT_LINK_REPLACE	0x10000
+#endif
 
 static cmdinfo_t flink_cmd;
 
@@ -22,6 +25,7 @@ flink_help(void)
 "\n"
 "link the open file descriptor to the supplied filename\n"
 "\n"
+" -f -- overwrite the target filename if it exists (AT_LINK_REPLACE)\n"
 "\n"));
 }
 
@@ -30,10 +34,22 @@ flink_f(
 	int		argc,
 	char		**argv)
 {
-	if (argc != 2)
+	int		flags = AT_EMPTY_PATH;
+	int		c;
+
+	while ((c = getopt(argc, argv, "f")) != EOF) {
+		switch (c) {
+		case 'f':
+			flags |= AT_LINK_REPLACE;
+			break;
+		default:
+			return command_usage(&flink_cmd);
+		}
+	}
+	if (optind != argc - 1)
 		return command_usage(&flink_cmd);
 
-	if (linkat(file->fd, "", AT_FDCWD, argv[1], AT_EMPTY_PATH) < 0) {
+	if (linkat(file->fd, "", AT_FDCWD, argv[optind], flags) < 0) {
 		perror("flink");
 		return 0;
 	}
@@ -46,9 +62,9 @@ flink_init(void)
 	flink_cmd.name = "flink";
 	flink_cmd.cfunc = flink_f;
 	flink_cmd.argmin = 1;
-	flink_cmd.argmax = 1;
+	flink_cmd.argmax = -1;
 	flink_cmd.flags = CMD_NOMAP_OK | CMD_FOREIGN_OK | CMD_FLAG_ONESHOT;
-	flink_cmd.args = _("filename");
+	flink_cmd.args = _("[-f] filename");
 	flink_cmd.oneline =
 		_("link the open file descriptor to the supplied filename");
 	flink_cmd.help = flink_help;
man/man8/xfs_io.8 +8 -1
index c69b295d..f79b3a59 100644
@@ -807,8 +807,15 @@ for the full list) is available via the
 .B help
 command.
 .TP
-.BI "flink " path
+.BI "flink [ \-f ]" " path"
 Link the currently open file descriptor into the filesystem namespace.
+.RS 1.0i
+.PD 0
+.TP 0.4i
+.B \-f
+overwrite the target path if it exists (AT_LINK_REPLACE).
+.PD
+.RE
 .TP
 .BR stat " [ " \-v "|" \-r " ]"
 Selected statistics from
Signature · 1 line
2.25.0
maintainer
#8 lore raw

Re: [RFC PATCH v4 1/4] fs: add flags argument to i_op->link()

In reply to Omar Sandoval

include/linux/fs.h struct inode_operations { · line 1872

Quoted 2 lines · Omar Sandoval
include/linux/fs.h +1 -1
-	int (*link) (struct dentry *,struct inode *,struct dentry *);
+	int (*link) (struct dentry *,struct inode *,struct dentry *, int);

Can you make it unsigned int?

David

#9 lore raw

Re: [RFC PATCH v4 1/4] fs: add flags argument to i_op->link()

Quoted 9 lines · David Howells

include/linux/fs.h struct inode_operations { · line 1872

Quoted 2 lines · Omar Sandoval
include/linux/fs.h +1 -1
-	int (*link) (struct dentry *,struct inode *,struct dentry *);
+	int (*link) (struct dentry *,struct inode *,struct dentry *, int);

Can you make it unsigned int?

David

For some reason, the linkat() syscall takes an int, but you're right, might as well make it an unsigned int in the VFS.

#4 lore raw

Re: [RFC PATCH xfstests] generic: add smoke test for AT_LINK_REPLACE

In reply to Omar Sandoval
Quoted 4 lines · Omar Sandoval

From: Omar Sandoval <[email protected]>

Looks fine overall, would you please provide more info about this AT_LINK_REPLACE flag? e.g. what's the expected behavior, what's current status (merged in kernel or still pending?), reference the related commits if already merged.

Quoted 95 lines
---
 common/rc             |  2 +-
 tests/generic/593     | 97 +++++++++++++++++++++++++++++++++++++++++++
 tests/generic/593.out |  6 +++
 tests/generic/group   |  1 +
 4 files changed, 105 insertions(+), 1 deletion(-)
 create mode 100755 tests/generic/593
 create mode 100644 tests/generic/593.out
common/rc +1 -1
index eeac1355..257f65a1 100644
@@ -2172,7 +2172,7 @@ _require_xfs_io_command()
 		;;
 	"flink")
 		local testlink=$TEST_DIR/$$.link.xfs_io
-		testio=`$XFS_IO_PROG -F -f -c "flink $testlink" $testfile 2>&1`
+		testio=`$XFS_IO_PROG -F -f -c "flink $param $testlink" $testfile 2>&1`
 		rm -f $testlink > /dev/null 2>&1
 		;;
 	"-T")
tests/generic/593 new file +67 -0
new file mode 100755
index 00000000..8a9fee02
@@ -0,0 +1,97 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2020 Facebook.  All Rights Reserved.
+#
+# FS QA Test 593
+#
+# Smoke test linkat() with AT_LINK_REPLACE.
+#
+seq=`basename $0`
+seqres=$RESULT_DIR/$seq
+echo "QA output created by $seq"
+
+here=`pwd`
+tmp=/tmp/$$
+status=1	# failure is the default!
+trap "_cleanup; exit \$status" 0 1 2 3 15
+
+_cleanup()
+{
+	cd /
+	rm -f $tmp.*
+}
+
+# get standard environment, filters and checks
+. ./common/rc
+. ./common/filter
+
+# remove previous $seqres.full before test
+rm -f $seqres.full
+
+_supported_fs generic
+_supported_os Linux
+_require_test
+_require_xfs_io_command "-T"
+_require_xfs_io_command "flink" "-f"
+
+same_file() {
+	[[ "$(stat -c '%d %i' "$1")" = "$(stat -c '%d %i' "$2")" ]]
+}
+
+touch "$TEST_DIR/$seq.src"
+touch "$TEST_DIR/$seq.tgt"
+$XFS_IO_PROG -c "flink -f $TEST_DIR/$seq.tgt" "$TEST_DIR/$seq.src"
+same_file "$TEST_DIR/$seq.src" "$TEST_DIR/$seq.tgt" ||
+	echo "Target was not replaced"
+
+# Linking to the same file should be a noop.
+$XFS_IO_PROG -c "flink -f $TEST_DIR/$seq.src" "$TEST_DIR/$seq.src"
+$XFS_IO_PROG -c "flink -f $TEST_DIR/$seq.tgt" "$TEST_DIR/$seq.src"
+same_file "$TEST_DIR/$seq.src" "$TEST_DIR/$seq.tgt" || echo "Target changed?"
+
+# Should work with O_TMPFILE.
+$XFS_IO_PROG -c "flink -f $TEST_DIR/$seq.tgt" -T "$TEST_DIR"
+stat -c '%h' "$TEST_DIR/$seq.tgt"
+same_file "$TEST_DIR/$seq.src" "$TEST_DIR/$seq.tgt" &&
+	echo "Target was not replaced"
+
+# It's okay if the target doesn't exist.
+$XFS_IO_PROG -c "flink -f $TEST_DIR/$seq.tgt2" "$TEST_DIR/$seq.src"
+same_file "$TEST_DIR/$seq.src" "$TEST_DIR/$seq.tgt2" ||
+	echo "Target was not created"
+
+# Can't replace directories.
+mkdir "$TEST_DIR/$seq.dir"
+$XFS_IO_PROG -c "flink -f $TEST_DIR/$seq.dir" "$TEST_DIR/$seq.src"
+cd "$TEST_DIR/$seq.dir"
+$XFS_IO_PROG -c "flink -f ." "$TEST_DIR/$seq.src"

tests/generic/593 line 77

Quoted 10 lines
tests/generic/593 new file +10 -0
+$XFS_IO_PROG -c "flink -f .." "$TEST_DIR/$seq.src"
+cd - &> /dev/null
+
+# Can't replace local mount points.
+touch "$TEST_DIR/$seq.mnt"
+$MOUNT_PROG --bind "$TEST_DIR/$seq.mnt" "$TEST_DIR/$seq.mnt"
+$XFS_IO_PROG -c "flink -f $TEST_DIR/$seq.mnt" "$TEST_DIR/$seq.src"
+
+# Can replace mount points in other namespaces, though.
+unshare -m \

Better to define an UNSHARE_PROG in common/config and require it in this test, then use $UNSHARE_PROG here.

Thanks,
Eryu

Quoted 44 lines
tests/generic/593 new file +20 -0
+	bash -c "$UMOUNT_PROG $TEST_DIR/$seq.mnt; $XFS_IO_PROG -c \"flink -f $TEST_DIR/$seq.mnt\" $TEST_DIR/$seq.src"
+if $UMOUNT_PROG "$TEST_DIR/$seq.mnt" &> /dev/null; then
+	echo "Mount point was not detached"
+fi
+same_file "$TEST_DIR/$seq.src" "$TEST_DIR/$seq.mnt" ||
+	echo "Mount point was not replaced"
+
+# Should replace symlinks, not follow them.
+touch "$TEST_DIR/$seq.symtgt"
+ln -s "$TEST_DIR/$seq.symtgt" "$TEST_DIR/$seq.sym"
+$XFS_IO_PROG -c "flink -f $TEST_DIR/$seq.sym" "$TEST_DIR/$seq.src"
+same_file "$TEST_DIR/$seq.src" "$TEST_DIR/$seq.sym" ||
+	echo "Symlink was not replaced"
+same_file "$TEST_DIR/$seq.src" "$TEST_DIR/$seq.symtgt" &&
+	echo "Symlink target was replaced"
+
+rm -rf "$TEST_DIR/$seq."*
+
+status=0
+exit
tests/generic/593.out new file +6 -0
new file mode 100644
index 00000000..834c34bf
@@ -0,0 +1,6 @@
+QA output created by 593
+1
+flink: Is a directory
+flink: Is a directory
+flink: Is a directory
+flink: Device or resource busy
tests/generic/group +1 -0
index 6fe62505..0a87efca 100644
@@ -595,3 +595,4 @@
 590 auto prealloc preallocrw
 591 auto quick rw pipe splice
 592 auto quick encrypt
+593 auto quick hardlink
Signature · 1 line
2.25.0