All of lore.kernel.org
 help / color / mirror / Atom feed
* [RFC PATCH xfstests] generic: add smoke test for AT_LINK_REPLACE
  2020-01-29  8:58 ` Omar Sandoval
@ 2020-01-29  8:58   ` Omar Sandoval
  -1 siblings, 0 replies; 31+ messages in thread
From: Omar Sandoval @ 2020-01-28 23:18 UTC (permalink / raw)
  To: linux-fsdevel, Al Viro; +Cc: kernel-team

From: Omar Sandoval <osandov@fb.com>

Signed-off-by: Omar Sandoval <osandov@fb.com>
---
 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

diff --git a/common/rc b/common/rc
index eeac1355..257f65a1 100644
--- a/common/rc
+++ b/common/rc
@@ -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")
diff --git a/tests/generic/593 b/tests/generic/593
new file mode 100755
index 00000000..8a9fee02
--- /dev/null
+++ b/tests/generic/593
@@ -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
diff --git a/tests/generic/593.out b/tests/generic/593.out
new file mode 100644
index 00000000..834c34bf
--- /dev/null
+++ b/tests/generic/593.out
@@ -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
diff --git a/tests/generic/group b/tests/generic/group
index 6fe62505..0a87efca 100644
--- a/tests/generic/group
+++ b/tests/generic/group
@@ -595,3 +595,4 @@
 590 auto prealloc preallocrw
 591 auto quick rw pipe splice
 592 auto quick encrypt
+593 auto quick hardlink
-- 
2.25.0


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [RFC PATCH man-pages] link.2: Document new AT_LINK_REPLACE flag
@ 2020-01-29  8:58   ` Omar Sandoval
  0 siblings, 0 replies; 31+ messages in thread
From: Omar Sandoval @ 2020-01-28 23:18 UTC (permalink / raw)
  To: linux-fsdevel, Al Viro; +Cc: kernel-team

From: Omar Sandoval <osandov@fb.com>

Signed-off-by: Omar Sandoval <osandov@fb.com>
---
 man2/link.2 | 191 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 191 insertions(+)

diff --git a/man2/link.2 b/man2/link.2
index 649ba00c7..0097e3071 100644
--- a/man2/link.2
+++ b/man2/link.2
@@ -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),
-- 
2.25.0


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [RFC PATCH xfsprogs] xfs_io: add support for linkat() AT_LINK_REPLACE
@ 2020-01-29  8:58   ` Omar Sandoval
  0 siblings, 0 replies; 31+ messages in thread
From: Omar Sandoval @ 2020-01-28 23:18 UTC (permalink / raw)
  To: linux-fsdevel, Al Viro; +Cc: kernel-team

From: Omar Sandoval <osandov@fb.com>

Signed-off-by: Omar Sandoval <osandov@fb.com>
---
 io/link.c         | 24 ++++++++++++++++++++----
 man/man8/xfs_io.8 |  9 ++++++++-
 2 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/io/link.c b/io/link.c
index f4f4b139..3fc3e24d 100644
--- a/io/link.c
+++ b/io/link.c
@@ -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;
diff --git a/man/man8/xfs_io.8 b/man/man8/xfs_io.8
index c69b295d..f79b3a59 100644
--- a/man/man8/xfs_io.8
+++ b/man/man8/xfs_io.8
@@ -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
-- 
2.25.0


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [RFC PATCH v4 0/4] fs: add flag to linkat() for replacing destination
@ 2020-01-29  8:58 ` Omar Sandoval
  0 siblings, 0 replies; 31+ messages in thread
From: Omar Sandoval @ 2020-01-28 23:18 UTC (permalink / raw)
  To: linux-fsdevel, Al Viro
  Cc: kernel-team, David Howells, Amir Goldstein, Xi Wang

From: Omar Sandoval <osandov@fb.com>

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/cover.1524549513.git.osandov@fb.com/
2: https://lore.kernel.org/linux-fsdevel/3326.1579019665@warthog.procyon.org.uk/
3: https://lore.kernel.org/linux-fsdevel/364531.1579265357@warthog.procyon.org.uk/
4: https://lore.kernel.org/linux-fsdevel/20190527172655.9287-1-amir73il@gmail.com/

Cc: David Howells <dhowells@redhat.com>
Cc: Amir Goldstein <amir73il@gmail.com>
Cc: Xi Wang <xi@cs.washington.edu>

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(-)

-- 
2.25.0


^ permalink raw reply	[flat|nested] 31+ messages in thread

* [RFC PATCH v4 1/4] fs: add flags argument to i_op->link()
@ 2020-01-29  8:58   ` Omar Sandoval
  0 siblings, 0 replies; 31+ messages in thread
From: Omar Sandoval @ 2020-01-28 23:19 UTC (permalink / raw)
  To: linux-fsdevel, Al Viro; +Cc: kernel-team

From: Omar Sandoval <osandov@fb.com>

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

Signed-off-by: Omar Sandoval <osandov@fb.com>
---
 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(-)

diff --git a/fs/9p/vfs_inode.c b/fs/9p/vfs_inode.c
index b82423a72f68..3cda7788122e 100644
--- a/fs/9p/vfs_inode.c
+++ b/fs/9p/vfs_inode.c
@@ -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);
 
diff --git a/fs/9p/vfs_inode_dotl.c b/fs/9p/vfs_inode_dotl.c
index 60328b21c5fb..636796582f68 100644
--- a/fs/9p/vfs_inode_dotl.c
+++ b/fs/9p/vfs_inode_dotl.c
@@ -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);
 
diff --git a/fs/affs/affs.h b/fs/affs/affs.h
index a755bef7c4c7..98d70fd7ea47 100644
--- a/fs/affs/affs.h
+++ b/fs/affs/affs.h
@@ -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,
diff --git a/fs/affs/namei.c b/fs/affs/namei.c
index 41c5749f4db7..198f2878145d 100644
--- a/fs/affs/namei.c
+++ b/fs/affs/namei.c
@@ -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);
 
diff --git a/fs/afs/dir.c b/fs/afs/dir.c
index 5c794f4b051a..0a17d8d8c009 100644
--- a/fs/afs/dir.c
+++ b/fs/afs/dir.c
@@ -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,
diff --git a/fs/bad_inode.c b/fs/bad_inode.c
index 8035d2a44561..41caa98597d6 100644
--- a/fs/bad_inode.c
+++ b/fs/bad_inode.c
@@ -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;
 }
diff --git a/fs/bfs/dir.c b/fs/bfs/dir.c
index d8dfe3a0cb39..0b0ef8a59a73 100644
--- a/fs/bfs/dir.c
+++ b/fs/bfs/dir.c
@@ -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) {
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index c70baafb2a39..bc7709c4f6eb 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -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;
diff --git a/fs/ceph/dir.c b/fs/ceph/dir.c
index 2e4764fd1872..a1517be5405c 100644
--- a/fs/ceph/dir.c
+++ b/fs/ceph/dir.c
@@ -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;
 
diff --git a/fs/cifs/cifsfs.h b/fs/cifs/cifsfs.h
index b59dc7478130..9360d28255ef 100644
--- a/fs/cifs/cifsfs.h
+++ b/fs/cifs/cifsfs.h
@@ -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 *);
diff --git a/fs/cifs/link.c b/fs/cifs/link.c
index b736acd3917b..1471d4c095d7 100644
--- a/fs/cifs/link.c
+++ b/fs/cifs/link.c
@@ -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);
diff --git a/fs/coda/dir.c b/fs/coda/dir.c
index ca40c2556ba6..acdbc47948cf 100644
--- a/fs/coda/dir.c
+++ b/fs/coda/dir.c
@@ -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;
 
diff --git a/fs/ecryptfs/inode.c b/fs/ecryptfs/inode.c
index e23752d9a79f..eeb351b220b2 100644
--- a/fs/ecryptfs/inode.c
+++ b/fs/ecryptfs/inode.c
@@ -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);
diff --git a/fs/ext2/namei.c b/fs/ext2/namei.c
index ccfbbf59e2fc..9417a96f6ea8 100644
--- a/fs/ext2/namei.c
+++ b/fs/ext2/namei.c
@@ -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;
diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c
index 1cb42d940784..80e27fd907d0 100644
--- a/fs/ext4/namei.c
+++ b/fs/ext4/namei.c
@@ -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;
 
diff --git a/fs/f2fs/namei.c b/fs/f2fs/namei.c
index a1c507b0b4ac..f7ca6fbf5aaf 100644
--- a/fs/f2fs/namei.c
+++ b/fs/f2fs/namei.c
@@ -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))
diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
index ee190119f45c..ae0cf00fd9ec 100644
--- a/fs/fuse/dir.c
+++ b/fs/fuse/dir.c
@@ -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;
diff --git a/fs/gfs2/inode.c b/fs/gfs2/inode.c
index dafef10b91f1..f2f97321b659 100644
--- a/fs/gfs2/inode.c
+++ b/fs/gfs2/inode.c
@@ -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;
 
diff --git a/fs/hfsplus/dir.c b/fs/hfsplus/dir.c
index 29a9dcfbe81f..16cf85e8e51d 100644
--- a/fs/hfsplus/dir.c
+++ b/fs/hfsplus/dir.c
@@ -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))
diff --git a/fs/hostfs/hostfs_kern.c b/fs/hostfs/hostfs_kern.c
index 5a7eb0c79839..f2bd0e42075b 100644
--- a/fs/hostfs/hostfs_kern.c
+++ b/fs/hostfs/hostfs_kern.c
@@ -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);
diff --git a/fs/jffs2/dir.c b/fs/jffs2/dir.c
index f20cff1194bb..c4ec9202ca8e 100644
--- a/fs/jffs2/dir.c
+++ b/fs/jffs2/dir.c
@@ -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;
diff --git a/fs/jfs/namei.c b/fs/jfs/namei.c
index 7a55d14cc1af..45447fca1fdb 100644
--- a/fs/jfs/namei.c
+++ b/fs/jfs/namei.c
@@ -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);
diff --git a/fs/libfs.c b/fs/libfs.c
index 1463b038ffc4..1894d6b202f2 100644
--- a/fs/libfs.c
+++ b/fs/libfs.c
@@ -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);
diff --git a/fs/minix/namei.c b/fs/minix/namei.c
index 1a6084d2b02e..6dc1c4fd4dd6 100644
--- a/fs/minix/namei.c
+++ b/fs/minix/namei.c
@@ -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);
diff --git a/fs/namei.c b/fs/namei.c
index 4fb61e0754ed..9d690df17aed 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -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)) {
diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c
index e180033e35cf..2497d0287043 100644
--- a/fs/nfs/dir.c
+++ b/fs/nfs/dir.c
@@ -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);
 
diff --git a/fs/nfs/internal.h b/fs/nfs/internal.h
index 24a65da58aa9..4f446465ed7b 100644
--- a/fs/nfs/internal.h
+++ b/fs/nfs/internal.h
@@ -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);
diff --git a/fs/nilfs2/namei.c b/fs/nilfs2/namei.c
index 9fe6d4ab74f0..bcbca3f9d947 100644
--- a/fs/nilfs2/namei.c
+++ b/fs/nilfs2/namei.c
@@ -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;
diff --git a/fs/ocfs2/namei.c b/fs/ocfs2/namei.c
index 8ea51cf27b97..c228e7bcb51b 100644
--- a/fs/ocfs2/namei.c
+++ b/fs/ocfs2/namei.c
@@ -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);
diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c
index 29abdb1d3b5c..923d1c93f570 100644
--- a/fs/overlayfs/dir.c
+++ b/fs/overlayfs/dir.c
@@ -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;
diff --git a/fs/reiserfs/namei.c b/fs/reiserfs/namei.c
index 959a066b7bb0..4ff94931f350 100644
--- a/fs/reiserfs/namei.c
+++ b/fs/reiserfs/namei.c
@@ -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;
diff --git a/fs/sysv/namei.c b/fs/sysv/namei.c
index ea2414b385ec..a202feb6ad89 100644
--- a/fs/sysv/namei.c
+++ b/fs/sysv/namei.c
@@ -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);
diff --git a/fs/ubifs/dir.c b/fs/ubifs/dir.c
index 0b98e3c8b461..1fc180266338 100644
--- a/fs/ubifs/dir.c
+++ b/fs/ubifs/dir.c
@@ -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.
diff --git a/fs/udf/namei.c b/fs/udf/namei.c
index 77b6d89b9bcd..263c9daf6aad 100644
--- a/fs/udf/namei.c
+++ b/fs/udf/namei.c
@@ -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;
diff --git a/fs/ufs/namei.c b/fs/ufs/namei.c
index 9ef40f100415..7f8d2e4eac8c 100644
--- a/fs/ufs/namei.c
+++ b/fs/ufs/namei.c
@@ -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);
diff --git a/fs/xfs/xfs_iops.c b/fs/xfs/xfs_iops.c
index 8afe69ca188b..62ecb726fa41 100644
--- a/fs/xfs/xfs_iops.c
+++ b/fs/xfs/xfs_iops.c
@@ -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;
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 98e0349adb52..3bdb71c97e8f 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -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 *,
diff --git a/mm/shmem.c b/mm/shmem.c
index 8793e8cc1a48..ff7c976e19b9 100644
--- a/mm/shmem.c
+++ b/mm/shmem.c
@@ -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
-- 
2.25.0


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [RFC PATCH v4 2/4] fs: add AT_LINK_REPLACE flag for linkat() which replaces the target
@ 2020-01-29  8:58   ` Omar Sandoval
  0 siblings, 0 replies; 31+ messages in thread
From: Omar Sandoval @ 2020-01-28 23:19 UTC (permalink / raw)
  To: linux-fsdevel, Al Viro; +Cc: kernel-team, Xi Wang

From: Omar Sandoval <osandov@fb.com>

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().

Cc: Xi Wang <xi@cs.washington.edu>
Signed-off-by: Omar Sandoval <osandov@fb.com>
---
 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(-)

diff --git a/fs/ecryptfs/inode.c b/fs/ecryptfs/inode.c
index eeb351b220b2..2f36b7a61a2f 100644
--- a/fs/ecryptfs/inode.c
+++ b/fs/ecryptfs/inode.c
@@ -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);
diff --git a/fs/namei.c b/fs/namei.c
index 9d690df17aed..78d364e99dca 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -4122,6 +4122,7 @@ SYSCALL_DEFINE2(symlink, const char __user *, oldname, const char __user *, newn
  * @dir:	new parent
  * @new_dentry:	where to create the new link
  * @delegated_inode: returns inode needing a delegation break
+ * @flags:      link flags
  *
  * The caller must hold dir->i_mutex
  *
@@ -4135,16 +4136,25 @@ SYSCALL_DEFINE2(symlink, const char __user *, oldname, const char __user *, newn
  * be appropriate for callers that expect the underlying filesystem not
  * to be NFS exported.
  */
-int vfs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_dentry, struct inode **delegated_inode)
+int vfs_link(struct dentry *old_dentry, struct inode *dir,
+	     struct dentry *new_dentry, struct inode **delegated_inode,
+	     int flags)
 {
 	struct inode *inode = old_dentry->d_inode;
+	struct inode *target = new_dentry->d_inode;
 	unsigned max_links = dir->i_sb->s_max_links;
 	int error;
 
 	if (!inode)
 		return -ENOENT;
 
-	error = may_create(dir, new_dentry);
+	if (target) {
+		if (inode == target)
+			return 0;
+		error = may_delete(dir, new_dentry, false);
+	} else {
+		error = may_create(dir, new_dentry);
+	}
 	if (error)
 		return error;
 
@@ -4172,26 +4182,55 @@ int vfs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_de
 	if (error)
 		return error;
 
-	inode_lock(inode);
+	dget(new_dentry);
+	lock_two_nondirectories(inode, target);
+
+	if (is_local_mountpoint(new_dentry)) {
+		error = -EBUSY;
+		goto out;
+	}
+
 	/* Make sure we don't allow creating hardlink to an unlinked file */
-	if (inode->i_nlink == 0 && !(inode->i_state & I_LINKABLE))
+	if (inode->i_nlink == 0 && !(inode->i_state & I_LINKABLE)) {
 		error =  -ENOENT;
-	else if (max_links && inode->i_nlink >= max_links)
+		goto out;
+	}
+	if (max_links && inode->i_nlink >= max_links) {
 		error = -EMLINK;
-	else {
-		error = try_break_deleg(inode, delegated_inode);
-		if (!error)
-			error = dir->i_op->link(old_dentry, dir, new_dentry, 0);
+		goto out;
+	}
+
+	error = try_break_deleg(inode, delegated_inode);
+	if (error)
+		goto out;
+	if (target) {
+		error = try_break_deleg(target, delegated_inode);
+		if (error)
+			goto out;
+	}
+
+	error = dir->i_op->link(old_dentry, dir, new_dentry, flags);
+	if (error)
+		goto out;
+
+	if (target) {
+		dont_mount(new_dentry);
+		detach_mounts(new_dentry);
 	}
 
-	if (!error && (inode->i_state & I_LINKABLE)) {
+	if (inode->i_state & I_LINKABLE) {
 		spin_lock(&inode->i_lock);
 		inode->i_state &= ~I_LINKABLE;
 		spin_unlock(&inode->i_lock);
 	}
-	inode_unlock(inode);
-	if (!error)
+out:
+	unlock_two_nondirectories(inode, target);
+	dput(new_dentry);
+	if (!error) {
+		if (target)
+			fsnotify_link_count(target);
 		fsnotify_link(dir, inode, new_dentry);
+	}
 	return error;
 }
 EXPORT_SYMBOL(vfs_link);
@@ -4210,11 +4249,16 @@ int do_linkat(int olddfd, const char __user *oldname, int newdfd,
 {
 	struct dentry *new_dentry;
 	struct path old_path, new_path;
+	struct qstr new_last;
+	int new_type;
 	struct inode *delegated_inode = NULL;
-	int how = 0;
+	struct filename *to;
+	unsigned int how = 0, target_flags;
+	bool should_retry = false;
 	int error;
 
-	if ((flags & ~(AT_SYMLINK_FOLLOW | AT_EMPTY_PATH)) != 0)
+	if ((flags & ~(AT_SYMLINK_FOLLOW | AT_EMPTY_PATH |
+		       AT_LINK_REPLACE)) != 0)
 		return -EINVAL;
 	/*
 	 * To use null names we require CAP_DAC_READ_SEARCH
@@ -4229,44 +4273,94 @@ int do_linkat(int olddfd, const char __user *oldname, int newdfd,
 
 	if (flags & AT_SYMLINK_FOLLOW)
 		how |= LOOKUP_FOLLOW;
+
+	if (flags & AT_LINK_REPLACE)
+		target_flags = LOOKUP_RENAME_TARGET;
+	else
+		target_flags = LOOKUP_CREATE | LOOKUP_EXCL;
 retry:
 	error = user_path_at(olddfd, oldname, how, &old_path);
 	if (error)
 		return error;
 
-	new_dentry = user_path_create(newdfd, newname, &new_path,
-					(how & LOOKUP_REVAL));
-	error = PTR_ERR(new_dentry);
-	if (IS_ERR(new_dentry))
-		goto out;
+	to = filename_parentat(newdfd, getname(newname), how & LOOKUP_REVAL,
+			       &new_path, &new_last, &new_type);
+	if (IS_ERR(to)) {
+		error = PTR_ERR(to);
+		goto exit1;
+	}
+
+	if (old_path.mnt != new_path.mnt) {
+		error = -EXDEV;
+		goto exit2;
+	}
+
+	if (new_type != LAST_NORM) {
+		if (flags & AT_LINK_REPLACE)
+			error = -EISDIR;
+		else
+			error = -EEXIST;
+		goto exit2;
+	}
+
+	error = mnt_want_write(old_path.mnt);
+	if (error)
+		goto exit2;
+
+retry_deleg:
+	inode_lock_nested(new_path.dentry->d_inode, I_MUTEX_PARENT);
+
+	new_dentry = __lookup_hash(&new_last, new_path.dentry,
+				   (how & LOOKUP_REVAL) | target_flags);
+	if (IS_ERR(new_dentry)) {
+		error = PTR_ERR(new_dentry);
+		goto exit3;
+	}
+	if (!(flags & AT_LINK_REPLACE) && d_is_positive(new_dentry)) {
+		error = -EEXIST;
+		goto exit4;
+	}
+	if (new_last.name[new_last.len]) {
+		if (d_is_negative(new_dentry)) {
+			error = -ENOENT;
+			goto exit4;
+		}
+		if (!d_is_dir(old_path.dentry)) {
+			error = -ENOTDIR;
+			goto exit4;
+		}
+	}
 
-	error = -EXDEV;
-	if (old_path.mnt != new_path.mnt)
-		goto out_dput;
 	error = may_linkat(&old_path);
 	if (unlikely(error))
-		goto out_dput;
+		goto exit4;
 	error = security_path_link(old_path.dentry, &new_path, new_dentry);
 	if (error)
-		goto out_dput;
-	error = vfs_link(old_path.dentry, new_path.dentry->d_inode, new_dentry, &delegated_inode);
-out_dput:
-	done_path_create(&new_path, new_dentry);
+		goto exit4;
+	error = vfs_link(old_path.dentry, new_path.dentry->d_inode, new_dentry,
+			 &delegated_inode, flags & AT_LINK_REPLACE);
+exit4:
+	dput(new_dentry);
+exit3:
+	inode_unlock(new_path.dentry->d_inode);
 	if (delegated_inode) {
 		error = break_deleg_wait(&delegated_inode);
-		if (!error) {
-			path_put(&old_path);
-			goto retry;
-		}
+		if (!error)
+			goto retry_deleg;
 	}
-	if (retry_estale(error, how)) {
-		path_put(&old_path);
+	mnt_drop_write(old_path.mnt);
+exit2:
+	if (retry_estale(error, how))
+		should_retry = true;
+	path_put(&new_path);
+	putname(to);
+exit1:
+	path_put(&old_path);
+	if (should_retry) {
+		should_retry = false;
 		how |= LOOKUP_REVAL;
 		goto retry;
 	}
-out:
-	path_put(&old_path);
-
 	return error;
 }
 
diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c
index c0dc491537a6..3f9291e76b99 100644
--- a/fs/nfsd/vfs.c
+++ b/fs/nfsd/vfs.c
@@ -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)
diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h
index f283b1d69a9e..b199fc03c891 100644
--- a/fs/overlayfs/overlayfs.h
+++ b/fs/overlayfs/overlayfs.h
@@ -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;
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 3bdb71c97e8f..93eb90eb1fdb 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -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);
diff --git a/include/uapi/linux/fcntl.h b/include/uapi/linux/fcntl.h
index 1f97b33c840e..3704793cd5ab 100644
--- a/include/uapi/linux/fcntl.h
+++ b/include/uapi/linux/fcntl.h
@@ -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 */
-- 
2.25.0


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [RFC PATCH v4 3/4] Btrfs: fix inode reference count leak in btrfs_link() error path
@ 2020-01-29  8:58   ` Omar Sandoval
  0 siblings, 0 replies; 31+ messages in thread
From: Omar Sandoval @ 2020-01-28 23:19 UTC (permalink / raw)
  To: linux-fsdevel, Al Viro; +Cc: kernel-team

From: Omar Sandoval <osandov@fb.com>

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")]
Signed-off-by: Omar Sandoval <osandov@fb.com>
---
 fs/btrfs/inode.c | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index bc7709c4f6eb..8c9a114f48f6 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -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;
 }
-- 
2.25.0


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [RFC PATCH v4 4/4] Btrfs: add support for linkat() AT_REPLACE
@ 2020-01-29  8:58   ` Omar Sandoval
  0 siblings, 0 replies; 31+ messages in thread
From: Omar Sandoval @ 2020-01-28 23:19 UTC (permalink / raw)
  To: linux-fsdevel, Al Viro; +Cc: kernel-team

From: Omar Sandoval <osandov@fb.com>

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.

Signed-off-by: Omar Sandoval <osandov@fb.com>
---
 fs/btrfs/inode.c | 63 +++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 59 insertions(+), 4 deletions(-)

diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index 8c9a114f48f6..b489671d1b5d 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -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) {
-- 
2.25.0


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* Re: [RFC PATCH xfstests] generic: add smoke test for AT_LINK_REPLACE
  2020-01-29  8:58   ` Omar Sandoval
  (?)
@ 2020-01-29  7:02   ` Zorro Lang
  -1 siblings, 0 replies; 31+ messages in thread
From: Zorro Lang @ 2020-01-29  7:02 UTC (permalink / raw)
  To: Omar Sandoval; +Cc: linux-fsdevel, fstests

On Tue, Jan 28, 2020 at 03:18:56PM -0800, Omar Sandoval wrote:
> From: Omar Sandoval <osandov@fb.com>
> 
> Signed-off-by: Omar Sandoval <osandov@fb.com>
> ---

I think you'd better to send this patch to/cc fstests@vger.kernel.org to
get review or merge.

Thanks,
Zorro

>  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
> 
> diff --git a/common/rc b/common/rc
> index eeac1355..257f65a1 100644
> --- a/common/rc
> +++ b/common/rc
> @@ -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")
> diff --git a/tests/generic/593 b/tests/generic/593
> new file mode 100755
> index 00000000..8a9fee02
> --- /dev/null
> +++ b/tests/generic/593
> @@ -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
> diff --git a/tests/generic/593.out b/tests/generic/593.out
> new file mode 100644
> index 00000000..834c34bf
> --- /dev/null
> +++ b/tests/generic/593.out
> @@ -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
> diff --git a/tests/generic/group b/tests/generic/group
> index 6fe62505..0a87efca 100644
> --- a/tests/generic/group
> +++ b/tests/generic/group
> @@ -595,3 +595,4 @@
>  590 auto prealloc preallocrw
>  591 auto quick rw pipe splice
>  592 auto quick encrypt
> +593 auto quick hardlink
> -- 
> 2.25.0
> 


^ permalink raw reply	[flat|nested] 31+ messages in thread

* [RFC PATCH xfstests] generic: add smoke test for AT_LINK_REPLACE
@ 2020-01-29  8:58   ` Omar Sandoval
  0 siblings, 0 replies; 31+ messages in thread
From: Omar Sandoval @ 2020-01-29  8:58 UTC (permalink / raw)
  To: linux-fsdevel, Al Viro
  Cc: kernel-team, linux-api, David Howells, Amir Goldstein, Xi Wang, fstests

From: Omar Sandoval <osandov@fb.com>

Cc: fstests@vger.kernel.org
Signed-off-by: Omar Sandoval <osandov@fb.com>
---
 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

diff --git a/common/rc b/common/rc
index eeac1355..257f65a1 100644
--- a/common/rc
+++ b/common/rc
@@ -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")
diff --git a/tests/generic/593 b/tests/generic/593
new file mode 100755
index 00000000..8a9fee02
--- /dev/null
+++ b/tests/generic/593
@@ -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
diff --git a/tests/generic/593.out b/tests/generic/593.out
new file mode 100644
index 00000000..834c34bf
--- /dev/null
+++ b/tests/generic/593.out
@@ -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
diff --git a/tests/generic/group b/tests/generic/group
index 6fe62505..0a87efca 100644
--- a/tests/generic/group
+++ b/tests/generic/group
@@ -595,3 +595,4 @@
 590 auto prealloc preallocrw
 591 auto quick rw pipe splice
 592 auto quick encrypt
+593 auto quick hardlink
-- 
2.25.0


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [RFC PATCH xfstests] generic: add smoke test for AT_LINK_REPLACE
       [not found] ` <cover.1580251857.git.osandov-b10kYP2dOMg@public.gmane.org>
@ 2020-01-29  8:58   ` Omar Sandoval
  2020-01-29  8:58   ` [RFC PATCH man-pages] link.2: Document new AT_LINK_REPLACE flag Omar Sandoval
                     ` (6 subsequent siblings)
  7 siblings, 0 replies; 31+ messages in thread
From: Omar Sandoval @ 2020-01-29  8:58 UTC (permalink / raw)
  To: linux-fsdevel-u79uwXL29TY76Z2rM5mHXA, Al Viro
  Cc: kernel-team-b10kYP2dOMg, linux-api-u79uwXL29TY76Z2rM5mHXA,
	David Howells, Amir Goldstein, Xi Wang,
	fstests-u79uwXL29TY76Z2rM5mHXA

From: Omar Sandoval <osandov-b10kYP2dOMg@public.gmane.org>

Cc: fstests-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
Signed-off-by: Omar Sandoval <osandov-b10kYP2dOMg@public.gmane.org>
---
 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

diff --git a/common/rc b/common/rc
index eeac1355..257f65a1 100644
--- a/common/rc
+++ b/common/rc
@@ -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")
diff --git a/tests/generic/593 b/tests/generic/593
new file mode 100755
index 00000000..8a9fee02
--- /dev/null
+++ b/tests/generic/593
@@ -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
diff --git a/tests/generic/593.out b/tests/generic/593.out
new file mode 100644
index 00000000..834c34bf
--- /dev/null
+++ b/tests/generic/593.out
@@ -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
diff --git a/tests/generic/group b/tests/generic/group
index 6fe62505..0a87efca 100644
--- a/tests/generic/group
+++ b/tests/generic/group
@@ -595,3 +595,4 @@
 590 auto prealloc preallocrw
 591 auto quick rw pipe splice
 592 auto quick encrypt
+593 auto quick hardlink
-- 
2.25.0

^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [RFC PATCH xfstests] generic: add smoke test for AT_LINK_REPLACE
  2020-01-29  8:58 ` Omar Sandoval
                   ` (7 preceding siblings ...)
  (?)
@ 2020-01-29  8:58 ` Omar Sandoval
  -1 siblings, 0 replies; 31+ messages in thread
From: Omar Sandoval @ 2020-01-29  8:58 UTC (permalink / raw)
  To: linux-fsdevel, Al Viro
  Cc: kernel-team, linux-api, David Howells, Amir Goldstein, Xi Wang, fstests

From: Omar Sandoval <osandov@fb.com>

Cc: fstests@vger.kernel.org
Signed-off-by: Omar Sandoval <osandov@fb.com>
---
 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

diff --git a/common/rc b/common/rc
index eeac1355..257f65a1 100644
--- a/common/rc
+++ b/common/rc
@@ -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")
diff --git a/tests/generic/593 b/tests/generic/593
new file mode 100755
index 00000000..8a9fee02
--- /dev/null
+++ b/tests/generic/593
@@ -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
diff --git a/tests/generic/593.out b/tests/generic/593.out
new file mode 100644
index 00000000..834c34bf
--- /dev/null
+++ b/tests/generic/593.out
@@ -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
diff --git a/tests/generic/group b/tests/generic/group
index 6fe62505..0a87efca 100644
--- a/tests/generic/group
+++ b/tests/generic/group
@@ -595,3 +595,4 @@
 590 auto prealloc preallocrw
 591 auto quick rw pipe splice
 592 auto quick encrypt
+593 auto quick hardlink
-- 
2.25.0

^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [RFC PATCH man-pages] link.2: Document new AT_LINK_REPLACE flag
@ 2020-01-29  8:58   ` Omar Sandoval
  0 siblings, 0 replies; 31+ messages in thread
From: Omar Sandoval @ 2020-01-29  8:58 UTC (permalink / raw)
  To: linux-fsdevel, Al Viro
  Cc: kernel-team, linux-api, David Howells, Amir Goldstein, Xi Wang

From: Omar Sandoval <osandov@fb.com>

Signed-off-by: Omar Sandoval <osandov@fb.com>
---
 man2/link.2 | 191 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 191 insertions(+)

diff --git a/man2/link.2 b/man2/link.2
index 649ba00c7..0097e3071 100644
--- a/man2/link.2
+++ b/man2/link.2
@@ -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),
-- 
2.25.0


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [RFC PATCH man-pages] link.2: Document new AT_LINK_REPLACE flag
       [not found] ` <cover.1580251857.git.osandov-b10kYP2dOMg@public.gmane.org>
  2020-01-29  8:58   ` Omar Sandoval
@ 2020-01-29  8:58   ` Omar Sandoval
  2020-01-29  8:58   ` [RFC PATCH xfsprogs] xfs_io: add support for linkat() AT_LINK_REPLACE Omar Sandoval
                     ` (5 subsequent siblings)
  7 siblings, 0 replies; 31+ messages in thread
From: Omar Sandoval @ 2020-01-29  8:58 UTC (permalink / raw)
  To: linux-fsdevel-u79uwXL29TY76Z2rM5mHXA, Al Viro
  Cc: kernel-team-b10kYP2dOMg, linux-api-u79uwXL29TY76Z2rM5mHXA,
	David Howells, Amir Goldstein, Xi Wang

From: Omar Sandoval <osandov-b10kYP2dOMg@public.gmane.org>

Signed-off-by: Omar Sandoval <osandov-b10kYP2dOMg@public.gmane.org>
---
 man2/link.2 | 191 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 191 insertions(+)

diff --git a/man2/link.2 b/man2/link.2
index 649ba00c7..0097e3071 100644
--- a/man2/link.2
+++ b/man2/link.2
@@ -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),
-- 
2.25.0

^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [RFC PATCH xfsprogs] xfs_io: add support for linkat() AT_LINK_REPLACE
@ 2020-01-29  8:58   ` Omar Sandoval
  0 siblings, 0 replies; 31+ messages in thread
From: Omar Sandoval @ 2020-01-29  8:58 UTC (permalink / raw)
  To: linux-fsdevel, Al Viro
  Cc: kernel-team, linux-api, David Howells, Amir Goldstein, Xi Wang

From: Omar Sandoval <osandov@fb.com>

Signed-off-by: Omar Sandoval <osandov@fb.com>
---
 io/link.c         | 24 ++++++++++++++++++++----
 man/man8/xfs_io.8 |  9 ++++++++-
 2 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/io/link.c b/io/link.c
index f4f4b139..3fc3e24d 100644
--- a/io/link.c
+++ b/io/link.c
@@ -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;
diff --git a/man/man8/xfs_io.8 b/man/man8/xfs_io.8
index c69b295d..f79b3a59 100644
--- a/man/man8/xfs_io.8
+++ b/man/man8/xfs_io.8
@@ -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
-- 
2.25.0


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [RFC PATCH xfsprogs] xfs_io: add support for linkat() AT_LINK_REPLACE
       [not found] ` <cover.1580251857.git.osandov-b10kYP2dOMg@public.gmane.org>
  2020-01-29  8:58   ` Omar Sandoval
  2020-01-29  8:58   ` [RFC PATCH man-pages] link.2: Document new AT_LINK_REPLACE flag Omar Sandoval
@ 2020-01-29  8:58   ` Omar Sandoval
  2020-01-29  8:58   ` [RFC PATCH v4 0/4] fs: add flag to linkat() for replacing destination Omar Sandoval
                     ` (4 subsequent siblings)
  7 siblings, 0 replies; 31+ messages in thread
From: Omar Sandoval @ 2020-01-29  8:58 UTC (permalink / raw)
  To: linux-fsdevel-u79uwXL29TY76Z2rM5mHXA, Al Viro
  Cc: kernel-team-b10kYP2dOMg, linux-api-u79uwXL29TY76Z2rM5mHXA,
	David Howells, Amir Goldstein, Xi Wang

From: Omar Sandoval <osandov-b10kYP2dOMg@public.gmane.org>

Signed-off-by: Omar Sandoval <osandov-b10kYP2dOMg@public.gmane.org>
---
 io/link.c         | 24 ++++++++++++++++++++----
 man/man8/xfs_io.8 |  9 ++++++++-
 2 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/io/link.c b/io/link.c
index f4f4b139..3fc3e24d 100644
--- a/io/link.c
+++ b/io/link.c
@@ -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;
diff --git a/man/man8/xfs_io.8 b/man/man8/xfs_io.8
index c69b295d..f79b3a59 100644
--- a/man/man8/xfs_io.8
+++ b/man/man8/xfs_io.8
@@ -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
-- 
2.25.0

^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [RFC PATCH v4 0/4] fs: add flag to linkat() for replacing destination
@ 2020-01-29  8:58 ` Omar Sandoval
  0 siblings, 0 replies; 31+ messages in thread
From: Omar Sandoval @ 2020-01-29  8:58 UTC (permalink / raw)
  To: linux-fsdevel, Al Viro
  Cc: kernel-team, linux-api, David Howells, Amir Goldstein, Xi Wang

From: Omar Sandoval <osandov@fb.com>

[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/cover.1524549513.git.osandov@fb.com/
2: https://lore.kernel.org/linux-fsdevel/3326.1579019665@warthog.procyon.org.uk/
3: https://lore.kernel.org/linux-fsdevel/364531.1579265357@warthog.procyon.org.uk/
4: https://lore.kernel.org/linux-fsdevel/20190527172655.9287-1-amir73il@gmail.com/

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(-)

-- 
2.25.0


^ permalink raw reply	[flat|nested] 31+ messages in thread

* [RFC PATCH v4 0/4] fs: add flag to linkat() for replacing destination
       [not found] ` <cover.1580251857.git.osandov-b10kYP2dOMg@public.gmane.org>
                     ` (2 preceding siblings ...)
  2020-01-29  8:58   ` [RFC PATCH xfsprogs] xfs_io: add support for linkat() AT_LINK_REPLACE Omar Sandoval
@ 2020-01-29  8:58   ` Omar Sandoval
  2020-01-29  8:58   ` [RFC PATCH v4 1/4] fs: add flags argument to i_op->link() Omar Sandoval
                     ` (3 subsequent siblings)
  7 siblings, 0 replies; 31+ messages in thread
From: Omar Sandoval @ 2020-01-29  8:58 UTC (permalink / raw)
  To: linux-fsdevel-u79uwXL29TY76Z2rM5mHXA, Al Viro
  Cc: kernel-team-b10kYP2dOMg, linux-api-u79uwXL29TY76Z2rM5mHXA,
	David Howells, Amir Goldstein, Xi Wang

From: Omar Sandoval <osandov-b10kYP2dOMg@public.gmane.org>

[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/cover.1524549513.git.osandov-b10kYP2dOMg@public.gmane.org/
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(-)

-- 
2.25.0

^ permalink raw reply	[flat|nested] 31+ messages in thread

* [RFC PATCH v4 1/4] fs: add flags argument to i_op->link()
@ 2020-01-29  8:58   ` Omar Sandoval
  0 siblings, 0 replies; 31+ messages in thread
From: Omar Sandoval @ 2020-01-29  8:58 UTC (permalink / raw)
  To: linux-fsdevel, Al Viro
  Cc: kernel-team, linux-api, David Howells, Amir Goldstein, Xi Wang

From: Omar Sandoval <osandov@fb.com>

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

Signed-off-by: Omar Sandoval <osandov@fb.com>
---
 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(-)

diff --git a/fs/9p/vfs_inode.c b/fs/9p/vfs_inode.c
index b82423a72f68..3cda7788122e 100644
--- a/fs/9p/vfs_inode.c
+++ b/fs/9p/vfs_inode.c
@@ -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);
 
diff --git a/fs/9p/vfs_inode_dotl.c b/fs/9p/vfs_inode_dotl.c
index 60328b21c5fb..636796582f68 100644
--- a/fs/9p/vfs_inode_dotl.c
+++ b/fs/9p/vfs_inode_dotl.c
@@ -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);
 
diff --git a/fs/affs/affs.h b/fs/affs/affs.h
index a755bef7c4c7..98d70fd7ea47 100644
--- a/fs/affs/affs.h
+++ b/fs/affs/affs.h
@@ -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,
diff --git a/fs/affs/namei.c b/fs/affs/namei.c
index 41c5749f4db7..198f2878145d 100644
--- a/fs/affs/namei.c
+++ b/fs/affs/namei.c
@@ -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);
 
diff --git a/fs/afs/dir.c b/fs/afs/dir.c
index 5c794f4b051a..0a17d8d8c009 100644
--- a/fs/afs/dir.c
+++ b/fs/afs/dir.c
@@ -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,
diff --git a/fs/bad_inode.c b/fs/bad_inode.c
index 8035d2a44561..41caa98597d6 100644
--- a/fs/bad_inode.c
+++ b/fs/bad_inode.c
@@ -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;
 }
diff --git a/fs/bfs/dir.c b/fs/bfs/dir.c
index d8dfe3a0cb39..0b0ef8a59a73 100644
--- a/fs/bfs/dir.c
+++ b/fs/bfs/dir.c
@@ -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) {
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index c70baafb2a39..bc7709c4f6eb 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -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;
diff --git a/fs/ceph/dir.c b/fs/ceph/dir.c
index 2e4764fd1872..a1517be5405c 100644
--- a/fs/ceph/dir.c
+++ b/fs/ceph/dir.c
@@ -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;
 
diff --git a/fs/cifs/cifsfs.h b/fs/cifs/cifsfs.h
index b59dc7478130..9360d28255ef 100644
--- a/fs/cifs/cifsfs.h
+++ b/fs/cifs/cifsfs.h
@@ -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 *);
diff --git a/fs/cifs/link.c b/fs/cifs/link.c
index b736acd3917b..1471d4c095d7 100644
--- a/fs/cifs/link.c
+++ b/fs/cifs/link.c
@@ -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);
diff --git a/fs/coda/dir.c b/fs/coda/dir.c
index ca40c2556ba6..acdbc47948cf 100644
--- a/fs/coda/dir.c
+++ b/fs/coda/dir.c
@@ -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;
 
diff --git a/fs/ecryptfs/inode.c b/fs/ecryptfs/inode.c
index e23752d9a79f..eeb351b220b2 100644
--- a/fs/ecryptfs/inode.c
+++ b/fs/ecryptfs/inode.c
@@ -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);
diff --git a/fs/ext2/namei.c b/fs/ext2/namei.c
index ccfbbf59e2fc..9417a96f6ea8 100644
--- a/fs/ext2/namei.c
+++ b/fs/ext2/namei.c
@@ -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;
diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c
index 1cb42d940784..80e27fd907d0 100644
--- a/fs/ext4/namei.c
+++ b/fs/ext4/namei.c
@@ -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;
 
diff --git a/fs/f2fs/namei.c b/fs/f2fs/namei.c
index a1c507b0b4ac..f7ca6fbf5aaf 100644
--- a/fs/f2fs/namei.c
+++ b/fs/f2fs/namei.c
@@ -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))
diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
index ee190119f45c..ae0cf00fd9ec 100644
--- a/fs/fuse/dir.c
+++ b/fs/fuse/dir.c
@@ -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;
diff --git a/fs/gfs2/inode.c b/fs/gfs2/inode.c
index dafef10b91f1..f2f97321b659 100644
--- a/fs/gfs2/inode.c
+++ b/fs/gfs2/inode.c
@@ -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;
 
diff --git a/fs/hfsplus/dir.c b/fs/hfsplus/dir.c
index 29a9dcfbe81f..16cf85e8e51d 100644
--- a/fs/hfsplus/dir.c
+++ b/fs/hfsplus/dir.c
@@ -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))
diff --git a/fs/hostfs/hostfs_kern.c b/fs/hostfs/hostfs_kern.c
index 5a7eb0c79839..f2bd0e42075b 100644
--- a/fs/hostfs/hostfs_kern.c
+++ b/fs/hostfs/hostfs_kern.c
@@ -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);
diff --git a/fs/jffs2/dir.c b/fs/jffs2/dir.c
index f20cff1194bb..c4ec9202ca8e 100644
--- a/fs/jffs2/dir.c
+++ b/fs/jffs2/dir.c
@@ -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;
diff --git a/fs/jfs/namei.c b/fs/jfs/namei.c
index 7a55d14cc1af..45447fca1fdb 100644
--- a/fs/jfs/namei.c
+++ b/fs/jfs/namei.c
@@ -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);
diff --git a/fs/libfs.c b/fs/libfs.c
index 1463b038ffc4..1894d6b202f2 100644
--- a/fs/libfs.c
+++ b/fs/libfs.c
@@ -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);
diff --git a/fs/minix/namei.c b/fs/minix/namei.c
index 1a6084d2b02e..6dc1c4fd4dd6 100644
--- a/fs/minix/namei.c
+++ b/fs/minix/namei.c
@@ -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);
diff --git a/fs/namei.c b/fs/namei.c
index 4fb61e0754ed..9d690df17aed 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -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)) {
diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c
index e180033e35cf..2497d0287043 100644
--- a/fs/nfs/dir.c
+++ b/fs/nfs/dir.c
@@ -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);
 
diff --git a/fs/nfs/internal.h b/fs/nfs/internal.h
index 24a65da58aa9..4f446465ed7b 100644
--- a/fs/nfs/internal.h
+++ b/fs/nfs/internal.h
@@ -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);
diff --git a/fs/nilfs2/namei.c b/fs/nilfs2/namei.c
index 9fe6d4ab74f0..bcbca3f9d947 100644
--- a/fs/nilfs2/namei.c
+++ b/fs/nilfs2/namei.c
@@ -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;
diff --git a/fs/ocfs2/namei.c b/fs/ocfs2/namei.c
index 8ea51cf27b97..c228e7bcb51b 100644
--- a/fs/ocfs2/namei.c
+++ b/fs/ocfs2/namei.c
@@ -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);
diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c
index 29abdb1d3b5c..923d1c93f570 100644
--- a/fs/overlayfs/dir.c
+++ b/fs/overlayfs/dir.c
@@ -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;
diff --git a/fs/reiserfs/namei.c b/fs/reiserfs/namei.c
index 959a066b7bb0..4ff94931f350 100644
--- a/fs/reiserfs/namei.c
+++ b/fs/reiserfs/namei.c
@@ -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;
diff --git a/fs/sysv/namei.c b/fs/sysv/namei.c
index ea2414b385ec..a202feb6ad89 100644
--- a/fs/sysv/namei.c
+++ b/fs/sysv/namei.c
@@ -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);
diff --git a/fs/ubifs/dir.c b/fs/ubifs/dir.c
index 0b98e3c8b461..1fc180266338 100644
--- a/fs/ubifs/dir.c
+++ b/fs/ubifs/dir.c
@@ -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.
diff --git a/fs/udf/namei.c b/fs/udf/namei.c
index 77b6d89b9bcd..263c9daf6aad 100644
--- a/fs/udf/namei.c
+++ b/fs/udf/namei.c
@@ -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;
diff --git a/fs/ufs/namei.c b/fs/ufs/namei.c
index 9ef40f100415..7f8d2e4eac8c 100644
--- a/fs/ufs/namei.c
+++ b/fs/ufs/namei.c
@@ -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);
diff --git a/fs/xfs/xfs_iops.c b/fs/xfs/xfs_iops.c
index 8afe69ca188b..62ecb726fa41 100644
--- a/fs/xfs/xfs_iops.c
+++ b/fs/xfs/xfs_iops.c
@@ -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;
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 98e0349adb52..3bdb71c97e8f 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -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 *,
diff --git a/mm/shmem.c b/mm/shmem.c
index 8793e8cc1a48..ff7c976e19b9 100644
--- a/mm/shmem.c
+++ b/mm/shmem.c
@@ -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
-- 
2.25.0


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [RFC PATCH v4 1/4] fs: add flags argument to i_op->link()
       [not found] ` <cover.1580251857.git.osandov-b10kYP2dOMg@public.gmane.org>
                     ` (3 preceding siblings ...)
  2020-01-29  8:58   ` [RFC PATCH v4 0/4] fs: add flag to linkat() for replacing destination Omar Sandoval
@ 2020-01-29  8:58   ` Omar Sandoval
  2020-01-29  8:58   ` [RFC PATCH v4 2/4] fs: add AT_LINK_REPLACE flag for linkat() which replaces the target Omar Sandoval
                     ` (2 subsequent siblings)
  7 siblings, 0 replies; 31+ messages in thread
From: Omar Sandoval @ 2020-01-29  8:58 UTC (permalink / raw)
  To: linux-fsdevel-u79uwXL29TY76Z2rM5mHXA, Al Viro
  Cc: kernel-team-b10kYP2dOMg, linux-api-u79uwXL29TY76Z2rM5mHXA,
	David Howells, Amir Goldstein, Xi Wang

From: Omar Sandoval <osandov-b10kYP2dOMg@public.gmane.org>

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

Signed-off-by: Omar Sandoval <osandov-b10kYP2dOMg@public.gmane.org>
---
 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(-)

diff --git a/fs/9p/vfs_inode.c b/fs/9p/vfs_inode.c
index b82423a72f68..3cda7788122e 100644
--- a/fs/9p/vfs_inode.c
+++ b/fs/9p/vfs_inode.c
@@ -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);
 
diff --git a/fs/9p/vfs_inode_dotl.c b/fs/9p/vfs_inode_dotl.c
index 60328b21c5fb..636796582f68 100644
--- a/fs/9p/vfs_inode_dotl.c
+++ b/fs/9p/vfs_inode_dotl.c
@@ -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);
 
diff --git a/fs/affs/affs.h b/fs/affs/affs.h
index a755bef7c4c7..98d70fd7ea47 100644
--- a/fs/affs/affs.h
+++ b/fs/affs/affs.h
@@ -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,
diff --git a/fs/affs/namei.c b/fs/affs/namei.c
index 41c5749f4db7..198f2878145d 100644
--- a/fs/affs/namei.c
+++ b/fs/affs/namei.c
@@ -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);
 
diff --git a/fs/afs/dir.c b/fs/afs/dir.c
index 5c794f4b051a..0a17d8d8c009 100644
--- a/fs/afs/dir.c
+++ b/fs/afs/dir.c
@@ -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,
diff --git a/fs/bad_inode.c b/fs/bad_inode.c
index 8035d2a44561..41caa98597d6 100644
--- a/fs/bad_inode.c
+++ b/fs/bad_inode.c
@@ -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;
 }
diff --git a/fs/bfs/dir.c b/fs/bfs/dir.c
index d8dfe3a0cb39..0b0ef8a59a73 100644
--- a/fs/bfs/dir.c
+++ b/fs/bfs/dir.c
@@ -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) {
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index c70baafb2a39..bc7709c4f6eb 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -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;
diff --git a/fs/ceph/dir.c b/fs/ceph/dir.c
index 2e4764fd1872..a1517be5405c 100644
--- a/fs/ceph/dir.c
+++ b/fs/ceph/dir.c
@@ -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;
 
diff --git a/fs/cifs/cifsfs.h b/fs/cifs/cifsfs.h
index b59dc7478130..9360d28255ef 100644
--- a/fs/cifs/cifsfs.h
+++ b/fs/cifs/cifsfs.h
@@ -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 *);
diff --git a/fs/cifs/link.c b/fs/cifs/link.c
index b736acd3917b..1471d4c095d7 100644
--- a/fs/cifs/link.c
+++ b/fs/cifs/link.c
@@ -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);
diff --git a/fs/coda/dir.c b/fs/coda/dir.c
index ca40c2556ba6..acdbc47948cf 100644
--- a/fs/coda/dir.c
+++ b/fs/coda/dir.c
@@ -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;
 
diff --git a/fs/ecryptfs/inode.c b/fs/ecryptfs/inode.c
index e23752d9a79f..eeb351b220b2 100644
--- a/fs/ecryptfs/inode.c
+++ b/fs/ecryptfs/inode.c
@@ -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);
diff --git a/fs/ext2/namei.c b/fs/ext2/namei.c
index ccfbbf59e2fc..9417a96f6ea8 100644
--- a/fs/ext2/namei.c
+++ b/fs/ext2/namei.c
@@ -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;
diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c
index 1cb42d940784..80e27fd907d0 100644
--- a/fs/ext4/namei.c
+++ b/fs/ext4/namei.c
@@ -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;
 
diff --git a/fs/f2fs/namei.c b/fs/f2fs/namei.c
index a1c507b0b4ac..f7ca6fbf5aaf 100644
--- a/fs/f2fs/namei.c
+++ b/fs/f2fs/namei.c
@@ -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))
diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
index ee190119f45c..ae0cf00fd9ec 100644
--- a/fs/fuse/dir.c
+++ b/fs/fuse/dir.c
@@ -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;
diff --git a/fs/gfs2/inode.c b/fs/gfs2/inode.c
index dafef10b91f1..f2f97321b659 100644
--- a/fs/gfs2/inode.c
+++ b/fs/gfs2/inode.c
@@ -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;
 
diff --git a/fs/hfsplus/dir.c b/fs/hfsplus/dir.c
index 29a9dcfbe81f..16cf85e8e51d 100644
--- a/fs/hfsplus/dir.c
+++ b/fs/hfsplus/dir.c
@@ -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))
diff --git a/fs/hostfs/hostfs_kern.c b/fs/hostfs/hostfs_kern.c
index 5a7eb0c79839..f2bd0e42075b 100644
--- a/fs/hostfs/hostfs_kern.c
+++ b/fs/hostfs/hostfs_kern.c
@@ -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);
diff --git a/fs/jffs2/dir.c b/fs/jffs2/dir.c
index f20cff1194bb..c4ec9202ca8e 100644
--- a/fs/jffs2/dir.c
+++ b/fs/jffs2/dir.c
@@ -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;
diff --git a/fs/jfs/namei.c b/fs/jfs/namei.c
index 7a55d14cc1af..45447fca1fdb 100644
--- a/fs/jfs/namei.c
+++ b/fs/jfs/namei.c
@@ -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);
diff --git a/fs/libfs.c b/fs/libfs.c
index 1463b038ffc4..1894d6b202f2 100644
--- a/fs/libfs.c
+++ b/fs/libfs.c
@@ -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);
diff --git a/fs/minix/namei.c b/fs/minix/namei.c
index 1a6084d2b02e..6dc1c4fd4dd6 100644
--- a/fs/minix/namei.c
+++ b/fs/minix/namei.c
@@ -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);
diff --git a/fs/namei.c b/fs/namei.c
index 4fb61e0754ed..9d690df17aed 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -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)) {
diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c
index e180033e35cf..2497d0287043 100644
--- a/fs/nfs/dir.c
+++ b/fs/nfs/dir.c
@@ -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);
 
diff --git a/fs/nfs/internal.h b/fs/nfs/internal.h
index 24a65da58aa9..4f446465ed7b 100644
--- a/fs/nfs/internal.h
+++ b/fs/nfs/internal.h
@@ -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);
diff --git a/fs/nilfs2/namei.c b/fs/nilfs2/namei.c
index 9fe6d4ab74f0..bcbca3f9d947 100644
--- a/fs/nilfs2/namei.c
+++ b/fs/nilfs2/namei.c
@@ -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;
diff --git a/fs/ocfs2/namei.c b/fs/ocfs2/namei.c
index 8ea51cf27b97..c228e7bcb51b 100644
--- a/fs/ocfs2/namei.c
+++ b/fs/ocfs2/namei.c
@@ -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);
diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c
index 29abdb1d3b5c..923d1c93f570 100644
--- a/fs/overlayfs/dir.c
+++ b/fs/overlayfs/dir.c
@@ -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;
diff --git a/fs/reiserfs/namei.c b/fs/reiserfs/namei.c
index 959a066b7bb0..4ff94931f350 100644
--- a/fs/reiserfs/namei.c
+++ b/fs/reiserfs/namei.c
@@ -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;
diff --git a/fs/sysv/namei.c b/fs/sysv/namei.c
index ea2414b385ec..a202feb6ad89 100644
--- a/fs/sysv/namei.c
+++ b/fs/sysv/namei.c
@@ -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);
diff --git a/fs/ubifs/dir.c b/fs/ubifs/dir.c
index 0b98e3c8b461..1fc180266338 100644
--- a/fs/ubifs/dir.c
+++ b/fs/ubifs/dir.c
@@ -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.
diff --git a/fs/udf/namei.c b/fs/udf/namei.c
index 77b6d89b9bcd..263c9daf6aad 100644
--- a/fs/udf/namei.c
+++ b/fs/udf/namei.c
@@ -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;
diff --git a/fs/ufs/namei.c b/fs/ufs/namei.c
index 9ef40f100415..7f8d2e4eac8c 100644
--- a/fs/ufs/namei.c
+++ b/fs/ufs/namei.c
@@ -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);
diff --git a/fs/xfs/xfs_iops.c b/fs/xfs/xfs_iops.c
index 8afe69ca188b..62ecb726fa41 100644
--- a/fs/xfs/xfs_iops.c
+++ b/fs/xfs/xfs_iops.c
@@ -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;
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 98e0349adb52..3bdb71c97e8f 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -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 *,
diff --git a/mm/shmem.c b/mm/shmem.c
index 8793e8cc1a48..ff7c976e19b9 100644
--- a/mm/shmem.c
+++ b/mm/shmem.c
@@ -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
-- 
2.25.0

^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [RFC PATCH v4 2/4] fs: add AT_LINK_REPLACE flag for linkat() which replaces the target
@ 2020-01-29  8:58   ` Omar Sandoval
  0 siblings, 0 replies; 31+ messages in thread
From: Omar Sandoval @ 2020-01-29  8:58 UTC (permalink / raw)
  To: linux-fsdevel, Al Viro
  Cc: kernel-team, linux-api, David Howells, Amir Goldstein, Xi Wang

From: Omar Sandoval <osandov@fb.com>

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().

Cc: Xi Wang <xi@cs.washington.edu>
Signed-off-by: Omar Sandoval <osandov@fb.com>
---
 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(-)

diff --git a/fs/ecryptfs/inode.c b/fs/ecryptfs/inode.c
index eeb351b220b2..2f36b7a61a2f 100644
--- a/fs/ecryptfs/inode.c
+++ b/fs/ecryptfs/inode.c
@@ -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);
diff --git a/fs/namei.c b/fs/namei.c
index 9d690df17aed..78d364e99dca 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -4122,6 +4122,7 @@ SYSCALL_DEFINE2(symlink, const char __user *, oldname, const char __user *, newn
  * @dir:	new parent
  * @new_dentry:	where to create the new link
  * @delegated_inode: returns inode needing a delegation break
+ * @flags:      link flags
  *
  * The caller must hold dir->i_mutex
  *
@@ -4135,16 +4136,25 @@ SYSCALL_DEFINE2(symlink, const char __user *, oldname, const char __user *, newn
  * be appropriate for callers that expect the underlying filesystem not
  * to be NFS exported.
  */
-int vfs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_dentry, struct inode **delegated_inode)
+int vfs_link(struct dentry *old_dentry, struct inode *dir,
+	     struct dentry *new_dentry, struct inode **delegated_inode,
+	     int flags)
 {
 	struct inode *inode = old_dentry->d_inode;
+	struct inode *target = new_dentry->d_inode;
 	unsigned max_links = dir->i_sb->s_max_links;
 	int error;
 
 	if (!inode)
 		return -ENOENT;
 
-	error = may_create(dir, new_dentry);
+	if (target) {
+		if (inode == target)
+			return 0;
+		error = may_delete(dir, new_dentry, false);
+	} else {
+		error = may_create(dir, new_dentry);
+	}
 	if (error)
 		return error;
 
@@ -4172,26 +4182,55 @@ int vfs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_de
 	if (error)
 		return error;
 
-	inode_lock(inode);
+	dget(new_dentry);
+	lock_two_nondirectories(inode, target);
+
+	if (is_local_mountpoint(new_dentry)) {
+		error = -EBUSY;
+		goto out;
+	}
+
 	/* Make sure we don't allow creating hardlink to an unlinked file */
-	if (inode->i_nlink == 0 && !(inode->i_state & I_LINKABLE))
+	if (inode->i_nlink == 0 && !(inode->i_state & I_LINKABLE)) {
 		error =  -ENOENT;
-	else if (max_links && inode->i_nlink >= max_links)
+		goto out;
+	}
+	if (max_links && inode->i_nlink >= max_links) {
 		error = -EMLINK;
-	else {
-		error = try_break_deleg(inode, delegated_inode);
-		if (!error)
-			error = dir->i_op->link(old_dentry, dir, new_dentry, 0);
+		goto out;
+	}
+
+	error = try_break_deleg(inode, delegated_inode);
+	if (error)
+		goto out;
+	if (target) {
+		error = try_break_deleg(target, delegated_inode);
+		if (error)
+			goto out;
+	}
+
+	error = dir->i_op->link(old_dentry, dir, new_dentry, flags);
+	if (error)
+		goto out;
+
+	if (target) {
+		dont_mount(new_dentry);
+		detach_mounts(new_dentry);
 	}
 
-	if (!error && (inode->i_state & I_LINKABLE)) {
+	if (inode->i_state & I_LINKABLE) {
 		spin_lock(&inode->i_lock);
 		inode->i_state &= ~I_LINKABLE;
 		spin_unlock(&inode->i_lock);
 	}
-	inode_unlock(inode);
-	if (!error)
+out:
+	unlock_two_nondirectories(inode, target);
+	dput(new_dentry);
+	if (!error) {
+		if (target)
+			fsnotify_link_count(target);
 		fsnotify_link(dir, inode, new_dentry);
+	}
 	return error;
 }
 EXPORT_SYMBOL(vfs_link);
@@ -4210,11 +4249,16 @@ int do_linkat(int olddfd, const char __user *oldname, int newdfd,
 {
 	struct dentry *new_dentry;
 	struct path old_path, new_path;
+	struct qstr new_last;
+	int new_type;
 	struct inode *delegated_inode = NULL;
-	int how = 0;
+	struct filename *to;
+	unsigned int how = 0, target_flags;
+	bool should_retry = false;
 	int error;
 
-	if ((flags & ~(AT_SYMLINK_FOLLOW | AT_EMPTY_PATH)) != 0)
+	if ((flags & ~(AT_SYMLINK_FOLLOW | AT_EMPTY_PATH |
+		       AT_LINK_REPLACE)) != 0)
 		return -EINVAL;
 	/*
 	 * To use null names we require CAP_DAC_READ_SEARCH
@@ -4229,44 +4273,94 @@ int do_linkat(int olddfd, const char __user *oldname, int newdfd,
 
 	if (flags & AT_SYMLINK_FOLLOW)
 		how |= LOOKUP_FOLLOW;
+
+	if (flags & AT_LINK_REPLACE)
+		target_flags = LOOKUP_RENAME_TARGET;
+	else
+		target_flags = LOOKUP_CREATE | LOOKUP_EXCL;
 retry:
 	error = user_path_at(olddfd, oldname, how, &old_path);
 	if (error)
 		return error;
 
-	new_dentry = user_path_create(newdfd, newname, &new_path,
-					(how & LOOKUP_REVAL));
-	error = PTR_ERR(new_dentry);
-	if (IS_ERR(new_dentry))
-		goto out;
+	to = filename_parentat(newdfd, getname(newname), how & LOOKUP_REVAL,
+			       &new_path, &new_last, &new_type);
+	if (IS_ERR(to)) {
+		error = PTR_ERR(to);
+		goto exit1;
+	}
+
+	if (old_path.mnt != new_path.mnt) {
+		error = -EXDEV;
+		goto exit2;
+	}
+
+	if (new_type != LAST_NORM) {
+		if (flags & AT_LINK_REPLACE)
+			error = -EISDIR;
+		else
+			error = -EEXIST;
+		goto exit2;
+	}
+
+	error = mnt_want_write(old_path.mnt);
+	if (error)
+		goto exit2;
+
+retry_deleg:
+	inode_lock_nested(new_path.dentry->d_inode, I_MUTEX_PARENT);
+
+	new_dentry = __lookup_hash(&new_last, new_path.dentry,
+				   (how & LOOKUP_REVAL) | target_flags);
+	if (IS_ERR(new_dentry)) {
+		error = PTR_ERR(new_dentry);
+		goto exit3;
+	}
+	if (!(flags & AT_LINK_REPLACE) && d_is_positive(new_dentry)) {
+		error = -EEXIST;
+		goto exit4;
+	}
+	if (new_last.name[new_last.len]) {
+		if (d_is_negative(new_dentry)) {
+			error = -ENOENT;
+			goto exit4;
+		}
+		if (!d_is_dir(old_path.dentry)) {
+			error = -ENOTDIR;
+			goto exit4;
+		}
+	}
 
-	error = -EXDEV;
-	if (old_path.mnt != new_path.mnt)
-		goto out_dput;
 	error = may_linkat(&old_path);
 	if (unlikely(error))
-		goto out_dput;
+		goto exit4;
 	error = security_path_link(old_path.dentry, &new_path, new_dentry);
 	if (error)
-		goto out_dput;
-	error = vfs_link(old_path.dentry, new_path.dentry->d_inode, new_dentry, &delegated_inode);
-out_dput:
-	done_path_create(&new_path, new_dentry);
+		goto exit4;
+	error = vfs_link(old_path.dentry, new_path.dentry->d_inode, new_dentry,
+			 &delegated_inode, flags & AT_LINK_REPLACE);
+exit4:
+	dput(new_dentry);
+exit3:
+	inode_unlock(new_path.dentry->d_inode);
 	if (delegated_inode) {
 		error = break_deleg_wait(&delegated_inode);
-		if (!error) {
-			path_put(&old_path);
-			goto retry;
-		}
+		if (!error)
+			goto retry_deleg;
 	}
-	if (retry_estale(error, how)) {
-		path_put(&old_path);
+	mnt_drop_write(old_path.mnt);
+exit2:
+	if (retry_estale(error, how))
+		should_retry = true;
+	path_put(&new_path);
+	putname(to);
+exit1:
+	path_put(&old_path);
+	if (should_retry) {
+		should_retry = false;
 		how |= LOOKUP_REVAL;
 		goto retry;
 	}
-out:
-	path_put(&old_path);
-
 	return error;
 }
 
diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c
index c0dc491537a6..3f9291e76b99 100644
--- a/fs/nfsd/vfs.c
+++ b/fs/nfsd/vfs.c
@@ -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)
diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h
index f283b1d69a9e..b199fc03c891 100644
--- a/fs/overlayfs/overlayfs.h
+++ b/fs/overlayfs/overlayfs.h
@@ -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;
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 3bdb71c97e8f..93eb90eb1fdb 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -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);
diff --git a/include/uapi/linux/fcntl.h b/include/uapi/linux/fcntl.h
index 1f97b33c840e..3704793cd5ab 100644
--- a/include/uapi/linux/fcntl.h
+++ b/include/uapi/linux/fcntl.h
@@ -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 */
-- 
2.25.0


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [RFC PATCH v4 2/4] fs: add AT_LINK_REPLACE flag for linkat() which replaces the target
       [not found] ` <cover.1580251857.git.osandov-b10kYP2dOMg@public.gmane.org>
                     ` (4 preceding siblings ...)
  2020-01-29  8:58   ` [RFC PATCH v4 1/4] fs: add flags argument to i_op->link() Omar Sandoval
@ 2020-01-29  8:58   ` Omar Sandoval
  2020-01-29  8:58   ` [RFC PATCH v4 3/4] Btrfs: fix inode reference count leak in btrfs_link() error path Omar Sandoval
  2020-01-29  8:58   ` [RFC PATCH v4 4/4] Btrfs: add support for linkat() AT_REPLACE Omar Sandoval
  7 siblings, 0 replies; 31+ messages in thread
From: Omar Sandoval @ 2020-01-29  8:58 UTC (permalink / raw)
  To: linux-fsdevel-u79uwXL29TY76Z2rM5mHXA, Al Viro
  Cc: kernel-team-b10kYP2dOMg, linux-api-u79uwXL29TY76Z2rM5mHXA,
	David Howells, Amir Goldstein, Xi Wang

From: Omar Sandoval <osandov-b10kYP2dOMg@public.gmane.org>

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().

Cc: Xi Wang <xi-GmWTxIRN22iJaUV4rX00uodd74u8MsAO@public.gmane.org>
Signed-off-by: Omar Sandoval <osandov-b10kYP2dOMg@public.gmane.org>
---
 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(-)

diff --git a/fs/ecryptfs/inode.c b/fs/ecryptfs/inode.c
index eeb351b220b2..2f36b7a61a2f 100644
--- a/fs/ecryptfs/inode.c
+++ b/fs/ecryptfs/inode.c
@@ -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);
diff --git a/fs/namei.c b/fs/namei.c
index 9d690df17aed..78d364e99dca 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -4122,6 +4122,7 @@ SYSCALL_DEFINE2(symlink, const char __user *, oldname, const char __user *, newn
  * @dir:	new parent
  * @new_dentry:	where to create the new link
  * @delegated_inode: returns inode needing a delegation break
+ * @flags:      link flags
  *
  * The caller must hold dir->i_mutex
  *
@@ -4135,16 +4136,25 @@ SYSCALL_DEFINE2(symlink, const char __user *, oldname, const char __user *, newn
  * be appropriate for callers that expect the underlying filesystem not
  * to be NFS exported.
  */
-int vfs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_dentry, struct inode **delegated_inode)
+int vfs_link(struct dentry *old_dentry, struct inode *dir,
+	     struct dentry *new_dentry, struct inode **delegated_inode,
+	     int flags)
 {
 	struct inode *inode = old_dentry->d_inode;
+	struct inode *target = new_dentry->d_inode;
 	unsigned max_links = dir->i_sb->s_max_links;
 	int error;
 
 	if (!inode)
 		return -ENOENT;
 
-	error = may_create(dir, new_dentry);
+	if (target) {
+		if (inode == target)
+			return 0;
+		error = may_delete(dir, new_dentry, false);
+	} else {
+		error = may_create(dir, new_dentry);
+	}
 	if (error)
 		return error;
 
@@ -4172,26 +4182,55 @@ int vfs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_de
 	if (error)
 		return error;
 
-	inode_lock(inode);
+	dget(new_dentry);
+	lock_two_nondirectories(inode, target);
+
+	if (is_local_mountpoint(new_dentry)) {
+		error = -EBUSY;
+		goto out;
+	}
+
 	/* Make sure we don't allow creating hardlink to an unlinked file */
-	if (inode->i_nlink == 0 && !(inode->i_state & I_LINKABLE))
+	if (inode->i_nlink == 0 && !(inode->i_state & I_LINKABLE)) {
 		error =  -ENOENT;
-	else if (max_links && inode->i_nlink >= max_links)
+		goto out;
+	}
+	if (max_links && inode->i_nlink >= max_links) {
 		error = -EMLINK;
-	else {
-		error = try_break_deleg(inode, delegated_inode);
-		if (!error)
-			error = dir->i_op->link(old_dentry, dir, new_dentry, 0);
+		goto out;
+	}
+
+	error = try_break_deleg(inode, delegated_inode);
+	if (error)
+		goto out;
+	if (target) {
+		error = try_break_deleg(target, delegated_inode);
+		if (error)
+			goto out;
+	}
+
+	error = dir->i_op->link(old_dentry, dir, new_dentry, flags);
+	if (error)
+		goto out;
+
+	if (target) {
+		dont_mount(new_dentry);
+		detach_mounts(new_dentry);
 	}
 
-	if (!error && (inode->i_state & I_LINKABLE)) {
+	if (inode->i_state & I_LINKABLE) {
 		spin_lock(&inode->i_lock);
 		inode->i_state &= ~I_LINKABLE;
 		spin_unlock(&inode->i_lock);
 	}
-	inode_unlock(inode);
-	if (!error)
+out:
+	unlock_two_nondirectories(inode, target);
+	dput(new_dentry);
+	if (!error) {
+		if (target)
+			fsnotify_link_count(target);
 		fsnotify_link(dir, inode, new_dentry);
+	}
 	return error;
 }
 EXPORT_SYMBOL(vfs_link);
@@ -4210,11 +4249,16 @@ int do_linkat(int olddfd, const char __user *oldname, int newdfd,
 {
 	struct dentry *new_dentry;
 	struct path old_path, new_path;
+	struct qstr new_last;
+	int new_type;
 	struct inode *delegated_inode = NULL;
-	int how = 0;
+	struct filename *to;
+	unsigned int how = 0, target_flags;
+	bool should_retry = false;
 	int error;
 
-	if ((flags & ~(AT_SYMLINK_FOLLOW | AT_EMPTY_PATH)) != 0)
+	if ((flags & ~(AT_SYMLINK_FOLLOW | AT_EMPTY_PATH |
+		       AT_LINK_REPLACE)) != 0)
 		return -EINVAL;
 	/*
 	 * To use null names we require CAP_DAC_READ_SEARCH
@@ -4229,44 +4273,94 @@ int do_linkat(int olddfd, const char __user *oldname, int newdfd,
 
 	if (flags & AT_SYMLINK_FOLLOW)
 		how |= LOOKUP_FOLLOW;
+
+	if (flags & AT_LINK_REPLACE)
+		target_flags = LOOKUP_RENAME_TARGET;
+	else
+		target_flags = LOOKUP_CREATE | LOOKUP_EXCL;
 retry:
 	error = user_path_at(olddfd, oldname, how, &old_path);
 	if (error)
 		return error;
 
-	new_dentry = user_path_create(newdfd, newname, &new_path,
-					(how & LOOKUP_REVAL));
-	error = PTR_ERR(new_dentry);
-	if (IS_ERR(new_dentry))
-		goto out;
+	to = filename_parentat(newdfd, getname(newname), how & LOOKUP_REVAL,
+			       &new_path, &new_last, &new_type);
+	if (IS_ERR(to)) {
+		error = PTR_ERR(to);
+		goto exit1;
+	}
+
+	if (old_path.mnt != new_path.mnt) {
+		error = -EXDEV;
+		goto exit2;
+	}
+
+	if (new_type != LAST_NORM) {
+		if (flags & AT_LINK_REPLACE)
+			error = -EISDIR;
+		else
+			error = -EEXIST;
+		goto exit2;
+	}
+
+	error = mnt_want_write(old_path.mnt);
+	if (error)
+		goto exit2;
+
+retry_deleg:
+	inode_lock_nested(new_path.dentry->d_inode, I_MUTEX_PARENT);
+
+	new_dentry = __lookup_hash(&new_last, new_path.dentry,
+				   (how & LOOKUP_REVAL) | target_flags);
+	if (IS_ERR(new_dentry)) {
+		error = PTR_ERR(new_dentry);
+		goto exit3;
+	}
+	if (!(flags & AT_LINK_REPLACE) && d_is_positive(new_dentry)) {
+		error = -EEXIST;
+		goto exit4;
+	}
+	if (new_last.name[new_last.len]) {
+		if (d_is_negative(new_dentry)) {
+			error = -ENOENT;
+			goto exit4;
+		}
+		if (!d_is_dir(old_path.dentry)) {
+			error = -ENOTDIR;
+			goto exit4;
+		}
+	}
 
-	error = -EXDEV;
-	if (old_path.mnt != new_path.mnt)
-		goto out_dput;
 	error = may_linkat(&old_path);
 	if (unlikely(error))
-		goto out_dput;
+		goto exit4;
 	error = security_path_link(old_path.dentry, &new_path, new_dentry);
 	if (error)
-		goto out_dput;
-	error = vfs_link(old_path.dentry, new_path.dentry->d_inode, new_dentry, &delegated_inode);
-out_dput:
-	done_path_create(&new_path, new_dentry);
+		goto exit4;
+	error = vfs_link(old_path.dentry, new_path.dentry->d_inode, new_dentry,
+			 &delegated_inode, flags & AT_LINK_REPLACE);
+exit4:
+	dput(new_dentry);
+exit3:
+	inode_unlock(new_path.dentry->d_inode);
 	if (delegated_inode) {
 		error = break_deleg_wait(&delegated_inode);
-		if (!error) {
-			path_put(&old_path);
-			goto retry;
-		}
+		if (!error)
+			goto retry_deleg;
 	}
-	if (retry_estale(error, how)) {
-		path_put(&old_path);
+	mnt_drop_write(old_path.mnt);
+exit2:
+	if (retry_estale(error, how))
+		should_retry = true;
+	path_put(&new_path);
+	putname(to);
+exit1:
+	path_put(&old_path);
+	if (should_retry) {
+		should_retry = false;
 		how |= LOOKUP_REVAL;
 		goto retry;
 	}
-out:
-	path_put(&old_path);
-
 	return error;
 }
 
diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c
index c0dc491537a6..3f9291e76b99 100644
--- a/fs/nfsd/vfs.c
+++ b/fs/nfsd/vfs.c
@@ -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)
diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h
index f283b1d69a9e..b199fc03c891 100644
--- a/fs/overlayfs/overlayfs.h
+++ b/fs/overlayfs/overlayfs.h
@@ -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;
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 3bdb71c97e8f..93eb90eb1fdb 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -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);
diff --git a/include/uapi/linux/fcntl.h b/include/uapi/linux/fcntl.h
index 1f97b33c840e..3704793cd5ab 100644
--- a/include/uapi/linux/fcntl.h
+++ b/include/uapi/linux/fcntl.h
@@ -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 */
-- 
2.25.0

^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [RFC PATCH v4 3/4] Btrfs: fix inode reference count leak in btrfs_link() error path
@ 2020-01-29  8:58   ` Omar Sandoval
  0 siblings, 0 replies; 31+ messages in thread
From: Omar Sandoval @ 2020-01-29  8:58 UTC (permalink / raw)
  To: linux-fsdevel, Al Viro
  Cc: kernel-team, linux-api, David Howells, Amir Goldstein, Xi Wang

From: Omar Sandoval <osandov@fb.com>

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")]
Signed-off-by: Omar Sandoval <osandov@fb.com>
---
 fs/btrfs/inode.c | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index bc7709c4f6eb..8c9a114f48f6 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -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;
 }
-- 
2.25.0


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [RFC PATCH v4 3/4] Btrfs: fix inode reference count leak in btrfs_link() error path
       [not found] ` <cover.1580251857.git.osandov-b10kYP2dOMg@public.gmane.org>
                     ` (5 preceding siblings ...)
  2020-01-29  8:58   ` [RFC PATCH v4 2/4] fs: add AT_LINK_REPLACE flag for linkat() which replaces the target Omar Sandoval
@ 2020-01-29  8:58   ` Omar Sandoval
  2020-01-29  8:58   ` [RFC PATCH v4 4/4] Btrfs: add support for linkat() AT_REPLACE Omar Sandoval
  7 siblings, 0 replies; 31+ messages in thread
From: Omar Sandoval @ 2020-01-29  8:58 UTC (permalink / raw)
  To: linux-fsdevel-u79uwXL29TY76Z2rM5mHXA, Al Viro
  Cc: kernel-team-b10kYP2dOMg, linux-api-u79uwXL29TY76Z2rM5mHXA,
	David Howells, Amir Goldstein, Xi Wang

From: Omar Sandoval <osandov-b10kYP2dOMg@public.gmane.org>

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")]
Signed-off-by: Omar Sandoval <osandov-b10kYP2dOMg@public.gmane.org>
---
 fs/btrfs/inode.c | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index bc7709c4f6eb..8c9a114f48f6 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -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;
 }
-- 
2.25.0

^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [RFC PATCH v4 4/4] Btrfs: add support for linkat() AT_REPLACE
@ 2020-01-29  8:58   ` Omar Sandoval
  0 siblings, 0 replies; 31+ messages in thread
From: Omar Sandoval @ 2020-01-29  8:58 UTC (permalink / raw)
  To: linux-fsdevel, Al Viro
  Cc: kernel-team, linux-api, David Howells, Amir Goldstein, Xi Wang

From: Omar Sandoval <osandov@fb.com>

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.

Signed-off-by: Omar Sandoval <osandov@fb.com>
---
 fs/btrfs/inode.c | 63 +++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 59 insertions(+), 4 deletions(-)

diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index 8c9a114f48f6..b489671d1b5d 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -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) {
-- 
2.25.0


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [RFC PATCH v4 4/4] Btrfs: add support for linkat() AT_REPLACE
       [not found] ` <cover.1580251857.git.osandov-b10kYP2dOMg@public.gmane.org>
                     ` (6 preceding siblings ...)
  2020-01-29  8:58   ` [RFC PATCH v4 3/4] Btrfs: fix inode reference count leak in btrfs_link() error path Omar Sandoval
@ 2020-01-29  8:58   ` Omar Sandoval
  7 siblings, 0 replies; 31+ messages in thread
From: Omar Sandoval @ 2020-01-29  8:58 UTC (permalink / raw)
  To: linux-fsdevel-u79uwXL29TY76Z2rM5mHXA, Al Viro
  Cc: kernel-team-b10kYP2dOMg, linux-api-u79uwXL29TY76Z2rM5mHXA,
	David Howells, Amir Goldstein, Xi Wang

From: Omar Sandoval <osandov-b10kYP2dOMg@public.gmane.org>

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.

Signed-off-by: Omar Sandoval <osandov-b10kYP2dOMg@public.gmane.org>
---
 fs/btrfs/inode.c | 63 +++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 59 insertions(+), 4 deletions(-)

diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index 8c9a114f48f6..b489671d1b5d 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -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) {
-- 
2.25.0

^ permalink raw reply related	[flat|nested] 31+ messages in thread

* Re: [RFC PATCH xfsprogs] xfs_io: add support for linkat() AT_LINK_REPLACE
  2020-01-29  8:58   ` Omar Sandoval
  (?)
@ 2020-01-30  4:42   ` Zorro Lang
  -1 siblings, 0 replies; 31+ messages in thread
From: Zorro Lang @ 2020-01-30  4:42 UTC (permalink / raw)
  To: Omar Sandoval; +Cc: linux-fsdevel, linux-xfs

On Wed, Jan 29, 2020 at 12:58:29AM -0800, Omar Sandoval wrote:
> From: Omar Sandoval <osandov@fb.com>
> 
> Signed-off-by: Omar Sandoval <osandov@fb.com>
> ---

And this patch would be better to send to/cc linux-xfs@vger.kernel.org to get
xfsprogs maintainers/developers review.

>  io/link.c         | 24 ++++++++++++++++++++----
>  man/man8/xfs_io.8 |  9 ++++++++-
>  2 files changed, 28 insertions(+), 5 deletions(-)
> 
> diff --git a/io/link.c b/io/link.c
> index f4f4b139..3fc3e24d 100644
> --- a/io/link.c
> +++ b/io/link.c
> @@ -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;
> diff --git a/man/man8/xfs_io.8 b/man/man8/xfs_io.8
> index c69b295d..f79b3a59 100644
> --- a/man/man8/xfs_io.8
> +++ b/man/man8/xfs_io.8
> @@ -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
> -- 
> 2.25.0
> 


^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [RFC PATCH v4 1/4] fs: add flags argument to i_op->link()
  2020-01-29  8:58 ` Omar Sandoval
                   ` (9 preceding siblings ...)
  (?)
@ 2020-01-31 13:48 ` David Howells
  2020-01-31 20:24     ` Omar Sandoval
  -1 siblings, 1 reply; 31+ messages in thread
From: David Howells @ 2020-01-31 13:48 UTC (permalink / raw)
  To: Omar Sandoval
  Cc: dhowells, linux-fsdevel, Al Viro, kernel-team, linux-api,
	Amir Goldstein, Xi Wang

Omar Sandoval <osandov@osandov.com> wrote:

> -	int (*link) (struct dentry *,struct inode *,struct dentry *);
> +	int (*link) (struct dentry *,struct inode *,struct dentry *, int);

Can you make it unsigned int?

David


^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [RFC PATCH v4 1/4] fs: add flags argument to i_op->link()
@ 2020-01-31 20:24     ` Omar Sandoval
  0 siblings, 0 replies; 31+ messages in thread
From: Omar Sandoval @ 2020-01-31 20:24 UTC (permalink / raw)
  To: David Howells
  Cc: linux-fsdevel, Al Viro, kernel-team, linux-api, Amir Goldstein, Xi Wang

On Fri, Jan 31, 2020 at 01:48:42PM +0000, David Howells wrote:
> Omar Sandoval <osandov@osandov.com> wrote:
> 
> > -	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.

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [RFC PATCH v4 1/4] fs: add flags argument to i_op->link()
@ 2020-01-31 20:24     ` Omar Sandoval
  0 siblings, 0 replies; 31+ messages in thread
From: Omar Sandoval @ 2020-01-31 20:24 UTC (permalink / raw)
  To: David Howells
  Cc: linux-fsdevel-u79uwXL29TY76Z2rM5mHXA, Al Viro,
	kernel-team-b10kYP2dOMg, linux-api-u79uwXL29TY76Z2rM5mHXA,
	Amir Goldstein, Xi Wang

On Fri, Jan 31, 2020 at 01:48:42PM +0000, David Howells wrote:
> Omar Sandoval <osandov-nWWhXC5lh1RBDgjK7y7TUQ@public.gmane.org> wrote:
> 
> > -	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.

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [RFC PATCH xfstests] generic: add smoke test for AT_LINK_REPLACE
  2020-01-29  8:58   ` Omar Sandoval
  (?)
  (?)
@ 2020-02-23 14:46   ` Eryu Guan
  -1 siblings, 0 replies; 31+ messages in thread
From: Eryu Guan @ 2020-02-23 14:46 UTC (permalink / raw)
  To: Omar Sandoval
  Cc: linux-fsdevel, Al Viro, kernel-team, linux-api, David Howells,
	Amir Goldstein, Xi Wang, fstests

On Wed, Jan 29, 2020 at 12:58:27AM -0800, Omar Sandoval wrote:
> From: Omar Sandoval <osandov@fb.com>
> 
> Cc: fstests@vger.kernel.org
> Signed-off-by: Omar Sandoval <osandov@fb.com>

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.

> ---
>  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
> 
> diff --git a/common/rc b/common/rc
> index eeac1355..257f65a1 100644
> --- a/common/rc
> +++ b/common/rc
> @@ -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")
> diff --git a/tests/generic/593 b/tests/generic/593
> new file mode 100755
> index 00000000..8a9fee02
> --- /dev/null
> +++ b/tests/generic/593
> @@ -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 \

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

Thanks,
Eryu

> +	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
> diff --git a/tests/generic/593.out b/tests/generic/593.out
> new file mode 100644
> index 00000000..834c34bf
> --- /dev/null
> +++ b/tests/generic/593.out
> @@ -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
> diff --git a/tests/generic/group b/tests/generic/group
> index 6fe62505..0a87efca 100644
> --- a/tests/generic/group
> +++ b/tests/generic/group
> @@ -595,3 +595,4 @@
>  590 auto prealloc preallocrw
>  591 auto quick rw pipe splice
>  592 auto quick encrypt
> +593 auto quick hardlink
> -- 
> 2.25.0
> 

^ permalink raw reply	[flat|nested] 31+ messages in thread

end of thread, other threads:[~2020-02-23 14:46 UTC | newest]

Thread overview: 31+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-01-28 23:18 [RFC PATCH v4 0/4] fs: add flag to linkat() for replacing destination Omar Sandoval
2020-01-29  8:58 ` Omar Sandoval
2020-01-28 23:18 ` [RFC PATCH xfstests] generic: add smoke test for AT_LINK_REPLACE Omar Sandoval
2020-01-29  8:58   ` Omar Sandoval
2020-01-29  7:02   ` Zorro Lang
2020-02-23 14:46   ` Eryu Guan
2020-01-28 23:18 ` [RFC PATCH man-pages] link.2: Document new AT_LINK_REPLACE flag Omar Sandoval
2020-01-29  8:58   ` Omar Sandoval
2020-01-28 23:18 ` [RFC PATCH xfsprogs] xfs_io: add support for linkat() AT_LINK_REPLACE Omar Sandoval
2020-01-29  8:58   ` Omar Sandoval
2020-01-30  4:42   ` Zorro Lang
2020-01-28 23:19 ` [RFC PATCH v4 1/4] fs: add flags argument to i_op->link() Omar Sandoval
2020-01-29  8:58   ` Omar Sandoval
2020-01-28 23:19 ` [RFC PATCH v4 2/4] fs: add AT_LINK_REPLACE flag for linkat() which replaces the target Omar Sandoval
2020-01-29  8:58   ` Omar Sandoval
2020-01-28 23:19 ` [RFC PATCH v4 3/4] Btrfs: fix inode reference count leak in btrfs_link() error path Omar Sandoval
2020-01-29  8:58   ` Omar Sandoval
2020-01-28 23:19 ` [RFC PATCH v4 4/4] Btrfs: add support for linkat() AT_REPLACE Omar Sandoval
2020-01-29  8:58   ` Omar Sandoval
2020-01-29  8:58 ` [RFC PATCH xfstests] generic: add smoke test for AT_LINK_REPLACE Omar Sandoval
     [not found] ` <cover.1580251857.git.osandov-b10kYP2dOMg@public.gmane.org>
2020-01-29  8:58   ` Omar Sandoval
2020-01-29  8:58   ` [RFC PATCH man-pages] link.2: Document new AT_LINK_REPLACE flag Omar Sandoval
2020-01-29  8:58   ` [RFC PATCH xfsprogs] xfs_io: add support for linkat() AT_LINK_REPLACE Omar Sandoval
2020-01-29  8:58   ` [RFC PATCH v4 0/4] fs: add flag to linkat() for replacing destination Omar Sandoval
2020-01-29  8:58   ` [RFC PATCH v4 1/4] fs: add flags argument to i_op->link() Omar Sandoval
2020-01-29  8:58   ` [RFC PATCH v4 2/4] fs: add AT_LINK_REPLACE flag for linkat() which replaces the target Omar Sandoval
2020-01-29  8:58   ` [RFC PATCH v4 3/4] Btrfs: fix inode reference count leak in btrfs_link() error path Omar Sandoval
2020-01-29  8:58   ` [RFC PATCH v4 4/4] Btrfs: add support for linkat() AT_REPLACE Omar Sandoval
2020-01-31 13:48 ` [RFC PATCH v4 1/4] fs: add flags argument to i_op->link() David Howells
2020-01-31 20:24   ` Omar Sandoval
2020-01-31 20:24     ` Omar Sandoval

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.