fs: add flag to linkat() for replacing destination
Subsystems (29)
RFC PATCH 1/1 link.2: Document new AT_LINK_REPLACE flag
In reply to Omar SandovalFrom: Omar Sandoval <[email protected]>
--- man2/link.2 | 191 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 191 insertions(+)
man2/link.2 +191 -0
@@ -174,6 +174,60 @@ like this: linkat(AT_FDCWD, "/proc/self/fd/<fd>", newdirfd, newname, AT_SYMLINK_FOLLOW); .EE +.TP +.BR AT_LINK_REPLACE " (since Linux 5.7)" +If +.I newpath +exists, replace it atomically. +There is no point at which another process attempting to access +.I newpath +will find it missing. +If +.I newpath +exists but the operation fails, +the original entry specified by +.I newpath +will remain in place. +This does not guarantee data integrity; +see EXAMPLE below for how to use this for crash-safe file replacement with +.BR O_TMPFILE . +.IP +If +.I newpath +is replaced, +any other hard links referring to the original file are unaffected. +Open file descriptors for +.I newpath +are also unaffected. +.IP +.I newpath +must not be a directory. +.IP +If the entry specified by +.I newpath +refers to the file specified by +.I oldpath, +.BR linkat () +does nothing and returns a success status. +Note that this comparison does not follow mounts on +.IR newpath . +.IP +Otherwise, +.I newpath +must not be a mount point in the local namespace. +If it is a mount point in another namespace and the operation succeeds, +all mounts are detached from +.I newpath +in all namespaces, as is the case for +.BR rename (2), +.BR rmdir (2), +and +.BR unlink (2). +.IP +If +.I newpath +refers to a symbolic link, +the link will be replaced. .in .PP Before kernel 2.6.18, the
@@ -293,10 +347,34 @@ or .I newdirfd is not a valid file descriptor. .TP +.B EBUSY +.B AT_LINK_REPLACE +was specified in +.IR flags , +.I newpath +does not refer to the file specified by +.IR oldpath , +and +.I newpath +is in use by the system +(for example, it is a mount point in the local namespace). +.TP .B EINVAL An invalid flag value was specified in .IR flags . .TP +.B EINVAL +The filesystem does not support one of the flags in +.IR flags . +.TP +.B EISDIR +.B AT_LINK_REPLACE +was specified in +.I flags +and +.I newpath +refers to an existing directory. +.TP .B ENOENT .B AT_EMPTY_PATH was specified in
@@ -344,6 +422,31 @@ was specified in is an empty string, and .IR olddirfd refers to a directory. +.TP +.B EPERM +.B AT_LINK_REPLACE +was specified in +.I flags +and +.I newpath +refers to an immutable or append-only file +or a file in an immutable or append-only directory. +(See +.BR ioctl_iflags (2).) +.TP +.BR EPERM " or " EACCES +.B AT_LINK_REPLACE +was specified in +.IR flags , +the directory containing +.I newpath +has the sticky bit +.RB ( S_ISVTX ) +set, and the process's effective UID is neither the UID of the file to +be deleted nor that of the directory containing it, and +the process is not privileged (Linux: does not have the +.B CAP_FOWNER +capability). .SH VERSIONS .BR linkat () was added to Linux in kernel 2.6.16;
@@ -421,6 +524,94 @@ performs the link creation and dies before it can say so. Use .BR stat (2) to find out if the link got created. +.SH EXAMPLE +The following program demonstrates the use of +.BR linkat () +with +.B AT_LINK_REPLACE +and +.BR open (2) +with +.B O_TMPFILE +for crash-safe file replacement. +.SS Example output +.in +4n +.EX +$ \fBecho bar > foo\fP +$ \fB./replace foo\fP +$ \fBcat foo\fP +hello, world +.EE +.in +.SS Program source (replace.c) +.EX +#define _GNU_SOURCE +#include <fcntl.h> +#include <libgen.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/stat.h> +#include <sys/types.h> + +int +main(int argc, char *argv[]) +{ + char *path, *dirc, *basec, *dir, *base; + int fd, dirfd; + + if (argc != 2) { + fprintf(stderr, "usage: %s PATH\en", argv[0]); + exit(EXIT_FAILURE); + } + + path = argv[1]; + + dirc = strdup(path); + basec = strdup(path); + if (!dirc || !basec) { + perror("strdup"); + exit(EXIT_FAILURE); + } + dir = dirname(dirc); + base = basename(basec); + + /* Open the parent directory. */ + dirfd = open(dir, O_DIRECTORY | O_RDONLY); + if (dirfd == -1) { + perror("open"); + exit(EXIT_FAILURE); + } + + /* Open a temporary file, write data to it, and persist it. */ + fd = open(dir, O_TMPFILE | O_RDWR, 0644); + if (fd == -1) { + perror("open"); + exit(EXIT_FAILURE); + } + if (write(fd, "hello, world\en", 13) == -1) { + perror("write"); + exit(EXIT_FAILURE); + } + if (fsync(fd) == -1) { + perror("fsync"); + exit(EXIT_FAILURE); + } + + /* Replace the original file and persist the directory. */ + if (linkat(fd, "", dirfd, base, AT_EMPTY_PATH | AT_LINK_REPLACE) == -1) { + perror("linkat"); + exit(EXIT_FAILURE); + } + if (fsync(dirfd) == -1) { + perror("fsync"); + exit(EXIT_FAILURE); + } + + exit(EXIT_SUCCESS); +} +.EE .SH SEE ALSO .BR ln (1), .BR open (2),
Signature · 1 line
2.25.0
RFC PATCH 1/1 generic: add smoke test for AT_LINK_REPLACE
In reply to Omar SandovalFrom: Omar Sandoval <[email protected]>
--- common/rc | 2 +- tests/generic/593 | 97 +++++++++++++++++++++++++++++++++++++++++++ tests/generic/593.out | 6 +++ tests/generic/group | 1 + 4 files changed, 105 insertions(+), 1 deletion(-) create mode 100755 tests/generic/593 create mode 100644 tests/generic/593.out
common/rc +1 -1
@@ -2172,7 +2172,7 @@ _require_xfs_io_command() ;; "flink") local testlink=$TEST_DIR/$$.link.xfs_io - testio=`$XFS_IO_PROG -F -f -c "flink $testlink" $testfile 2>&1` + testio=`$XFS_IO_PROG -F -f -c "flink $param $testlink" $testfile 2>&1` rm -f $testlink > /dev/null 2>&1 ;; "-T")
tests/generic/593 new file +97 -0
97 lines not on this page · raw message
tests/generic/593.out new file +6 -0
@@ -0,0 +1,6 @@ +QA output created by 593 +1 +flink: Is a directory +flink: Is a directory +flink: Is a directory +flink: Device or resource busy
tests/generic/group +1 -0
@@ -595,3 +595,4 @@ 590 auto prealloc preallocrw 591 auto quick rw pipe splice 592 auto quick encrypt +593 auto quick hardlink
Signature · 1 line
2.25.0
RFC PATCH 1/1 xfs_io: add support for linkat() AT_LINK_REPLACE
In reply to Omar SandovalFrom: Omar Sandoval <[email protected]>
--- io/link.c | 24 ++++++++++++++++++++---- man/man8/xfs_io.8 | 9 ++++++++- 2 files changed, 28 insertions(+), 5 deletions(-)
io/link.c +20 -4
@@ -12,6 +12,9 @@#ifndef AT_EMPTY_PATH #define AT_EMPTY_PATH 0x1000#endif +#ifndef AT_LINK_REPLACE +#define AT_LINK_REPLACE 0x10000 +#endif static cmdinfo_t flink_cmd;
@@ -22,6 +25,7 @@ flink_help(void)"\n" "link the open file descriptor to the supplied filename\n" "\n" +" -f -- overwrite the target filename if it exists (AT_LINK_REPLACE)\n" "\n")); }
@@ -30,10 +34,22 @@ flink_f(int argc, char **argv) { -if (argc !=2 ) + int flags = AT_EMPTY_PATH; +int c; + +while ((c =getopt (argc, argv,"f" )) !=EOF ) { +switch (c) { +case 'f' : + flags |=AT_LINK_REPLACE ; +break ; +default : +return command_usage (&flink_cmd); + } + } +if (optind != argc -1 )return command_usage (&flink_cmd); -if (linkat (file->fd,"" ,AT_FDCWD , argv[1 ],AT_EMPTY_PATH ) <0 ) { +if (linkat (file->fd,"" ,AT_FDCWD , argv[optind], flags) <0 ) {perror ("flink" );return 0 ; }
@@ -46,9 +62,9 @@ flink_init(void) flink_cmd.name ="flink" ; flink_cmd.cfunc = flink_f; flink_cmd.argmin =1 ; - flink_cmd.argmax =1 ; + flink_cmd.argmax =-1 ; flink_cmd.flags =CMD_NOMAP_OK |CMD_FOREIGN_OK |CMD_FLAG_ONESHOT ; - flink_cmd.args =_ ("filename" ); + flink_cmd.args =_ ("[-f] filename" ); flink_cmd.oneline =_ ("link the open file descriptor to the supplied filename" ); flink_cmd.help = flink_help;
man/man8/xfs_io.8 +8 -1
@@ -807,8 +807,15 @@ for the full list) is available via the .B help command. .TP -.BI "flink " path +.BI "flink [ \-f ]" " path" Link the currently open file descriptor into the filesystem namespace. +.RS 1.0i +.PD 0 +.TP 0.4i +.B \-f +overwrite the target path if it exists (AT_LINK_REPLACE). +.PD +.RE .TP .BR stat " [ " \-v "|" \-r " ]" Selected statistics from
Signature · 1 line
2.25.0
v4 cover fs: add flag to linkat() for replacing destination
From: Omar Sandoval <[email protected]>
Hello,
This series adds an AT_LINK_REPLACE flag to linkat() which allows atomically replacing the destination if it exists. This is a respin of an old series [1] that I was convinced to resend due to some recent interest [2][3].
Patch 1 adds a flags argument to i_ops->link() in preparation. Patch 2 adds the VFS support. Patch 3 fixes an inode leak in btrfs_link(), included in this series because it conflicts with patch 4. Patch 4 adds support for AT_LINK_REPLACE to Btrfs.
I've also included a man-page patch (with an example program), an xfs_io patch, and an fstest.
Some outstanding issues:
- The Btrfs implementation does a d_drop() on the replaced dentry. We probably want a d_replace() helper for filesystems to use.
- Should AT_LINK_REPLACE be limited to O_TMPFILE? In my opinion, the answer is no. After all, ln -f is not that exotic.
- Should AT_LINK_REPLACE guarantee data integrity? Again, I think the answer is no. That's more suited to something like Amir's AT_ATOMIC proposal [4].
Changes since v3:
- Rebased on v5.5.
- Added patches 1 and 3.
- Incorporated Al's feedback on various error cases in patch 2.
- Renamed the flag to AT_LINK_REPLACE.
Thanks!
1: https://lore.kernel.org/linux-fsdevel/[email protected]/
2: https://lore.kernel.org/linux-fsdevel/[email protected]/
3: https://lore.kernel.org/linux-fsdevel/[email protected]/
4: https://lore.kernel.org/linux-fsdevel/[email protected]/
Omar Sandoval (4):
fs: add flags argument to i_op->link()
fs: add AT_LINK_REPLACE flag for linkat() which replaces the target
Btrfs: fix inode reference count leak in btrfs_link() error path
Btrfs: add support for linkat() AT_REPLACE
fs/9p/vfs_inode.c | 5 +- fs/9p/vfs_inode_dotl.c | 5 +- fs/affs/affs.h | 2 +- fs/affs/namei.c | 6 +- fs/afs/dir.c | 7 +- fs/bad_inode.c | 2 +- fs/bfs/dir.c | 7 +- fs/btrfs/inode.c | 70 ++++++++++++++-- fs/ceph/dir.c | 5 +- fs/cifs/cifsfs.h | 2 +- fs/cifs/link.c | 5 +- fs/coda/dir.c | 5 +- fs/ecryptfs/inode.c | 7 +- fs/ext2/namei.c | 5 +- fs/ext4/namei.c | 7 +- fs/f2fs/namei.c | 5 +- fs/fuse/dir.c | 5 +- fs/gfs2/inode.c | 5 +- fs/hfsplus/dir.c | 5 +- fs/hostfs/hostfs_kern.c | 5 +- fs/jffs2/dir.c | 8 +- fs/jfs/namei.c | 7 +- fs/libfs.c | 6 +- fs/minix/namei.c | 5 +- fs/namei.c | 166 +++++++++++++++++++++++++++++-------- fs/nfs/dir.c | 6 +- fs/nfs/internal.h | 2 +- fs/nfsd/vfs.c | 2 +- fs/nilfs2/namei.c | 5 +- fs/ocfs2/namei.c | 6 +- fs/overlayfs/dir.c | 5 +- fs/overlayfs/overlayfs.h | 2 +- fs/reiserfs/namei.c | 5 +- fs/sysv/namei.c | 7 +- fs/ubifs/dir.c | 5 +- fs/udf/namei.c | 5 +- fs/ufs/namei.c | 5 +- fs/xfs/xfs_iops.c | 6 +- include/linux/fs.h | 6 +- include/uapi/linux/fcntl.h | 1 + mm/shmem.c | 6 +- 41 files changed, 341 insertions(+), 90 deletions(-)
Signature · 1 line
2.25.0
RFC PATCH v4 2/4 fs: add AT_LINK_REPLACE flag for linkat() which replaces the target
From: Omar Sandoval <[email protected]>
One of the most common uses of temporary files is the classic atomic replacement pattern, i.e.,
- write temporary file
- fsync temporary file
- rename temporary file over real file
- fsync parent directory
Now, we have O_TMPFILE, which gives us a much better way to create temporary files, but it's not possible to use it for this pattern.
This patch introduces an AT_LINK_REPLACE flag which allows linkat() to replace the target file. Now, the temporary file in the pattern above can be a proper O_TMPFILE. Even without O_TMPFILE, this is a new primitive which might be useful in other contexts.
The implementation on the VFS side mimics sys_renameat2().
--- fs/ecryptfs/inode.c | 2 +- fs/namei.c | 166 +++++++++++++++++++++++++++++-------- fs/nfsd/vfs.c | 2 +- fs/overlayfs/overlayfs.h | 2 +- include/linux/fs.h | 2 +- include/uapi/linux/fcntl.h | 1 + 6 files changed, 135 insertions(+), 40 deletions(-)
fs/ecryptfs/inode.c +1 -1
@@ -440,7 +440,7 @@ static int ecryptfs_link(struct dentry *old_dentry, struct inode *dir,dget (lower_new_dentry); lower_dir_dentry =lock_parent (lower_new_dentry); rc =vfs_link (lower_old_dentry,d_inode (lower_dir_dentry), - lower_new_dentry,NULL ); + lower_new_dentry,NULL ,0 );if (rc ||d_really_is_negative (lower_new_dentry)) goto out_lock; rc =ecryptfs_interpose (lower_new_dentry, new_dentry, dir->i_sb);
fs/namei.c +130 -36
233 lines not on this page · raw message
fs/nfsd/vfs.c +1 -1
@@ -1598,7 +1598,7 @@ nfsd_link(struct svc_rqst *rqstp, struct svc_fh *ffhp, err = nfserr_noent;if (d_really_is_negative (dold)) goto out_dput; - host_err =vfs_link (dold, dirp, dnew,NULL ); + host_err =vfs_link (dold, dirp, dnew,NULL ,0 );if (!host_err) { err =nfserrno (commit_metadata (ffhp));if (!err)
fs/overlayfs/overlayfs.h +1 -1
@@ -120,7 +120,7 @@ static inline int ovl_do_unlink(struct inode *dir, struct dentry *dentry)static inline int ovl_do_link (struct dentry *old_dentry,struct inode *dir,struct dentry *new_dentry) { -int err =vfs_link (old_dentry, dir, new_dentry,NULL ); +int err =vfs_link (old_dentry, dir, new_dentry,NULL ,0 );pr_debug ("link(%pd2, %pd2) = %i\n" , old_dentry, new_dentry, err);return err;
include/linux/fs.h +1 -1
@@ -1712,7 +1712,7 @@ extern int vfs_create(struct inode *, struct dentry *, umode_t, bool);extern int vfs_mkdir (struct inode *,struct dentry *,umode_t );extern int vfs_mknod (struct inode *,struct dentry *,umode_t ,dev_t );extern int vfs_symlink (struct inode *,struct dentry *,const char *); -extern int vfs_link (struct dentry *,struct inode *,struct dentry *,struct inode **); +extern int vfs_link (struct dentry *,struct inode *,struct dentry *,struct inode **,int );extern int vfs_rmdir (struct inode *,struct dentry *);extern int vfs_unlink (struct inode *,struct dentry *,struct inode **);extern int vfs_rename (struct inode *,struct dentry *,struct inode *,struct dentry *,struct inode **,unsigned int );
include/uapi/linux/fcntl.h +1 -0
@@ -99,6 +99,7 @@#define AT_STATX_DONT_SYNC 0x4000/* - Don't sync attributes with the server */ #define AT_RECURSIVE 0x8000/* Apply to the entire subtree */ +#define AT_LINK_REPLACE 0x10000/* Replace link() target */ #endif /* _UAPI_LINUX_FCNTL_H */
Signature · 1 line
2.25.0
RFC PATCH v4 3/4 Btrfs: fix inode reference count leak in btrfs_link() error path
In reply to Omar SandovalFrom: Omar Sandoval <[email protected]>
If btrfs_update_inode() or btrfs_orphan_del() fails in btrfs_link(), then we don't drop the reference we got with ihold(). This results in the "VFS: Busy inodes after unmount" crash.
The reference is needed for the new dentry, so get it right before we instantiate the dentry.
[Although d_instantiate() was moved further from ihold() before that, in commit 08c422c27f85 ("Btrfs: call d_instantiate after all ops are setup")]
--- fs/btrfs/inode.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-)
fs/btrfs/inode.c +2 -4
@@ -6801,7 +6801,6 @@ static int btrfs_link(struct dentry *old_dentry, struct inode *dir,inc_nlink (inode);inode_inc_iversion (inode); inode->i_ctime =current_time (inode); -ihold (inode);set_bit (BTRFS_INODE_COPY_EVERYTHING , &BTRFS_I (inode)->runtime_flags); err = btrfs_add_nondir(trans, BTRFS_I(dir), dentry, BTRFS_I(inode),
@@ -6825,6 +6824,7 @@ static int btrfs_link(struct dentry *old_dentry, struct inode *dir,if (err) goto fail; } +ihold (inode);d_instantiate (dentry, inode); ret =btrfs_log_new_name (trans,BTRFS_I (inode),NULL , parent, true,NULL );
@@ -6837,10 +6837,8 @@ static int btrfs_link(struct dentry *old_dentry, struct inode *dir, fail:if (trans)btrfs_end_transaction (trans); -if (drop_inode) { +if (drop_inode)inode_dec_link_count (inode); -iput (inode); - }btrfs_btree_balance_dirty (fs_info);return err; }
Signature · 1 line
2.25.0
RFC PATCH v4 1/4 fs: add flags argument to i_op->link()
In reply to Omar SandovalFrom: Omar Sandoval <[email protected]>
In preparation for adding the AT_LINK_REPLACE flag, make ->link() take and check a flags argument.
--- fs/9p/vfs_inode.c | 5 ++++- fs/9p/vfs_inode_dotl.c | 5 ++++- fs/affs/affs.h | 2 +- fs/affs/namei.c | 6 +++++- fs/afs/dir.c | 7 +++++-- fs/bad_inode.c | 2 +- fs/bfs/dir.c | 7 +++++-- fs/btrfs/inode.c | 5 ++++- fs/ceph/dir.c | 5 ++++- fs/cifs/cifsfs.h | 2 +- fs/cifs/link.c | 5 ++++- fs/coda/dir.c | 5 ++++- fs/ecryptfs/inode.c | 5 ++++- fs/ext2/namei.c | 5 ++++- fs/ext4/namei.c | 7 +++++-- fs/f2fs/namei.c | 5 ++++- fs/fuse/dir.c | 5 ++++- fs/gfs2/inode.c | 5 ++++- fs/hfsplus/dir.c | 5 ++++- fs/hostfs/hostfs_kern.c | 5 ++++- fs/jffs2/dir.c | 8 ++++++-- fs/jfs/namei.c | 7 +++++-- fs/libfs.c | 6 +++++- fs/minix/namei.c | 5 ++++- fs/namei.c | 2 +- fs/nfs/dir.c | 6 +++++- fs/nfs/internal.h | 2 +- fs/nilfs2/namei.c | 5 ++++- fs/ocfs2/namei.c | 6 +++++- fs/overlayfs/dir.c | 5 ++++- fs/reiserfs/namei.c | 5 ++++- fs/sysv/namei.c | 7 +++++-- fs/ubifs/dir.c | 5 ++++- fs/udf/namei.c | 5 ++++- fs/ufs/namei.c | 5 ++++- fs/xfs/xfs_iops.c | 6 +++++- include/linux/fs.h | 4 ++-- mm/shmem.c | 6 +++++- 38 files changed, 148 insertions(+), 45 deletions(-)
fs/9p/vfs_inode.c +4 -1
@@ -1335,12 +1335,15 @@ v9fs_vfs_symlink(struct inode *dir, struct dentry *dentry, const char *symname)static int v9fs_vfs_link (struct dentry *old_dentry,struct inode *dir, -struct dentry *dentry) +struct dentry *dentry,int flags) {int retval;char name[1 +U32_MAX_DIGITS +2 ];/* sign + number + \n + \0 */ struct p9_fid *oldfid; +if (flags) +return -EINVAL ; +p9_debug (P9_DEBUG_VFS ," %lu,%pd,%pd\n" , dir->i_ino, dentry, old_dentry);
fs/9p/vfs_inode_dotl.c +4 -1
@@ -749,12 +749,15 @@ v9fs_vfs_symlink_dotl(struct inode *dir, struct dentry *dentry,static int v9fs_vfs_link_dotl (struct dentry *old_dentry,struct inode *dir, -struct dentry *dentry) +struct dentry *dentry,int flags) {int err;struct p9_fid *dfid, *oldfid;struct v9fs_session_info *v9ses; +if (flags) +return -EINVAL ; +p9_debug (P9_DEBUG_VFS ,"dir ino: %lu, old_name: %pd, new_name: %pd\n" , dir->i_ino, old_dentry, dentry);
fs/affs/affs.h +1 -1
@@ -171,7 +171,7 @@ extern int affs_create(struct inode *dir, struct dentry *dentry, umode_t mode, bextern int affs_mkdir (struct inode *dir,struct dentry *dentry,umode_t mode);extern int affs_rmdir (struct inode *dir,struct dentry *dentry);extern int affs_link (struct dentry *olddentry,struct inode *dir, -struct dentry *dentry); +struct dentry *dentry,int flags);extern int affs_symlink (struct inode *dir,struct dentry *dentry,const char *symname); extern int affs_rename2(struct inode *old_dir, struct dentry *old_dentry,
fs/affs/namei.c +5 -1
@@ -388,10 +388,14 @@ affs_symlink(struct inode *dir, struct dentry *dentry, const char *symname) }int -affs_link (struct dentry *old_dentry,struct inode *dir,struct dentry *dentry) +affs_link (struct dentry *old_dentry,struct inode *dir,struct dentry *dentry, +int flags) {struct inode *inode =d_inode (old_dentry); +if (flags) +return -EINVAL ; +pr_debug ("%s(%lu, %lu, \"%pd\")\n" , __func__, inode->i_ino, dir->i_ino, dentry);
fs/afs/dir.c +5 -2
@@ -34,7 +34,7 @@ static int afs_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode);static int afs_rmdir (struct inode *dir,struct dentry *dentry);static int afs_unlink (struct inode *dir,struct dentry *dentry);static int afs_link (struct dentry *from,struct inode *dir, -struct dentry *dentry); +struct dentry *dentry,int flags);static int afs_symlink (struct inode *dir,struct dentry *dentry,const char *content); static int afs_rename(struct inode *old_dir, struct dentry *old_dentry,
@@ -1641,7 +1641,7 @@ static int afs_create(struct inode *dir, struct dentry *dentry, umode_t mode,* create a hard link between files in an AFS filesystem */ static int afs_link (struct dentry *from,struct inode *dir, -struct dentry *dentry) +struct dentry *dentry,int flags) {struct afs_fs_cursor fc;struct afs_status_cb *scb;
@@ -1650,6 +1650,9 @@ static int afs_link(struct dentry *from, struct inode *dir,struct key *key;int ret; +if (flags) +return -EINVAL ; + _enter("{%llx:%llu},{%llx:%llu},{%pd}", vnode->fid.vid, vnode->fid.vnode, dvnode->fid.vid, dvnode->fid.vnode,
fs/bad_inode.c +1 -1
@@ -39,7 +39,7 @@ static struct dentry *bad_inode_lookup(struct inode *dir, }static int bad_inode_link (struct dentry *old_dentry,struct inode *dir, -struct dentry *dentry) +struct dentry *dentry,int flags) {return -EIO ; }
fs/bfs/dir.c +5 -2
@@ -144,13 +144,16 @@ static struct dentry *bfs_lookup(struct inode *dir, struct dentry *dentry,return d_splice_alias (inode, dentry); } -static int bfs_link (struct dentry *old,struct inode *dir, -struct dentry *new) +static int bfs_link (struct dentry *old,struct inode *dir,struct dentry *new, +int flags) {struct inode *inode =d_inode (old);struct bfs_sb_info *info =BFS_SB (inode->i_sb);int err; +if (flags) +return -EINVAL ; +mutex_lock (&info->bfs_lock); err =bfs_add_entry (dir, &new->d_name, inode->i_ino);if (err) {
fs/btrfs/inode.c +4 -1
@@ -6759,7 +6759,7 @@ static int btrfs_create(struct inode *dir, struct dentry *dentry, }static int btrfs_link (struct dentry *old_dentry,struct inode *dir, -struct dentry *dentry) +struct dentry *dentry,int flags) {struct btrfs_trans_handle *trans =NULL ;struct btrfs_root *root =BTRFS_I (dir)->root;
@@ -6769,6 +6769,9 @@ static int btrfs_link(struct dentry *old_dentry, struct inode *dir,int err;int drop_inode =0 ; +if (flags) +return -EINVAL ; +/* do not allow sys_link's with other subvols of the same device */ if (root->root_key.objectid !=BTRFS_I (inode)->root->root_key.objectid)return -EXDEV ;
fs/ceph/dir.c +4 -1
@@ -999,13 +999,16 @@ static int ceph_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode) }static int ceph_link (struct dentry *old_dentry,struct inode *dir, -struct dentry *dentry) +struct dentry *dentry,int flags) {struct ceph_fs_client *fsc =ceph_sb_to_client (dir->i_sb);struct ceph_mds_client *mdsc = fsc->mdsc;struct ceph_mds_request *req;int err; +if (flags) +return -EINVAL ; +if (ceph_snap (dir) !=CEPH_NOSNAP )return -EROFS ;
fs/cifs/cifsfs.h +1 -1
@@ -69,7 +69,7 @@ extern int cifs_atomic_open(struct inode *, struct dentry *,extern struct dentry *cifs_lookup (struct inode *,struct dentry *,unsigned int );extern int cifs_unlink (struct inode *dir,struct dentry *dentry); -extern int cifs_hardlink (struct dentry *,struct inode *,struct dentry *); +extern int cifs_hardlink (struct dentry *,struct inode *,struct dentry *,int );extern int cifs_mknod (struct inode *,struct dentry *,umode_t ,dev_t );extern int cifs_mkdir (struct inode *,struct dentry *,umode_t );extern int cifs_rmdir (struct inode *,struct dentry *);
fs/cifs/link.c +4 -1
@@ -516,7 +516,7 @@ smb3_create_mf_symlink(unsigned int xid, struct cifs_tcon *tcon,int cifs_hardlink (struct dentry *old_file,struct inode *inode, -struct dentry *direntry) +struct dentry *direntry,int flags) {int rc = -EACCES ;unsigned int xid;
@@ -528,6 +528,9 @@ cifs_hardlink(struct dentry *old_file, struct inode *inode,struct TCP_Server_Info *server;struct cifsInodeInfo *cifsInode; +if (flags) +return -EINVAL ; + tlink =cifs_sb_tlink (cifs_sb);if (IS_ERR (tlink))return PTR_ERR (tlink);
fs/coda/dir.c +4 -1
@@ -200,13 +200,16 @@ static int coda_mkdir(struct inode *dir, struct dentry *de, umode_t mode)/* try to make de an entry in dir_inodde linked to source_de */ static int coda_link (struct dentry *source_de,struct inode *dir_inode, -struct dentry *de) +struct dentry *de,int flags) {struct inode *inode =d_inode (source_de);const char * name = de->d_name.name;int len = de->d_name.len;int error; +if (flags) +return -EINVAL ; +if (is_root_inode (dir_inode) &&coda_iscontrol (name, len))return -EPERM ;
fs/ecryptfs/inode.c +4 -1
@@ -422,7 +422,7 @@ static struct dentry *ecryptfs_lookup(struct inode *ecryptfs_dir_inode, }static int ecryptfs_link (struct dentry *old_dentry,struct inode *dir, -struct dentry *new_dentry) +struct dentry *new_dentry,int flags) {struct dentry *lower_old_dentry;struct dentry *lower_new_dentry;
@@ -430,6 +430,9 @@ static int ecryptfs_link(struct dentry *old_dentry, struct inode *dir,u64 file_size_save;int rc; +if (flags) +return -EINVAL ; + file_size_save =i_size_read (d_inode (old_dentry)); lower_old_dentry =ecryptfs_dentry_to_lower (old_dentry); lower_new_dentry =ecryptfs_dentry_to_lower (new_dentry);
fs/ext2/namei.c +4 -1
@@ -196,11 +196,14 @@ static int ext2_symlink (struct inode * dir, struct dentry * dentry, }static int ext2_link (struct dentry * old_dentry,struct inode * dir, -struct dentry *dentry) +struct dentry *dentry,int flags) {struct inode *inode =d_inode (old_dentry);int err; +if (flags) +return -EINVAL ; + err =dquot_initialize (dir);if (err)return err;
fs/ext4/namei.c +5 -2
@@ -3366,13 +3366,16 @@ static int ext4_symlink(struct inode *dir,return err; } -static int ext4_link (struct dentry *old_dentry, -struct inode *dir,struct dentry *dentry) +static int ext4_link (struct dentry *old_dentry,struct inode *dir, +struct dentry *dentry,int flags) {handle_t *handle;struct inode *inode =d_inode (old_dentry);int err, retries =0 ; +if (flags) +return -EINVAL ; +if (inode->i_nlink >=EXT4_LINK_MAX )return -EMLINK ;
fs/f2fs/namei.c +4 -1
@@ -312,12 +312,15 @@ static int f2fs_create(struct inode *dir, struct dentry *dentry, umode_t mode, }static int f2fs_link (struct dentry *old_dentry,struct inode *dir, -struct dentry *dentry) +struct dentry *dentry,int flags) {struct inode *inode =d_inode (old_dentry);struct f2fs_sb_info *sbi =F2FS_I_SB (dir);int err; +if (flags) +return -EINVAL ; +if (unlikely (f2fs_cp_error (sbi)))return -EIO ;if (!f2fs_is_checkpoint_ready (sbi))
fs/fuse/dir.c +4 -1
@@ -842,7 +842,7 @@ static int fuse_rename2(struct inode *olddir, struct dentry *oldent, }static int fuse_link (struct dentry *entry,struct inode *newdir, -struct dentry *newent) +struct dentry *newent,int flags) {int err;struct fuse_link_in inarg;
@@ -850,6 +850,9 @@ static int fuse_link(struct dentry *entry, struct inode *newdir,struct fuse_conn *fc =get_fuse_conn (inode);FUSE_ARGS (args); +if (flags) +return -EINVAL ; +memset (&inarg,0 ,sizeof (inarg)); inarg.oldnodeid =get_node_id (inode); args.opcode =FUSE_LINK ;
fs/gfs2/inode.c +4 -1
@@ -891,7 +891,7 @@ static struct dentry *gfs2_lookup(struct inode *dir, struct dentry *dentry,*/ static int gfs2_link (struct dentry *old_dentry,struct inode *dir, -struct dentry *dentry) +struct dentry *dentry,int flags) {struct gfs2_inode *dip =GFS2_I (dir);struct gfs2_sbd *sdp =GFS2_SB (dir);
@@ -902,6 +902,9 @@ static int gfs2_link(struct dentry *old_dentry, struct inode *dir,struct gfs2_diradd da = { .bh =NULL , .save_loc =1 , };int error; +if (flags) +return -EINVAL ; +if (S_ISDIR (inode->i_mode))return -EPERM ;
fs/hfsplus/dir.c +4 -1
@@ -298,7 +298,7 @@ static int hfsplus_dir_release(struct inode *inode, struct file *file) }static int hfsplus_link (struct dentry *src_dentry,struct inode *dst_dir, -struct dentry *dst_dentry) +struct dentry *dst_dentry,int flags) {struct hfsplus_sb_info *sbi =HFSPLUS_SB (dst_dir->i_sb);struct inode *inode =d_inode (src_dentry);
@@ -308,6 +308,9 @@ static int hfsplus_link(struct dentry *src_dentry, struct inode *dst_dir,u32 cnid, id;int res; +if (flags) +return -EINVAL ; +if (HFSPLUS_IS_RSRC (inode))return -EPERM ;if (!S_ISREG (inode->i_mode))
fs/hostfs/hostfs_kern.c +4 -1
@@ -622,11 +622,14 @@ static struct dentry *hostfs_lookup(struct inode *ino, struct dentry *dentry, }static int hostfs_link (struct dentry *to,struct inode *ino, -struct dentry *from) +struct dentry *from,int flags) {char *from_name, *to_name;int err; +if (flags) +return -EOPNOTSUPP ; +if ((from_name =dentry_name (from)) ==NULL )return -ENOMEM ; to_name =dentry_name (to);
fs/jffs2/dir.c +6 -2
@@ -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 deuint8_t type;uint32_t now; +if (flags) +return -EINVAL ; +/* Don't let people make hard links to bad inodes. */ if (!f->inocache)return -EIO ;
fs/jfs/namei.c +5 -2
@@ -781,8 +781,8 @@ void jfs_free_zero_link(struct inode *ip)* EXDEV: target object and new link are on different file systems and * implementation does not support links between file systems [XPG4.2]. */ -static int jfs_link (struct dentry *old_dentry, -struct inode *dir,struct dentry *dentry) +static int jfs_link (struct dentry *old_dentry,struct inode *dir, +struct dentry *dentry,int flags) {int rc;tid_t tid;
@@ -792,6 +792,9 @@ static int jfs_link(struct dentry *old_dentry,struct btstack btstack;struct inode *iplist[2 ]; +if (flags) +return -EINVAL ; +jfs_info ("jfs_link: %pd %pd" , old_dentry, dentry); rc =dquot_initialize (dir);
fs/libfs.c +5 -1
@@ -318,10 +318,14 @@ int simple_open(struct inode *inode, struct file *file) }EXPORT_SYMBOL (simple_open); -int simple_link (struct dentry *old_dentry,struct inode *dir,struct dentry *dentry) +int simple_link (struct dentry *old_dentry,struct inode *dir, +struct dentry *dentry,int flags) {struct inode *inode =d_inode (old_dentry); +if (flags) +return -EINVAL ; + inode->i_ctime = dir->i_ctime = dir->i_mtime =current_time (inode);inc_nlink (inode);ihold (inode);
fs/minix/namei.c +4 -1
@@ -99,10 +99,13 @@ static int minix_symlink(struct inode * dir, struct dentry *dentry, }static int minix_link (struct dentry * old_dentry,struct inode * dir, -struct dentry *dentry) +struct dentry *dentry,int flags) {struct inode *inode =d_inode (old_dentry); +if (flags) +return -EINVAL ; + inode->i_ctime =current_time (inode);inode_inc_link_count (inode);ihold (inode);
fs/namei.c +1 -1
@@ -4181,7 +4181,7 @@ int vfs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_deelse { error =try_break_deleg (inode, delegated_inode);if (!error) - error = dir->i_op->link (old_dentry, dir, new_dentry); + error = dir->i_op->link (old_dentry, dir, new_dentry,0 ); }if (!error && (inode->i_state &I_LINKABLE )) {
fs/nfs/dir.c +5 -1
@@ -1991,11 +1991,15 @@ int nfs_symlink(struct inode *dir, struct dentry *dentry, const char *symname)EXPORT_SYMBOL_GPL (nfs_symlink);int -nfs_link (struct dentry *old_dentry,struct inode *dir,struct dentry *dentry) +nfs_link (struct dentry *old_dentry,struct inode *dir,struct dentry *dentry, +int flags) {struct inode *inode =d_inode (old_dentry);int error; +if (flags) +return -EINVAL ; +dfprintk (VFS ,"NFS: link(%pd2 -> %pd2)\n" , old_dentry, dentry);
fs/nfs/internal.h +1 -1
@@ -356,7 +356,7 @@ int nfs_mkdir(struct inode *, struct dentry *, umode_t);int nfs_rmdir (struct inode *,struct dentry *);int nfs_unlink (struct inode *,struct dentry *);int nfs_symlink (struct inode *,struct dentry *,const char *); -int nfs_link (struct dentry *,struct inode *,struct dentry *); +int nfs_link (struct dentry *,struct inode *,struct dentry *,int );int nfs_mknod (struct inode *,struct dentry *,umode_t ,dev_t );int nfs_rename (struct inode *,struct dentry *,struct inode *,struct dentry *,unsigned int );
fs/nilfs2/namei.c +4 -1
@@ -174,12 +174,15 @@ static int nilfs_symlink(struct inode *dir, struct dentry *dentry, }static int nilfs_link (struct dentry *old_dentry,struct inode *dir, -struct dentry *dentry) +struct dentry *dentry,int flags) {struct inode *inode =d_inode (old_dentry);struct nilfs_transaction_info ti;int err; +if (flags) +return -EINVAL ; + err =nilfs_transaction_begin (dir->i_sb, &ti,1 );if (err)return err;
fs/ocfs2/namei.c +5 -1
@@ -672,7 +672,8 @@ static int ocfs2_create(struct inode *dir,static int ocfs2_link (struct dentry *old_dentry,struct inode *dir, -struct dentry *dentry) +struct dentry *dentry, +int flags) {handle_t *handle;struct inode *inode =d_inode (old_dentry);
@@ -687,6 +688,9 @@ static int ocfs2_link(struct dentry *old_dentry,sigset_t oldset;u64 old_de_ino; +if (flags) +return -EINVAL ; +trace_ocfs2_link ((unsigned long long )OCFS2_I (inode)->ip_blkno, old_dentry->d_name.len, old_dentry->d_name.name, dentry->d_name.len, dentry->d_name.name);
fs/overlayfs/dir.c +4 -1
@@ -661,11 +661,14 @@ static int ovl_set_link_redirect(struct dentry *dentry) }static int ovl_link (struct dentry *old,struct inode *newdir, -struct dentry *new) +struct dentry *new,int flags) {int err;struct inode *inode; +if (flags) +return -EINVAL ; + err =ovl_want_write (old);if (err) goto out;
fs/reiserfs/namei.c +4 -1
@@ -1195,7 +1195,7 @@ static int reiserfs_symlink(struct inode *parent_dir, }static int reiserfs_link (struct dentry *old_dentry,struct inode *dir, -struct dentry *dentry) +struct dentry *dentry,int flags) {int retval;struct inode *inode =d_inode (old_dentry);
@@ -1208,6 +1208,9 @@ static int reiserfs_link(struct dentry *old_dentry, struct inode *dir,JOURNAL_PER_BALANCE_CNT *3 +2 *REISERFS_QUOTA_TRANS_BLOCKS (dir->i_sb); +if (flags) +return -EINVAL ; + retval =dquot_initialize (dir);if (retval)return retval;
fs/sysv/namei.c +5 -2
@@ -96,11 +96,14 @@ static int sysv_symlink(struct inode * dir, struct dentry * dentry, goto out; } -static int sysv_link (struct dentry * old_dentry,struct inode * dir, -struct dentry * dentry) +static int sysv_link (struct dentry *old_dentry,struct inode *dir, +struct dentry *dentry,int flags) {struct inode *inode =d_inode (old_dentry); +if (flags) +return -EINVAL ; + inode->i_ctime =current_time (inode);inode_inc_link_count (inode);ihold (inode);
fs/ubifs/dir.c +4 -1
@@ -683,7 +683,7 @@ static void unlock_2_inodes(struct inode *inode1, struct inode *inode2) }static int ubifs_link (struct dentry *old_dentry,struct inode *dir, -struct dentry *dentry) +struct dentry *dentry,int flags) {struct ubifs_info *c = dir->i_sb->s_fs_info;struct inode *inode =d_inode (old_dentry);
@@ -694,6 +694,9 @@ static int ubifs_link(struct dentry *old_dentry, struct inode *dir, .dirtied_ino_d = ALIGN(ui->data_len, 8) };struct fscrypt_name nm; +if (flags) +return -EINVAL ; +/* * Budget request settings: new direntry, changing the target inode, * changing the parent inode.
fs/udf/namei.c +4 -1
@@ -1028,13 +1028,16 @@ static int udf_symlink(struct inode *dir, struct dentry *dentry, }static int udf_link (struct dentry *old_dentry,struct inode *dir, -struct dentry *dentry) +struct dentry *dentry,int flags) {struct inode *inode =d_inode (old_dentry);struct udf_fileident_bh fibh;struct fileIdentDesc cfi, *fi;int err; +if (flags) +return -EINVAL ; + fi =udf_add_entry (dir, dentry, &fibh, &cfi, &err);if (!fi) {return err;
fs/ufs/namei.c +4 -1
@@ -146,11 +146,14 @@ static int ufs_symlink (struct inode * dir, struct dentry * dentry, }static int ufs_link (struct dentry * old_dentry,struct inode * dir, -struct dentry *dentry) +struct dentry *dentry,int flags) {struct inode *inode =d_inode (old_dentry);int error; +if (flags) +return -EINVAL ; + inode->i_ctime =current_time (inode);inode_inc_link_count (inode);ihold (inode);
fs/xfs/xfs_iops.c +5 -1
@@ -311,12 +311,16 @@ STATIC intxfs_vn_link ( struct dentry *old_dentry, struct inode *dir, - struct dentry *dentry) + struct dentry *dentry, + int flags) { struct inode *inode = d_inode(old_dentry);struct xfs_name name;int error; +if (flags) +return -EINVAL ; + error =xfs_dentry_mode_to_name (&name, dentry, inode->i_mode);if (unlikely (error))return error;
include/linux/fs.h +2 -2
@@ -1869,7 +1869,7 @@ struct inode_operations {int (*readlink) (struct dentry *,char __user *,int );int (*create) (struct inode *,struct dentry *,umode_t ,bool ); -int (*link) (struct dentry *,struct inode *,struct dentry *); +int (*link) (struct dentry *,struct inode *,struct dentry *,int );int (*unlink) (struct inode *,struct dentry *);int (*symlink) (struct inode *,struct dentry *,const char *);int (*mkdir) (struct inode *,struct dentry *,umode_t );
@@ -3298,7 +3298,7 @@ extern int simple_setattr(struct dentry *, struct iattr *);extern int simple_getattr (const struct path *,struct kstat *,u32 ,unsigned int );extern int simple_statfs (struct dentry *,struct kstatfs *);extern int simple_open (struct inode *inode,struct file *file); -extern int simple_link (struct dentry *,struct inode *,struct dentry *); +extern int simple_link (struct dentry *,struct inode *,struct dentry *,int );extern int simple_unlink (struct inode *,struct dentry *);extern int simple_rmdir (struct inode *,struct dentry *); extern int simple_rename(struct inode *, struct dentry *,
mm/shmem.c +5 -1
@@ -2948,11 +2948,15 @@ static int shmem_create(struct inode *dir, struct dentry *dentry, umode_t mode,/* * Link a file.. */ -static int shmem_link (struct dentry *old_dentry,struct inode *dir,struct dentry *dentry) +static int shmem_link (struct dentry *old_dentry,struct inode *dir, +struct dentry *dentry,int flags) {struct inode *inode =d_inode (old_dentry);int ret =0 ; +if (flags) +return -EINVAL ; +/* * No ordinary (disk based) filesystem counts links as inodes; * but each new link needs a new dentry, pinning lowmem, and
Signature · 1 line
2.25.0
RFC PATCH v4 4/4 Btrfs: add support for linkat() AT_REPLACE
In reply to Omar SandovalFrom: Omar Sandoval <[email protected]>
The implementation is fairly straightforward and looks a lot like btrfs_rename(). The only tricky bit is that instead of playing games with the dcache, we simply drop the dentry for it to be instantiated on the next lookup. This can be improved in the future.
--- fs/btrfs/inode.c | 63 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 59 insertions(+), 4 deletions(-)
fs/btrfs/inode.c +59 -4
@@ -6762,14 +6762,16 @@ static int btrfs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *dentry, int flags) {struct btrfs_trans_handle *trans =NULL ; +unsigned int trans_num_items;struct btrfs_root *root =BTRFS_I (dir)->root;struct inode *inode =d_inode (old_dentry); +struct inode *new_inode =d_inode (dentry);struct btrfs_fs_info *fs_info =btrfs_sb (inode->i_sb);u64 index;int err;int drop_inode =0 ; -if (flags) +if (flags & ~AT_LINK_REPLACE )return -EINVAL ;/* do not allow sys_link's with other subvols of the same device */
@@ -6779,17 +6781,50 @@ static int btrfs_link(struct dentry *old_dentry, struct inode *dir,if (inode->i_nlink >=BTRFS_LINK_MAX )return -EMLINK ; +/* check for collisions, even if the name isn't there */ + err =btrfs_check_dir_item_collision (root, dir->i_ino, + dentry->d_name.name, + dentry->d_name.len); +if (err) { +if (err == -EEXIST ) { +if (WARN_ON (!new_inode)) +return err; + }else { +return err; + } + } + +/* +* we're using link to replace one file with another. Start IO on it now +* so we don't add too much work to the end of the transaction +*/ +if (new_inode &&S_ISREG (inode->i_mode) && new_inode->i_size) +filemap_flush (inode->i_mapping); + err =btrfs_set_inode_index (BTRFS_I (dir), &index);if (err) goto fail;/* +* For the source: * 2 items for inode and inode ref * 2 items for dir items * 1 item for parent inode * 1 item for orphan item deletion if O_TMPFILE +* +* For the target: +* 1 for the possible orphan item +* 1 for the dir item +* 1 for the dir index +* 1 for the inode ref +* 1 for the inode */ - trans =btrfs_start_transaction (root, inode->i_nlink ?5 :6 ); + trans_num_items =5 ; +if (!inode->i_nlink) + trans_num_items++; +if (new_inode) + trans_num_items +=5 ; + trans =btrfs_start_transaction (root, trans_num_items);if (IS_ERR (trans)) { err =PTR_ERR (trans); trans =NULL ;
@@ -6801,6 +6836,22 @@ static int btrfs_link(struct dentry *old_dentry, struct inode *dir,inc_nlink (inode);inode_inc_iversion (inode); inode->i_ctime =current_time (inode); + +if (new_inode) { +inode_inc_iversion (new_inode); + new_inode->i_ctime =current_time (new_inode); + err =btrfs_unlink_inode (trans, root,BTRFS_I (dir), +BTRFS_I (new_inode), + dentry->d_name.name, + dentry->d_name.len); +if (!err && new_inode->i_nlink ==0 ) + err =btrfs_orphan_add (trans,BTRFS_I (new_inode)); +if (err) { +btrfs_abort_transaction (trans, err); + goto fail; + } + } +set_bit (BTRFS_INODE_COPY_EVERYTHING , &BTRFS_I (inode)->runtime_flags); err = btrfs_add_nondir(trans, BTRFS_I(dir), dentry, BTRFS_I(inode),
@@ -6824,8 +6875,12 @@ static int btrfs_link(struct dentry *old_dentry, struct inode *dir,if (err) goto fail; } -ihold (inode); -d_instantiate (dentry, inode); +if (new_inode) { +d_drop (dentry); + }else { +ihold (inode); +d_instantiate (dentry, inode); + } ret =btrfs_log_new_name (trans,BTRFS_I (inode),NULL , parent, true,NULL );if (ret ==BTRFS_NEED_TRANS_COMMIT ) {
Signature · 1 line
2.25.0
Re: [RFC PATCH xfstests] generic: add smoke test for AT_LINK_REPLACE
In reply to Omar SandovalQuoted 4 lines · Omar Sandoval
From: Omar Sandoval <[email protected]>
Signed-off-by Omar Sandoval <[email protected]>---
I think you'd better to send this patch to/cc [email protected] to get review or merge.
Thanks,
Zorro
Quoted 148 lines
common/rc | 2 +- tests/generic/593 | 97 +++++++++++++++++++++++++++++++++++++++++++ tests/generic/593.out | 6 +++ tests/generic/group | 1 + 4 files changed, 105 insertions(+), 1 deletion(-) create mode 100755 tests/generic/593 create mode 100644 tests/generic/593.outcommon/rc +1
-1@@ -2172,7 +2172,7 @@ _require_xfs_io_command() ;; "flink") local testlink=$TEST_DIR/$$.link.xfs_io - testio=`$XFS_IO_PROG -F -f -c "flink $testlink" $testfile 2>&1` + testio=`$XFS_IO_PROG -F -f -c "flink $param $testlink" $testfile 2>&1` rm -f $testlink > /dev/null 2>&1 ;; "-T")tests/generic/593 new file +97
-0@@ -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 +exittests/generic/593.out new file +6
-0@@ -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 busytests/generic/group +1
-0@@ -595,3 +595,4 @@ 590 auto prealloc preallocrw 591 auto quick rw pipe splice 592 auto quick encrypt +593 auto quick hardlinkSignature · 1 line
2.25.0
RFC PATCH 1/1 generic: add smoke test for AT_LINK_REPLACE
In reply to a message not in the archiveFrom: Omar Sandoval <[email protected]>
--- common/rc | 2 +- tests/generic/593 | 97 +++++++++++++++++++++++++++++++++++++++++++ tests/generic/593.out | 6 +++ tests/generic/group | 1 + 4 files changed, 105 insertions(+), 1 deletion(-) create mode 100755 tests/generic/593 create mode 100644 tests/generic/593.out
common/rc +1 -1
@@ -2172,7 +2172,7 @@ _require_xfs_io_command() ;; "flink") local testlink=$TEST_DIR/$$.link.xfs_io - testio=`$XFS_IO_PROG -F -f -c "flink $testlink" $testfile 2>&1` + testio=`$XFS_IO_PROG -F -f -c "flink $param $testlink" $testfile 2>&1` rm -f $testlink > /dev/null 2>&1 ;; "-T")
tests/generic/593 new file +97 -0
97 lines not on this page · raw message
tests/generic/593.out new file +6 -0
@@ -0,0 +1,6 @@ +QA output created by 593 +1 +flink: Is a directory +flink: Is a directory +flink: Is a directory +flink: Device or resource busy
tests/generic/group +1 -0
@@ -595,3 +595,4 @@ 590 auto prealloc preallocrw 591 auto quick rw pipe splice 592 auto quick encrypt +593 auto quick hardlink
Signature · 1 line
2.25.0
RFC PATCH 1/1 generic: add smoke test for AT_LINK_REPLACE
In reply to Omar SandovalFrom: Omar Sandoval <[email protected]>
--- common/rc | 2 +- tests/generic/593 | 97 +++++++++++++++++++++++++++++++++++++++++++ tests/generic/593.out | 6 +++ tests/generic/group | 1 + 4 files changed, 105 insertions(+), 1 deletion(-) create mode 100755 tests/generic/593 create mode 100644 tests/generic/593.out
common/rc +1 -1
@@ -2172,7 +2172,7 @@ _require_xfs_io_command() ;; "flink") local testlink=$TEST_DIR/$$.link.xfs_io - testio=`$XFS_IO_PROG -F -f -c "flink $testlink" $testfile 2>&1` + testio=`$XFS_IO_PROG -F -f -c "flink $param $testlink" $testfile 2>&1` rm -f $testlink > /dev/null 2>&1 ;; "-T")
tests/generic/593 new file +97 -0
97 lines not on this page · raw message
tests/generic/593.out new file +6 -0
@@ -0,0 +1,6 @@ +QA output created by 593 +1 +flink: Is a directory +flink: Is a directory +flink: Is a directory +flink: Device or resource busy
tests/generic/group +1 -0
@@ -595,3 +595,4 @@ 590 auto prealloc preallocrw 591 auto quick rw pipe splice 592 auto quick encrypt +593 auto quick hardlink
Signature · 1 line
2.25.0
RFC PATCH 1/1 link.2: Document new AT_LINK_REPLACE flag
In reply to a message not in the archiveFrom: Omar Sandoval <[email protected]>
--- man2/link.2 | 191 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 191 insertions(+)
man2/link.2 +191 -0
@@ -174,6 +174,60 @@ like this: linkat(AT_FDCWD, "/proc/self/fd/<fd>", newdirfd, newname, AT_SYMLINK_FOLLOW); .EE +.TP +.BR AT_LINK_REPLACE " (since Linux 5.7)" +If +.I newpath +exists, replace it atomically. +There is no point at which another process attempting to access +.I newpath +will find it missing. +If +.I newpath +exists but the operation fails, +the original entry specified by +.I newpath +will remain in place. +This does not guarantee data integrity; +see EXAMPLE below for how to use this for crash-safe file replacement with +.BR O_TMPFILE . +.IP +If +.I newpath +is replaced, +any other hard links referring to the original file are unaffected. +Open file descriptors for +.I newpath +are also unaffected. +.IP +.I newpath +must not be a directory. +.IP +If the entry specified by +.I newpath +refers to the file specified by +.I oldpath, +.BR linkat () +does nothing and returns a success status. +Note that this comparison does not follow mounts on +.IR newpath . +.IP +Otherwise, +.I newpath +must not be a mount point in the local namespace. +If it is a mount point in another namespace and the operation succeeds, +all mounts are detached from +.I newpath +in all namespaces, as is the case for +.BR rename (2), +.BR rmdir (2), +and +.BR unlink (2). +.IP +If +.I newpath +refers to a symbolic link, +the link will be replaced. .in .PP Before kernel 2.6.18, the
@@ -293,10 +347,34 @@ or .I newdirfd is not a valid file descriptor. .TP +.B EBUSY +.B AT_LINK_REPLACE +was specified in +.IR flags , +.I newpath +does not refer to the file specified by +.IR oldpath , +and +.I newpath +is in use by the system +(for example, it is a mount point in the local namespace). +.TP .B EINVAL An invalid flag value was specified in .IR flags . .TP +.B EINVAL +The filesystem does not support one of the flags in +.IR flags . +.TP +.B EISDIR +.B AT_LINK_REPLACE +was specified in +.I flags +and +.I newpath +refers to an existing directory. +.TP .B ENOENT .B AT_EMPTY_PATH was specified in
@@ -344,6 +422,31 @@ was specified in is an empty string, and .IR olddirfd refers to a directory. +.TP +.B EPERM +.B AT_LINK_REPLACE +was specified in +.I flags +and +.I newpath +refers to an immutable or append-only file +or a file in an immutable or append-only directory. +(See +.BR ioctl_iflags (2).) +.TP +.BR EPERM " or " EACCES +.B AT_LINK_REPLACE +was specified in +.IR flags , +the directory containing +.I newpath +has the sticky bit +.RB ( S_ISVTX ) +set, and the process's effective UID is neither the UID of the file to +be deleted nor that of the directory containing it, and +the process is not privileged (Linux: does not have the +.B CAP_FOWNER +capability). .SH VERSIONS .BR linkat () was added to Linux in kernel 2.6.16;
@@ -421,6 +524,94 @@ performs the link creation and dies before it can say so. Use .BR stat (2) to find out if the link got created. +.SH EXAMPLE +The following program demonstrates the use of +.BR linkat () +with +.B AT_LINK_REPLACE +and +.BR open (2) +with +.B O_TMPFILE +for crash-safe file replacement. +.SS Example output +.in +4n +.EX +$ \fBecho bar > foo\fP +$ \fB./replace foo\fP +$ \fBcat foo\fP +hello, world +.EE +.in +.SS Program source (replace.c) +.EX +#define _GNU_SOURCE +#include <fcntl.h> +#include <libgen.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/stat.h> +#include <sys/types.h> + +int +main(int argc, char *argv[]) +{ + char *path, *dirc, *basec, *dir, *base; + int fd, dirfd; + + if (argc != 2) { + fprintf(stderr, "usage: %s PATH\en", argv[0]); + exit(EXIT_FAILURE); + } + + path = argv[1]; + + dirc = strdup(path); + basec = strdup(path); + if (!dirc || !basec) { + perror("strdup"); + exit(EXIT_FAILURE); + } + dir = dirname(dirc); + base = basename(basec); + + /* Open the parent directory. */ + dirfd = open(dir, O_DIRECTORY | O_RDONLY); + if (dirfd == -1) { + perror("open"); + exit(EXIT_FAILURE); + } + + /* Open a temporary file, write data to it, and persist it. */ + fd = open(dir, O_TMPFILE | O_RDWR, 0644); + if (fd == -1) { + perror("open"); + exit(EXIT_FAILURE); + } + if (write(fd, "hello, world\en", 13) == -1) { + perror("write"); + exit(EXIT_FAILURE); + } + if (fsync(fd) == -1) { + perror("fsync"); + exit(EXIT_FAILURE); + } + + /* Replace the original file and persist the directory. */ + if (linkat(fd, "", dirfd, base, AT_EMPTY_PATH | AT_LINK_REPLACE) == -1) { + perror("linkat"); + exit(EXIT_FAILURE); + } + if (fsync(dirfd) == -1) { + perror("fsync"); + exit(EXIT_FAILURE); + } + + exit(EXIT_SUCCESS); +} +.EE .SH SEE ALSO .BR ln (1), .BR open (2),
Signature · 1 line
2.25.0
RFC PATCH 1/1 xfs_io: add support for linkat() AT_LINK_REPLACE
In reply to a message not in the archiveFrom: Omar Sandoval <[email protected]>
--- io/link.c | 24 ++++++++++++++++++++---- man/man8/xfs_io.8 | 9 ++++++++- 2 files changed, 28 insertions(+), 5 deletions(-)
io/link.c +20 -4
@@ -12,6 +12,9 @@#ifndef AT_EMPTY_PATH #define AT_EMPTY_PATH 0x1000#endif +#ifndef AT_LINK_REPLACE +#define AT_LINK_REPLACE 0x10000 +#endif static cmdinfo_t flink_cmd;
@@ -22,6 +25,7 @@ flink_help(void)"\n" "link the open file descriptor to the supplied filename\n" "\n" +" -f -- overwrite the target filename if it exists (AT_LINK_REPLACE)\n" "\n")); }
@@ -30,10 +34,22 @@ flink_f(int argc, char **argv) { -if (argc !=2 ) + int flags = AT_EMPTY_PATH; +int c; + +while ((c =getopt (argc, argv,"f" )) !=EOF ) { +switch (c) { +case 'f' : + flags |=AT_LINK_REPLACE ; +break ; +default : +return command_usage (&flink_cmd); + } + } +if (optind != argc -1 )return command_usage (&flink_cmd); -if (linkat (file->fd,"" ,AT_FDCWD , argv[1 ],AT_EMPTY_PATH ) <0 ) { +if (linkat (file->fd,"" ,AT_FDCWD , argv[optind], flags) <0 ) {perror ("flink" );return 0 ; }
@@ -46,9 +62,9 @@ flink_init(void) flink_cmd.name ="flink" ; flink_cmd.cfunc = flink_f; flink_cmd.argmin =1 ; - flink_cmd.argmax =1 ; + flink_cmd.argmax =-1 ; flink_cmd.flags =CMD_NOMAP_OK |CMD_FOREIGN_OK |CMD_FLAG_ONESHOT ; - flink_cmd.args =_ ("filename" ); + flink_cmd.args =_ ("[-f] filename" ); flink_cmd.oneline =_ ("link the open file descriptor to the supplied filename" ); flink_cmd.help = flink_help;
man/man8/xfs_io.8 +8 -1
@@ -807,8 +807,15 @@ for the full list) is available via the .B help command. .TP -.BI "flink " path +.BI "flink [ \-f ]" " path" Link the currently open file descriptor into the filesystem namespace. +.RS 1.0i +.PD 0 +.TP 0.4i +.B \-f +overwrite the target path if it exists (AT_LINK_REPLACE). +.PD +.RE .TP .BR stat " [ " \-v "|" \-r " ]" Selected statistics from
Signature · 1 line
2.25.0
v4 cover fs: add flag to linkat() for replacing destination
In reply to a message not in the archiveFrom: Omar Sandoval <[email protected]>
[Sorry if you got this twice, I botched the Cc's on the previous
submission.]
Hello,
This series adds an AT_LINK_REPLACE flag to linkat() which allows atomically replacing the destination if it exists. This is a respin of an old series [1] that I was convinced to resend due to some recent interest [2][3].
Patch 1 adds a flags argument to i_ops->link() in preparation. Patch 2 adds the VFS support. Patch 3 fixes an inode leak in btrfs_link(), included in this series because it conflicts with patch 4. Patch 4 adds support for AT_LINK_REPLACE to Btrfs.
I've also included a man-page patch (with an example program), an xfs_io patch, and an fstest.
Some outstanding issues:
- The Btrfs implementation does a d_drop() on the replaced dentry. We probably want a d_replace() helper for filesystems to use.
- Should AT_LINK_REPLACE be limited to O_TMPFILE? In my opinion, the answer is no. After all, ln -f is not that exotic.
- Should AT_LINK_REPLACE guarantee data integrity? Again, I think the answer is no. That's more suited to something like Amir's AT_ATOMIC proposal [4].
Changes since v3:
- Rebased on v5.5.
- Added patches 1 and 3.
- Incorporated Al's feedback on various error cases in patch 2.
- Renamed the flag to AT_LINK_REPLACE.
Thanks!
1: https://lore.kernel.org/linux-fsdevel/[email protected]/
2: https://lore.kernel.org/linux-fsdevel/3326.1579019665-S6HVgzuS8uM4Awkfq6JHfwNdhmdF6hFW@public.gmane.org/
3: https://lore.kernel.org/linux-fsdevel/364531.1579265357-S6HVgzuS8uM4Awkfq6JHfwNdhmdF6hFW@public.gmane.org/
4: https://lore.kernel.org/linux-fsdevel/20190527172655.9287-1-amir73il-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org/
Omar Sandoval (4):
fs: add flags argument to i_op->link()
fs: add AT_LINK_REPLACE flag for linkat() which replaces the target
Btrfs: fix inode reference count leak in btrfs_link() error path
Btrfs: add support for linkat() AT_REPLACE
fs/9p/vfs_inode.c | 5 +- fs/9p/vfs_inode_dotl.c | 5 +- fs/affs/affs.h | 2 +- fs/affs/namei.c | 6 +- fs/afs/dir.c | 7 +- fs/bad_inode.c | 2 +- fs/bfs/dir.c | 7 +- fs/btrfs/inode.c | 70 ++++++++++++++-- fs/ceph/dir.c | 5 +- fs/cifs/cifsfs.h | 2 +- fs/cifs/link.c | 5 +- fs/coda/dir.c | 5 +- fs/ecryptfs/inode.c | 7 +- fs/ext2/namei.c | 5 +- fs/ext4/namei.c | 7 +- fs/f2fs/namei.c | 5 +- fs/fuse/dir.c | 5 +- fs/gfs2/inode.c | 5 +- fs/hfsplus/dir.c | 5 +- fs/hostfs/hostfs_kern.c | 5 +- fs/jffs2/dir.c | 8 +- fs/jfs/namei.c | 7 +- fs/libfs.c | 6 +- fs/minix/namei.c | 5 +- fs/namei.c | 166 +++++++++++++++++++++++++++++-------- fs/nfs/dir.c | 6 +- fs/nfs/internal.h | 2 +- fs/nfsd/vfs.c | 2 +- fs/nilfs2/namei.c | 5 +- fs/ocfs2/namei.c | 6 +- fs/overlayfs/dir.c | 5 +- fs/overlayfs/overlayfs.h | 2 +- fs/reiserfs/namei.c | 5 +- fs/sysv/namei.c | 7 +- fs/ubifs/dir.c | 5 +- fs/udf/namei.c | 5 +- fs/ufs/namei.c | 5 +- fs/xfs/xfs_iops.c | 6 +- include/linux/fs.h | 6 +- include/uapi/linux/fcntl.h | 1 + mm/shmem.c | 6 +- 41 files changed, 341 insertions(+), 90 deletions(-)
Signature · 1 line
2.25.0
RFC PATCH v4 1/4 fs: add flags argument to i_op->link()
In reply to a message not in the archiveFrom: Omar Sandoval <[email protected]>
In preparation for adding the AT_LINK_REPLACE flag, make ->link() take and check a flags argument.
--- fs/9p/vfs_inode.c | 5 ++++- fs/9p/vfs_inode_dotl.c | 5 ++++- fs/affs/affs.h | 2 +- fs/affs/namei.c | 6 +++++- fs/afs/dir.c | 7 +++++-- fs/bad_inode.c | 2 +- fs/bfs/dir.c | 7 +++++-- fs/btrfs/inode.c | 5 ++++- fs/ceph/dir.c | 5 ++++- fs/cifs/cifsfs.h | 2 +- fs/cifs/link.c | 5 ++++- fs/coda/dir.c | 5 ++++- fs/ecryptfs/inode.c | 5 ++++- fs/ext2/namei.c | 5 ++++- fs/ext4/namei.c | 7 +++++-- fs/f2fs/namei.c | 5 ++++- fs/fuse/dir.c | 5 ++++- fs/gfs2/inode.c | 5 ++++- fs/hfsplus/dir.c | 5 ++++- fs/hostfs/hostfs_kern.c | 5 ++++- fs/jffs2/dir.c | 8 ++++++-- fs/jfs/namei.c | 7 +++++-- fs/libfs.c | 6 +++++- fs/minix/namei.c | 5 ++++- fs/namei.c | 2 +- fs/nfs/dir.c | 6 +++++- fs/nfs/internal.h | 2 +- fs/nilfs2/namei.c | 5 ++++- fs/ocfs2/namei.c | 6 +++++- fs/overlayfs/dir.c | 5 ++++- fs/reiserfs/namei.c | 5 ++++- fs/sysv/namei.c | 7 +++++-- fs/ubifs/dir.c | 5 ++++- fs/udf/namei.c | 5 ++++- fs/ufs/namei.c | 5 ++++- fs/xfs/xfs_iops.c | 6 +++++- include/linux/fs.h | 4 ++-- mm/shmem.c | 6 +++++- 38 files changed, 148 insertions(+), 45 deletions(-)
fs/9p/vfs_inode.c +4 -1
@@ -1335,12 +1335,15 @@ v9fs_vfs_symlink(struct inode *dir, struct dentry *dentry, const char *symname)static int v9fs_vfs_link (struct dentry *old_dentry,struct inode *dir, -struct dentry *dentry) +struct dentry *dentry,int flags) {int retval;char name[1 +U32_MAX_DIGITS +2 ];/* sign + number + \n + \0 */ struct p9_fid *oldfid; +if (flags) +return -EINVAL ; +p9_debug (P9_DEBUG_VFS ," %lu,%pd,%pd\n" , dir->i_ino, dentry, old_dentry);
fs/9p/vfs_inode_dotl.c +4 -1
@@ -749,12 +749,15 @@ v9fs_vfs_symlink_dotl(struct inode *dir, struct dentry *dentry,static int v9fs_vfs_link_dotl (struct dentry *old_dentry,struct inode *dir, -struct dentry *dentry) +struct dentry *dentry,int flags) {int err;struct p9_fid *dfid, *oldfid;struct v9fs_session_info *v9ses; +if (flags) +return -EINVAL ; +p9_debug (P9_DEBUG_VFS ,"dir ino: %lu, old_name: %pd, new_name: %pd\n" , dir->i_ino, old_dentry, dentry);
fs/affs/affs.h +1 -1
@@ -171,7 +171,7 @@ extern int affs_create(struct inode *dir, struct dentry *dentry, umode_t mode, bextern int affs_mkdir (struct inode *dir,struct dentry *dentry,umode_t mode);extern int affs_rmdir (struct inode *dir,struct dentry *dentry);extern int affs_link (struct dentry *olddentry,struct inode *dir, -struct dentry *dentry); +struct dentry *dentry,int flags);extern int affs_symlink (struct inode *dir,struct dentry *dentry,const char *symname); extern int affs_rename2(struct inode *old_dir, struct dentry *old_dentry,
fs/affs/namei.c +5 -1
@@ -388,10 +388,14 @@ affs_symlink(struct inode *dir, struct dentry *dentry, const char *symname) }int -affs_link (struct dentry *old_dentry,struct inode *dir,struct dentry *dentry) +affs_link (struct dentry *old_dentry,struct inode *dir,struct dentry *dentry, +int flags) {struct inode *inode =d_inode (old_dentry); +if (flags) +return -EINVAL ; +pr_debug ("%s(%lu, %lu, \"%pd\")\n" , __func__, inode->i_ino, dir->i_ino, dentry);
fs/afs/dir.c +5 -2
@@ -34,7 +34,7 @@ static int afs_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode);static int afs_rmdir (struct inode *dir,struct dentry *dentry);static int afs_unlink (struct inode *dir,struct dentry *dentry);static int afs_link (struct dentry *from,struct inode *dir, -struct dentry *dentry); +struct dentry *dentry,int flags);static int afs_symlink (struct inode *dir,struct dentry *dentry,const char *content); static int afs_rename(struct inode *old_dir, struct dentry *old_dentry,
@@ -1641,7 +1641,7 @@ static int afs_create(struct inode *dir, struct dentry *dentry, umode_t mode,* create a hard link between files in an AFS filesystem */ static int afs_link (struct dentry *from,struct inode *dir, -struct dentry *dentry) +struct dentry *dentry,int flags) {struct afs_fs_cursor fc;struct afs_status_cb *scb;
@@ -1650,6 +1650,9 @@ static int afs_link(struct dentry *from, struct inode *dir,struct key *key;int ret; +if (flags) +return -EINVAL ; + _enter("{%llx:%llu},{%llx:%llu},{%pd}", vnode->fid.vid, vnode->fid.vnode, dvnode->fid.vid, dvnode->fid.vnode,
fs/bad_inode.c +1 -1
@@ -39,7 +39,7 @@ static struct dentry *bad_inode_lookup(struct inode *dir, }static int bad_inode_link (struct dentry *old_dentry,struct inode *dir, -struct dentry *dentry) +struct dentry *dentry,int flags) {return -EIO ; }
fs/bfs/dir.c +5 -2
@@ -144,13 +144,16 @@ static struct dentry *bfs_lookup(struct inode *dir, struct dentry *dentry,return d_splice_alias (inode, dentry); } -static int bfs_link (struct dentry *old,struct inode *dir, -struct dentry *new) +static int bfs_link (struct dentry *old,struct inode *dir,struct dentry *new, +int flags) {struct inode *inode =d_inode (old);struct bfs_sb_info *info =BFS_SB (inode->i_sb);int err; +if (flags) +return -EINVAL ; +mutex_lock (&info->bfs_lock); err =bfs_add_entry (dir, &new->d_name, inode->i_ino);if (err) {
fs/btrfs/inode.c +4 -1
@@ -6759,7 +6759,7 @@ static int btrfs_create(struct inode *dir, struct dentry *dentry, }static int btrfs_link (struct dentry *old_dentry,struct inode *dir, -struct dentry *dentry) +struct dentry *dentry,int flags) {struct btrfs_trans_handle *trans =NULL ;struct btrfs_root *root =BTRFS_I (dir)->root;
@@ -6769,6 +6769,9 @@ static int btrfs_link(struct dentry *old_dentry, struct inode *dir,int err;int drop_inode =0 ; +if (flags) +return -EINVAL ; +/* do not allow sys_link's with other subvols of the same device */ if (root->root_key.objectid !=BTRFS_I (inode)->root->root_key.objectid)return -EXDEV ;
fs/ceph/dir.c +4 -1
@@ -999,13 +999,16 @@ static int ceph_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode) }static int ceph_link (struct dentry *old_dentry,struct inode *dir, -struct dentry *dentry) +struct dentry *dentry,int flags) {struct ceph_fs_client *fsc =ceph_sb_to_client (dir->i_sb);struct ceph_mds_client *mdsc = fsc->mdsc;struct ceph_mds_request *req;int err; +if (flags) +return -EINVAL ; +if (ceph_snap (dir) !=CEPH_NOSNAP )return -EROFS ;
fs/cifs/cifsfs.h +1 -1
@@ -69,7 +69,7 @@ extern int cifs_atomic_open(struct inode *, struct dentry *,extern struct dentry *cifs_lookup (struct inode *,struct dentry *,unsigned int );extern int cifs_unlink (struct inode *dir,struct dentry *dentry); -extern int cifs_hardlink (struct dentry *,struct inode *,struct dentry *); +extern int cifs_hardlink (struct dentry *,struct inode *,struct dentry *,int );extern int cifs_mknod (struct inode *,struct dentry *,umode_t ,dev_t );extern int cifs_mkdir (struct inode *,struct dentry *,umode_t );extern int cifs_rmdir (struct inode *,struct dentry *);
fs/cifs/link.c +4 -1
@@ -516,7 +516,7 @@ smb3_create_mf_symlink(unsigned int xid, struct cifs_tcon *tcon,int cifs_hardlink (struct dentry *old_file,struct inode *inode, -struct dentry *direntry) +struct dentry *direntry,int flags) {int rc = -EACCES ;unsigned int xid;
@@ -528,6 +528,9 @@ cifs_hardlink(struct dentry *old_file, struct inode *inode,struct TCP_Server_Info *server;struct cifsInodeInfo *cifsInode; +if (flags) +return -EINVAL ; + tlink =cifs_sb_tlink (cifs_sb);if (IS_ERR (tlink))return PTR_ERR (tlink);
fs/coda/dir.c +4 -1
@@ -200,13 +200,16 @@ static int coda_mkdir(struct inode *dir, struct dentry *de, umode_t mode)/* try to make de an entry in dir_inodde linked to source_de */ static int coda_link (struct dentry *source_de,struct inode *dir_inode, -struct dentry *de) +struct dentry *de,int flags) {struct inode *inode =d_inode (source_de);const char * name = de->d_name.name;int len = de->d_name.len;int error; +if (flags) +return -EINVAL ; +if (is_root_inode (dir_inode) &&coda_iscontrol (name, len))return -EPERM ;
fs/ecryptfs/inode.c +4 -1
@@ -422,7 +422,7 @@ static struct dentry *ecryptfs_lookup(struct inode *ecryptfs_dir_inode, }static int ecryptfs_link (struct dentry *old_dentry,struct inode *dir, -struct dentry *new_dentry) +struct dentry *new_dentry,int flags) {struct dentry *lower_old_dentry;struct dentry *lower_new_dentry;
@@ -430,6 +430,9 @@ static int ecryptfs_link(struct dentry *old_dentry, struct inode *dir,u64 file_size_save;int rc; +if (flags) +return -EINVAL ; + file_size_save =i_size_read (d_inode (old_dentry)); lower_old_dentry =ecryptfs_dentry_to_lower (old_dentry); lower_new_dentry =ecryptfs_dentry_to_lower (new_dentry);
fs/ext2/namei.c +4 -1
@@ -196,11 +196,14 @@ static int ext2_symlink (struct inode * dir, struct dentry * dentry, }static int ext2_link (struct dentry * old_dentry,struct inode * dir, -struct dentry *dentry) +struct dentry *dentry,int flags) {struct inode *inode =d_inode (old_dentry);int err; +if (flags) +return -EINVAL ; + err =dquot_initialize (dir);if (err)return err;
fs/ext4/namei.c +5 -2
@@ -3366,13 +3366,16 @@ static int ext4_symlink(struct inode *dir,return err; } -static int ext4_link (struct dentry *old_dentry, -struct inode *dir,struct dentry *dentry) +static int ext4_link (struct dentry *old_dentry,struct inode *dir, +struct dentry *dentry,int flags) {handle_t *handle;struct inode *inode =d_inode (old_dentry);int err, retries =0 ; +if (flags) +return -EINVAL ; +if (inode->i_nlink >=EXT4_LINK_MAX )return -EMLINK ;
fs/f2fs/namei.c +4 -1
@@ -312,12 +312,15 @@ static int f2fs_create(struct inode *dir, struct dentry *dentry, umode_t mode, }static int f2fs_link (struct dentry *old_dentry,struct inode *dir, -struct dentry *dentry) +struct dentry *dentry,int flags) {struct inode *inode =d_inode (old_dentry);struct f2fs_sb_info *sbi =F2FS_I_SB (dir);int err; +if (flags) +return -EINVAL ; +if (unlikely (f2fs_cp_error (sbi)))return -EIO ;if (!f2fs_is_checkpoint_ready (sbi))
fs/fuse/dir.c +4 -1
@@ -842,7 +842,7 @@ static int fuse_rename2(struct inode *olddir, struct dentry *oldent, }static int fuse_link (struct dentry *entry,struct inode *newdir, -struct dentry *newent) +struct dentry *newent,int flags) {int err;struct fuse_link_in inarg;
@@ -850,6 +850,9 @@ static int fuse_link(struct dentry *entry, struct inode *newdir,struct fuse_conn *fc =get_fuse_conn (inode);FUSE_ARGS (args); +if (flags) +return -EINVAL ; +memset (&inarg,0 ,sizeof (inarg)); inarg.oldnodeid =get_node_id (inode); args.opcode =FUSE_LINK ;
fs/gfs2/inode.c +4 -1
@@ -891,7 +891,7 @@ static struct dentry *gfs2_lookup(struct inode *dir, struct dentry *dentry,*/ static int gfs2_link (struct dentry *old_dentry,struct inode *dir, -struct dentry *dentry) +struct dentry *dentry,int flags) {struct gfs2_inode *dip =GFS2_I (dir);struct gfs2_sbd *sdp =GFS2_SB (dir);
@@ -902,6 +902,9 @@ static int gfs2_link(struct dentry *old_dentry, struct inode *dir,struct gfs2_diradd da = { .bh =NULL , .save_loc =1 , };int error; +if (flags) +return -EINVAL ; +if (S_ISDIR (inode->i_mode))return -EPERM ;
fs/hfsplus/dir.c +4 -1
@@ -298,7 +298,7 @@ static int hfsplus_dir_release(struct inode *inode, struct file *file) }static int hfsplus_link (struct dentry *src_dentry,struct inode *dst_dir, -struct dentry *dst_dentry) +struct dentry *dst_dentry,int flags) {struct hfsplus_sb_info *sbi =HFSPLUS_SB (dst_dir->i_sb);struct inode *inode =d_inode (src_dentry);
@@ -308,6 +308,9 @@ static int hfsplus_link(struct dentry *src_dentry, struct inode *dst_dir,u32 cnid, id;int res; +if (flags) +return -EINVAL ; +if (HFSPLUS_IS_RSRC (inode))return -EPERM ;if (!S_ISREG (inode->i_mode))
fs/hostfs/hostfs_kern.c +4 -1
@@ -622,11 +622,14 @@ static struct dentry *hostfs_lookup(struct inode *ino, struct dentry *dentry, }static int hostfs_link (struct dentry *to,struct inode *ino, -struct dentry *from) +struct dentry *from,int flags) {char *from_name, *to_name;int err; +if (flags) +return -EOPNOTSUPP ; +if ((from_name =dentry_name (from)) ==NULL )return -ENOMEM ; to_name =dentry_name (to);
fs/jffs2/dir.c +6 -2
@@ -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 deuint8_t type;uint32_t now; +if (flags) +return -EINVAL ; +/* Don't let people make hard links to bad inodes. */ if (!f->inocache)return -EIO ;
fs/jfs/namei.c +5 -2
@@ -781,8 +781,8 @@ void jfs_free_zero_link(struct inode *ip)* EXDEV: target object and new link are on different file systems and * implementation does not support links between file systems [XPG4.2]. */ -static int jfs_link (struct dentry *old_dentry, -struct inode *dir,struct dentry *dentry) +static int jfs_link (struct dentry *old_dentry,struct inode *dir, +struct dentry *dentry,int flags) {int rc;tid_t tid;
@@ -792,6 +792,9 @@ static int jfs_link(struct dentry *old_dentry,struct btstack btstack;struct inode *iplist[2 ]; +if (flags) +return -EINVAL ; +jfs_info ("jfs_link: %pd %pd" , old_dentry, dentry); rc =dquot_initialize (dir);
fs/libfs.c +5 -1
@@ -318,10 +318,14 @@ int simple_open(struct inode *inode, struct file *file) }EXPORT_SYMBOL (simple_open); -int simple_link (struct dentry *old_dentry,struct inode *dir,struct dentry *dentry) +int simple_link (struct dentry *old_dentry,struct inode *dir, +struct dentry *dentry,int flags) {struct inode *inode =d_inode (old_dentry); +if (flags) +return -EINVAL ; + inode->i_ctime = dir->i_ctime = dir->i_mtime =current_time (inode);inc_nlink (inode);ihold (inode);
fs/minix/namei.c +4 -1
@@ -99,10 +99,13 @@ static int minix_symlink(struct inode * dir, struct dentry *dentry, }static int minix_link (struct dentry * old_dentry,struct inode * dir, -struct dentry *dentry) +struct dentry *dentry,int flags) {struct inode *inode =d_inode (old_dentry); +if (flags) +return -EINVAL ; + inode->i_ctime =current_time (inode);inode_inc_link_count (inode);ihold (inode);
fs/namei.c +1 -1
@@ -4181,7 +4181,7 @@ int vfs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_deelse { error =try_break_deleg (inode, delegated_inode);if (!error) - error = dir->i_op->link (old_dentry, dir, new_dentry); + error = dir->i_op->link (old_dentry, dir, new_dentry,0 ); }if (!error && (inode->i_state &I_LINKABLE )) {
fs/nfs/dir.c +5 -1
@@ -1991,11 +1991,15 @@ int nfs_symlink(struct inode *dir, struct dentry *dentry, const char *symname)EXPORT_SYMBOL_GPL (nfs_symlink);int -nfs_link (struct dentry *old_dentry,struct inode *dir,struct dentry *dentry) +nfs_link (struct dentry *old_dentry,struct inode *dir,struct dentry *dentry, +int flags) {struct inode *inode =d_inode (old_dentry);int error; +if (flags) +return -EINVAL ; +dfprintk (VFS ,"NFS: link(%pd2 -> %pd2)\n" , old_dentry, dentry);
fs/nfs/internal.h +1 -1
@@ -356,7 +356,7 @@ int nfs_mkdir(struct inode *, struct dentry *, umode_t);int nfs_rmdir (struct inode *,struct dentry *);int nfs_unlink (struct inode *,struct dentry *);int nfs_symlink (struct inode *,struct dentry *,const char *); -int nfs_link (struct dentry *,struct inode *,struct dentry *); +int nfs_link (struct dentry *,struct inode *,struct dentry *,int );int nfs_mknod (struct inode *,struct dentry *,umode_t ,dev_t );int nfs_rename (struct inode *,struct dentry *,struct inode *,struct dentry *,unsigned int );
fs/nilfs2/namei.c +4 -1
@@ -174,12 +174,15 @@ static int nilfs_symlink(struct inode *dir, struct dentry *dentry, }static int nilfs_link (struct dentry *old_dentry,struct inode *dir, -struct dentry *dentry) +struct dentry *dentry,int flags) {struct inode *inode =d_inode (old_dentry);struct nilfs_transaction_info ti;int err; +if (flags) +return -EINVAL ; + err =nilfs_transaction_begin (dir->i_sb, &ti,1 );if (err)return err;
fs/ocfs2/namei.c +5 -1
@@ -672,7 +672,8 @@ static int ocfs2_create(struct inode *dir,static int ocfs2_link (struct dentry *old_dentry,struct inode *dir, -struct dentry *dentry) +struct dentry *dentry, +int flags) {handle_t *handle;struct inode *inode =d_inode (old_dentry);
@@ -687,6 +688,9 @@ static int ocfs2_link(struct dentry *old_dentry,sigset_t oldset;u64 old_de_ino; +if (flags) +return -EINVAL ; +trace_ocfs2_link ((unsigned long long )OCFS2_I (inode)->ip_blkno, old_dentry->d_name.len, old_dentry->d_name.name, dentry->d_name.len, dentry->d_name.name);
fs/overlayfs/dir.c +4 -1
@@ -661,11 +661,14 @@ static int ovl_set_link_redirect(struct dentry *dentry) }static int ovl_link (struct dentry *old,struct inode *newdir, -struct dentry *new) +struct dentry *new,int flags) {int err;struct inode *inode; +if (flags) +return -EINVAL ; + err =ovl_want_write (old);if (err) goto out;
fs/reiserfs/namei.c +4 -1
@@ -1195,7 +1195,7 @@ static int reiserfs_symlink(struct inode *parent_dir, }static int reiserfs_link (struct dentry *old_dentry,struct inode *dir, -struct dentry *dentry) +struct dentry *dentry,int flags) {int retval;struct inode *inode =d_inode (old_dentry);
@@ -1208,6 +1208,9 @@ static int reiserfs_link(struct dentry *old_dentry, struct inode *dir,JOURNAL_PER_BALANCE_CNT *3 +2 *REISERFS_QUOTA_TRANS_BLOCKS (dir->i_sb); +if (flags) +return -EINVAL ; + retval =dquot_initialize (dir);if (retval)return retval;
fs/sysv/namei.c +5 -2
@@ -96,11 +96,14 @@ static int sysv_symlink(struct inode * dir, struct dentry * dentry, goto out; } -static int sysv_link (struct dentry * old_dentry,struct inode * dir, -struct dentry * dentry) +static int sysv_link (struct dentry *old_dentry,struct inode *dir, +struct dentry *dentry,int flags) {struct inode *inode =d_inode (old_dentry); +if (flags) +return -EINVAL ; + inode->i_ctime =current_time (inode);inode_inc_link_count (inode);ihold (inode);
fs/ubifs/dir.c +4 -1
@@ -683,7 +683,7 @@ static void unlock_2_inodes(struct inode *inode1, struct inode *inode2) }static int ubifs_link (struct dentry *old_dentry,struct inode *dir, -struct dentry *dentry) +struct dentry *dentry,int flags) {struct ubifs_info *c = dir->i_sb->s_fs_info;struct inode *inode =d_inode (old_dentry);
@@ -694,6 +694,9 @@ static int ubifs_link(struct dentry *old_dentry, struct inode *dir, .dirtied_ino_d = ALIGN(ui->data_len, 8) };struct fscrypt_name nm; +if (flags) +return -EINVAL ; +/* * Budget request settings: new direntry, changing the target inode, * changing the parent inode.
fs/udf/namei.c +4 -1
@@ -1028,13 +1028,16 @@ static int udf_symlink(struct inode *dir, struct dentry *dentry, }static int udf_link (struct dentry *old_dentry,struct inode *dir, -struct dentry *dentry) +struct dentry *dentry,int flags) {struct inode *inode =d_inode (old_dentry);struct udf_fileident_bh fibh;struct fileIdentDesc cfi, *fi;int err; +if (flags) +return -EINVAL ; + fi =udf_add_entry (dir, dentry, &fibh, &cfi, &err);if (!fi) {return err;
fs/ufs/namei.c +4 -1
@@ -146,11 +146,14 @@ static int ufs_symlink (struct inode * dir, struct dentry * dentry, }static int ufs_link (struct dentry * old_dentry,struct inode * dir, -struct dentry *dentry) +struct dentry *dentry,int flags) {struct inode *inode =d_inode (old_dentry);int error; +if (flags) +return -EINVAL ; + inode->i_ctime =current_time (inode);inode_inc_link_count (inode);ihold (inode);
fs/xfs/xfs_iops.c +5 -1
@@ -311,12 +311,16 @@ STATIC intxfs_vn_link ( struct dentry *old_dentry, struct inode *dir, - struct dentry *dentry) + struct dentry *dentry, + int flags) { struct inode *inode = d_inode(old_dentry);struct xfs_name name;int error; +if (flags) +return -EINVAL ; + error =xfs_dentry_mode_to_name (&name, dentry, inode->i_mode);if (unlikely (error))return error;
include/linux/fs.h +2 -2
@@ -1869,7 +1869,7 @@ struct inode_operations {int (*readlink) (struct dentry *,char __user *,int );int (*create) (struct inode *,struct dentry *,umode_t ,bool ); -int (*link) (struct dentry *,struct inode *,struct dentry *); +int (*link) (struct dentry *,struct inode *,struct dentry *,int );int (*unlink) (struct inode *,struct dentry *);int (*symlink) (struct inode *,struct dentry *,const char *);int (*mkdir) (struct inode *,struct dentry *,umode_t );
@@ -3298,7 +3298,7 @@ extern int simple_setattr(struct dentry *, struct iattr *);extern int simple_getattr (const struct path *,struct kstat *,u32 ,unsigned int );extern int simple_statfs (struct dentry *,struct kstatfs *);extern int simple_open (struct inode *inode,struct file *file); -extern int simple_link (struct dentry *,struct inode *,struct dentry *); +extern int simple_link (struct dentry *,struct inode *,struct dentry *,int );extern int simple_unlink (struct inode *,struct dentry *);extern int simple_rmdir (struct inode *,struct dentry *); extern int simple_rename(struct inode *, struct dentry *,
mm/shmem.c +5 -1
@@ -2948,11 +2948,15 @@ static int shmem_create(struct inode *dir, struct dentry *dentry, umode_t mode,/* * Link a file.. */ -static int shmem_link (struct dentry *old_dentry,struct inode *dir,struct dentry *dentry) +static int shmem_link (struct dentry *old_dentry,struct inode *dir, +struct dentry *dentry,int flags) {struct inode *inode =d_inode (old_dentry);int ret =0 ; +if (flags) +return -EINVAL ; +/* * No ordinary (disk based) filesystem counts links as inodes; * but each new link needs a new dentry, pinning lowmem, and
Signature · 1 line
2.25.0
RFC PATCH v4 2/4 fs: add AT_LINK_REPLACE flag for linkat() which replaces the target
In reply to a message not in the archiveFrom: Omar Sandoval <[email protected]>
One of the most common uses of temporary files is the classic atomic replacement pattern, i.e.,
- write temporary file
- fsync temporary file
- rename temporary file over real file
- fsync parent directory
Now, we have O_TMPFILE, which gives us a much better way to create temporary files, but it's not possible to use it for this pattern.
This patch introduces an AT_LINK_REPLACE flag which allows linkat() to replace the target file. Now, the temporary file in the pattern above can be a proper O_TMPFILE. Even without O_TMPFILE, this is a new primitive which might be useful in other contexts.
The implementation on the VFS side mimics sys_renameat2().
--- fs/ecryptfs/inode.c | 2 +- fs/namei.c | 166 +++++++++++++++++++++++++++++-------- fs/nfsd/vfs.c | 2 +- fs/overlayfs/overlayfs.h | 2 +- include/linux/fs.h | 2 +- include/uapi/linux/fcntl.h | 1 + 6 files changed, 135 insertions(+), 40 deletions(-)
fs/ecryptfs/inode.c +1 -1
@@ -440,7 +440,7 @@ static int ecryptfs_link(struct dentry *old_dentry, struct inode *dir,dget (lower_new_dentry); lower_dir_dentry =lock_parent (lower_new_dentry); rc =vfs_link (lower_old_dentry,d_inode (lower_dir_dentry), - lower_new_dentry,NULL ); + lower_new_dentry,NULL ,0 );if (rc ||d_really_is_negative (lower_new_dentry)) goto out_lock; rc =ecryptfs_interpose (lower_new_dentry, new_dentry, dir->i_sb);
fs/namei.c +130 -36
233 lines not on this page · raw message
fs/nfsd/vfs.c +1 -1
@@ -1598,7 +1598,7 @@ nfsd_link(struct svc_rqst *rqstp, struct svc_fh *ffhp, err = nfserr_noent;if (d_really_is_negative (dold)) goto out_dput; - host_err =vfs_link (dold, dirp, dnew,NULL ); + host_err =vfs_link (dold, dirp, dnew,NULL ,0 );if (!host_err) { err =nfserrno (commit_metadata (ffhp));if (!err)
fs/overlayfs/overlayfs.h +1 -1
@@ -120,7 +120,7 @@ static inline int ovl_do_unlink(struct inode *dir, struct dentry *dentry)static inline int ovl_do_link (struct dentry *old_dentry,struct inode *dir,struct dentry *new_dentry) { -int err =vfs_link (old_dentry, dir, new_dentry,NULL ); +int err =vfs_link (old_dentry, dir, new_dentry,NULL ,0 );pr_debug ("link(%pd2, %pd2) = %i\n" , old_dentry, new_dentry, err);return err;
include/linux/fs.h +1 -1
@@ -1712,7 +1712,7 @@ extern int vfs_create(struct inode *, struct dentry *, umode_t, bool);extern int vfs_mkdir (struct inode *,struct dentry *,umode_t );extern int vfs_mknod (struct inode *,struct dentry *,umode_t ,dev_t );extern int vfs_symlink (struct inode *,struct dentry *,const char *); -extern int vfs_link (struct dentry *,struct inode *,struct dentry *,struct inode **); +extern int vfs_link (struct dentry *,struct inode *,struct dentry *,struct inode **,int );extern int vfs_rmdir (struct inode *,struct dentry *);extern int vfs_unlink (struct inode *,struct dentry *,struct inode **);extern int vfs_rename (struct inode *,struct dentry *,struct inode *,struct dentry *,struct inode **,unsigned int );
include/uapi/linux/fcntl.h +1 -0
@@ -99,6 +99,7 @@#define AT_STATX_DONT_SYNC 0x4000/* - Don't sync attributes with the server */ #define AT_RECURSIVE 0x8000/* Apply to the entire subtree */ +#define AT_LINK_REPLACE 0x10000/* Replace link() target */ #endif /* _UAPI_LINUX_FCNTL_H */
Signature · 1 line
2.25.0
RFC PATCH v4 3/4 Btrfs: fix inode reference count leak in btrfs_link() error path
In reply to a message not in the archiveFrom: Omar Sandoval <[email protected]>
If btrfs_update_inode() or btrfs_orphan_del() fails in btrfs_link(), then we don't drop the reference we got with ihold(). This results in the "VFS: Busy inodes after unmount" crash.
The reference is needed for the new dentry, so get it right before we instantiate the dentry.
[Although d_instantiate() was moved further from ihold() before that, in commit 08c422c27f85 ("Btrfs: call d_instantiate after all ops are setup")]
--- fs/btrfs/inode.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-)
fs/btrfs/inode.c +2 -4
@@ -6801,7 +6801,6 @@ static int btrfs_link(struct dentry *old_dentry, struct inode *dir,inc_nlink (inode);inode_inc_iversion (inode); inode->i_ctime =current_time (inode); -ihold (inode);set_bit (BTRFS_INODE_COPY_EVERYTHING , &BTRFS_I (inode)->runtime_flags); err = btrfs_add_nondir(trans, BTRFS_I(dir), dentry, BTRFS_I(inode),
@@ -6825,6 +6824,7 @@ static int btrfs_link(struct dentry *old_dentry, struct inode *dir,if (err) goto fail; } +ihold (inode);d_instantiate (dentry, inode); ret =btrfs_log_new_name (trans,BTRFS_I (inode),NULL , parent, true,NULL );
@@ -6837,10 +6837,8 @@ static int btrfs_link(struct dentry *old_dentry, struct inode *dir, fail:if (trans)btrfs_end_transaction (trans); -if (drop_inode) { +if (drop_inode)inode_dec_link_count (inode); -iput (inode); - }btrfs_btree_balance_dirty (fs_info);return err; }
Signature · 1 line
2.25.0
RFC PATCH v4 4/4 Btrfs: add support for linkat() AT_REPLACE
In reply to a message not in the archiveFrom: Omar Sandoval <[email protected]>
The implementation is fairly straightforward and looks a lot like btrfs_rename(). The only tricky bit is that instead of playing games with the dcache, we simply drop the dentry for it to be instantiated on the next lookup. This can be improved in the future.
--- fs/btrfs/inode.c | 63 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 59 insertions(+), 4 deletions(-)
fs/btrfs/inode.c +59 -4
@@ -6762,14 +6762,16 @@ static int btrfs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *dentry, int flags) {struct btrfs_trans_handle *trans =NULL ; +unsigned int trans_num_items;struct btrfs_root *root =BTRFS_I (dir)->root;struct inode *inode =d_inode (old_dentry); +struct inode *new_inode =d_inode (dentry);struct btrfs_fs_info *fs_info =btrfs_sb (inode->i_sb);u64 index;int err;int drop_inode =0 ; -if (flags) +if (flags & ~AT_LINK_REPLACE )return -EINVAL ;/* do not allow sys_link's with other subvols of the same device */
@@ -6779,17 +6781,50 @@ static int btrfs_link(struct dentry *old_dentry, struct inode *dir,if (inode->i_nlink >=BTRFS_LINK_MAX )return -EMLINK ; +/* check for collisions, even if the name isn't there */ + err =btrfs_check_dir_item_collision (root, dir->i_ino, + dentry->d_name.name, + dentry->d_name.len); +if (err) { +if (err == -EEXIST ) { +if (WARN_ON (!new_inode)) +return err; + }else { +return err; + } + } + +/* +* we're using link to replace one file with another. Start IO on it now +* so we don't add too much work to the end of the transaction +*/ +if (new_inode &&S_ISREG (inode->i_mode) && new_inode->i_size) +filemap_flush (inode->i_mapping); + err =btrfs_set_inode_index (BTRFS_I (dir), &index);if (err) goto fail;/* +* For the source: * 2 items for inode and inode ref * 2 items for dir items * 1 item for parent inode * 1 item for orphan item deletion if O_TMPFILE +* +* For the target: +* 1 for the possible orphan item +* 1 for the dir item +* 1 for the dir index +* 1 for the inode ref +* 1 for the inode */ - trans =btrfs_start_transaction (root, inode->i_nlink ?5 :6 ); + trans_num_items =5 ; +if (!inode->i_nlink) + trans_num_items++; +if (new_inode) + trans_num_items +=5 ; + trans =btrfs_start_transaction (root, trans_num_items);if (IS_ERR (trans)) { err =PTR_ERR (trans); trans =NULL ;
@@ -6801,6 +6836,22 @@ static int btrfs_link(struct dentry *old_dentry, struct inode *dir,inc_nlink (inode);inode_inc_iversion (inode); inode->i_ctime =current_time (inode); + +if (new_inode) { +inode_inc_iversion (new_inode); + new_inode->i_ctime =current_time (new_inode); + err =btrfs_unlink_inode (trans, root,BTRFS_I (dir), +BTRFS_I (new_inode), + dentry->d_name.name, + dentry->d_name.len); +if (!err && new_inode->i_nlink ==0 ) + err =btrfs_orphan_add (trans,BTRFS_I (new_inode)); +if (err) { +btrfs_abort_transaction (trans, err); + goto fail; + } + } +set_bit (BTRFS_INODE_COPY_EVERYTHING , &BTRFS_I (inode)->runtime_flags); err = btrfs_add_nondir(trans, BTRFS_I(dir), dentry, BTRFS_I(inode),
@@ -6824,8 +6875,12 @@ static int btrfs_link(struct dentry *old_dentry, struct inode *dir,if (err) goto fail; } -ihold (inode); -d_instantiate (dentry, inode); +if (new_inode) { +d_drop (dentry); + }else { +ihold (inode); +d_instantiate (dentry, inode); + } ret =btrfs_log_new_name (trans,BTRFS_I (inode),NULL , parent, true,NULL );if (ret ==BTRFS_NEED_TRANS_COMMIT ) {
Signature · 1 line
2.25.0
Re: [RFC PATCH xfsprogs] xfs_io: add support for linkat() AT_LINK_REPLACE
In reply to Omar SandovalQuoted 4 lines · Omar Sandoval
From: Omar Sandoval <[email protected]>
Signed-off-by Omar Sandoval <[email protected]>---
And this patch would be better to send to/cc [email protected] to get xfsprogs maintainers/developers review.
Quoted 87 lines
io/link.c | 24 ++++++++++++++++++++---- man/man8/xfs_io.8 | 9 ++++++++- 2 files changed, 28 insertions(+), 5 deletions(-)io/link.c +20
-4@@ -12,6 +12,9 @@#ifndef AT_EMPTY_PATH #define AT_EMPTY_PATH 0x1000#endif +#ifndef AT_LINK_REPLACE +#define AT_LINK_REPLACE 0x10000 +#endif static cmdinfo_t flink_cmd;@@ -22,6 +25,7 @@ flink_help(void)"\n" "link the open file descriptor to the supplied filename\n" "\n" +" -f -- overwrite the target filename if it exists (AT_LINK_REPLACE)\n" "\n")); }@@ -30,10 +34,22 @@ flink_f(int argc, char **argv) { -if (argc !=2 ) + int flags = AT_EMPTY_PATH; +int c; + +while ((c =getopt (argc, argv,"f" )) !=EOF ) { +switch (c) { +case 'f' : + flags |=AT_LINK_REPLACE ; +break ; +default : +return command_usage (&flink_cmd); + } + } +if (optind != argc -1 )return command_usage (&flink_cmd); -if (linkat (file->fd,"" ,AT_FDCWD , argv[1 ],AT_EMPTY_PATH ) <0 ) { +if (linkat (file->fd,"" ,AT_FDCWD , argv[optind], flags) <0 ) {perror ("flink" );return 0 ; }@@ -46,9 +62,9 @@ flink_init(void) flink_cmd.name ="flink" ; flink_cmd.cfunc = flink_f; flink_cmd.argmin =1 ; - flink_cmd.argmax =1 ; + flink_cmd.argmax =-1 ; flink_cmd.flags =CMD_NOMAP_OK |CMD_FOREIGN_OK |CMD_FLAG_ONESHOT ; - flink_cmd.args =_ ("filename" ); + flink_cmd.args =_ ("[-f] filename" ); flink_cmd.oneline =_ ("link the open file descriptor to the supplied filename" ); flink_cmd.help = flink_help;man/man8/xfs_io.8 +8
-1@@ -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 fromSignature · 1 line
2.25.0
David Howells
maintainerRe: [RFC PATCH v4 1/4] fs: add flags argument to i_op->link()
In reply to Omar Sandovalinclude/linux/fs.h struct inode_operations { · line 1872
Quoted 2 lines · Omar Sandoval
include/linux/fs.h +1
-1-int (*link) (struct dentry *,struct inode *,struct dentry *); +int (*link) (struct dentry *,struct inode *,struct dentry *,int );
Can you make it unsigned int?
David
Re: [RFC PATCH v4 1/4] fs: add flags argument to i_op->link()
Quoted 9 lines · David Howells
include/linux/fs.h struct inode_operations { · line 1872
Quoted 2 lines · Omar Sandoval
include/linux/fs.h +1
-1-int (*link) (struct dentry *,struct inode *,struct dentry *); +int (*link) (struct dentry *,struct inode *,struct dentry *,int );Can you make it unsigned int?
David
For some reason, the linkat() syscall takes an int, but you're right, might as well make it an unsigned int in the VFS.
Re: [RFC PATCH xfstests] generic: add smoke test for AT_LINK_REPLACE
In reply to Omar SandovalQuoted 4 lines · Omar Sandoval
From: Omar Sandoval <[email protected]>
Signed-off-by Omar Sandoval <[email protected]>
Looks fine overall, would you please provide more info about this AT_LINK_REPLACE flag? e.g. what's the expected behavior, what's current status (merged in kernel or still pending?), reference the related commits if already merged.
Quoted 95 lines
--- common/rc | 2 +- tests/generic/593 | 97 +++++++++++++++++++++++++++++++++++++++++++ tests/generic/593.out | 6 +++ tests/generic/group | 1 + 4 files changed, 105 insertions(+), 1 deletion(-) create mode 100755 tests/generic/593 create mode 100644 tests/generic/593.outcommon/rc +1
-1@@ -2172,7 +2172,7 @@ _require_xfs_io_command() ;; "flink") local testlink=$TEST_DIR/$$.link.xfs_io - testio=`$XFS_IO_PROG -F -f -c "flink $testlink" $testfile 2>&1` + testio=`$XFS_IO_PROG -F -f -c "flink $param $testlink" $testfile 2>&1` rm -f $testlink > /dev/null 2>&1 ;; "-T")tests/generic/593 new file +67
-0@@ -0,0 +1,97 @@ +#! /bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Copyright (c) 2020 Facebook. All Rights Reserved. +# +# FS QA Test 593 +# +# Smoke test linkat() with AT_LINK_REPLACE. +# +seq=`basename $0` +seqres=$RESULT_DIR/$seq +echo "QA output created by $seq" + +here=`pwd` +tmp=/tmp/$$ +status=1 # failure is the default! +trap "_cleanup; exit \$status" 0 1 2 3 15 + +_cleanup() +{ + cd / + rm -f $tmp.* +} + +# get standard environment, filters and checks +. ./common/rc +. ./common/filter + +# remove previous $seqres.full before test +rm -f $seqres.full + +_supported_fs generic +_supported_os Linux +_require_test +_require_xfs_io_command "-T" +_require_xfs_io_command "flink" "-f" + +same_file() { + [[ "$(stat -c '%d %i' "$1")" = "$(stat -c '%d %i' "$2")" ]] +} + +touch "$TEST_DIR/$seq.src" +touch "$TEST_DIR/$seq.tgt" +$XFS_IO_PROG -c "flink -f $TEST_DIR/$seq.tgt" "$TEST_DIR/$seq.src" +same_file "$TEST_DIR/$seq.src" "$TEST_DIR/$seq.tgt" || + echo "Target was not replaced" + +# Linking to the same file should be a noop. +$XFS_IO_PROG -c "flink -f $TEST_DIR/$seq.src" "$TEST_DIR/$seq.src" +$XFS_IO_PROG -c "flink -f $TEST_DIR/$seq.tgt" "$TEST_DIR/$seq.src" +same_file "$TEST_DIR/$seq.src" "$TEST_DIR/$seq.tgt" || echo "Target changed?" + +# Should work with O_TMPFILE. +$XFS_IO_PROG -c "flink -f $TEST_DIR/$seq.tgt" -T "$TEST_DIR" +stat -c '%h' "$TEST_DIR/$seq.tgt" +same_file "$TEST_DIR/$seq.src" "$TEST_DIR/$seq.tgt" && + echo "Target was not replaced" + +# It's okay if the target doesn't exist. +$XFS_IO_PROG -c "flink -f $TEST_DIR/$seq.tgt2" "$TEST_DIR/$seq.src" +same_file "$TEST_DIR/$seq.src" "$TEST_DIR/$seq.tgt2" || + echo "Target was not created" + +# Can't replace directories. +mkdir "$TEST_DIR/$seq.dir" +$XFS_IO_PROG -c "flink -f $TEST_DIR/$seq.dir" "$TEST_DIR/$seq.src" +cd "$TEST_DIR/$seq.dir" +$XFS_IO_PROG -c "flink -f ." "$TEST_DIR/$seq.src"
tests/generic/593 line 77
Quoted 10 lines
tests/generic/593 new file +10
-0+$XFS_IO_PROG -c "flink -f .." "$TEST_DIR/$seq.src" +cd - &> /dev/null + +# Can't replace local mount points. +touch "$TEST_DIR/$seq.mnt" +$MOUNT_PROG --bind "$TEST_DIR/$seq.mnt" "$TEST_DIR/$seq.mnt" +$XFS_IO_PROG -c "flink -f $TEST_DIR/$seq.mnt" "$TEST_DIR/$seq.src" + +# Can replace mount points in other namespaces, though. +unshare -m \
Better to define an UNSHARE_PROG in common/config and require it in this test, then use $UNSHARE_PROG here.
Thanks,
Eryu
Quoted 44 lines
tests/generic/593 new file +20
-0+ bash -c "$UMOUNT_PROG $TEST_DIR/$seq.mnt; $XFS_IO_PROG -c \"flink -f $TEST_DIR/$seq.mnt\" $TEST_DIR/$seq.src" +if $UMOUNT_PROG "$TEST_DIR/$seq.mnt" &> /dev/null; then + echo "Mount point was not detached" +fi +same_file "$TEST_DIR/$seq.src" "$TEST_DIR/$seq.mnt" || + echo "Mount point was not replaced" + +# Should replace symlinks, not follow them. +touch "$TEST_DIR/$seq.symtgt" +ln -s "$TEST_DIR/$seq.symtgt" "$TEST_DIR/$seq.sym" +$XFS_IO_PROG -c "flink -f $TEST_DIR/$seq.sym" "$TEST_DIR/$seq.src" +same_file "$TEST_DIR/$seq.src" "$TEST_DIR/$seq.sym" || + echo "Symlink was not replaced" +same_file "$TEST_DIR/$seq.src" "$TEST_DIR/$seq.symtgt" && + echo "Symlink target was replaced" + +rm -rf "$TEST_DIR/$seq."* + +status=0 +exittests/generic/593.out new file +6
-0@@ -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 busytests/generic/group +1
-0@@ -595,3 +595,4 @@ 590 auto prealloc preallocrw 591 auto quick rw pipe splice 592 auto quick encrypt +593 auto quick hardlinkSignature · 1 line
2.25.0