* [PATCH 00/11] cross rename v2 @ 2013-11-20 13:01 Miklos Szeredi 2013-11-20 13:01 ` [PATCH 01/11] vfs: add d_is_dir() Miklos Szeredi ` (10 more replies) 0 siblings, 11 replies; 22+ messages in thread From: Miklos Szeredi @ 2013-11-20 13:01 UTC (permalink / raw) To: viro Cc: torvalds, linux-fsdevel, linux-kernel, hch, akpm, dhowells, zab, jack, luto, mszeredi Al, This series adds a new syscall, renameat2(), which is the same as renameat() but with a flags argument. The purpose of extending rename is to add cross-rename, a symmetric variant of rename, which exchanges the two files. This allows interesting things, which were not possible before, for example atomically replacing a directory tree with a symlink, etc... This also allows overlayfs and friends to operate on whiteouts atomically. Andy Lutomirski also suggested a "noreplace" flag, which disables the overwriting behavior of rename. These two flags, RENAME_EXCHANGE and RENAME_NOREPLACE are only implemented for ext4 as an example and for testing. Implementing RENAME_NOREPLACE for other local (disk or ram based) filesystems is trivial: just don't fail with -EOPNOTSUPP, the rest is done by the VFS. Network filesystems need special treatment to avoid creation races. I'm dropping the "rename + link-to-source" idea because it would only be used for a rare corner case of overlayfs, and which can be worked around in other ways. Please consider for -next (3.14). Git tree is here: git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/vfs.git cross-rename Thanks, Miklos --- Changes since the previous post: o Jan Kara reviewed the ext4 bits, thanks! o Added RENAME_NOREPLACE o Merged i_op->rename2 into i_op->rename (which results in lots of noise due to each filesystem's rename needing to be touched) --- Miklos Szeredi (11): vfs: add d_is_dir() vfs: rename: move d_move() up vfs: rename: use common code for dir and non-dir vfs: add renameat2 syscall vfs: add RENAME_NOREPLACE flag security: add flags to rename hooks vfs: add cross-rename ext4: rename: create ext4_renament structure for local vars ext4: rename: move EMLINK check up ext4: rename: split out helper functions ext4: add cross rename support --- Documentation/filesystems/Locking | 2 +- Documentation/filesystems/vfs.txt | 4 +- arch/x86/syscalls/syscall_64.tbl | 1 + .../lustre/lustre/include/linux/lustre_compat25.h | 4 +- drivers/staging/lustre/lustre/llite/namei.c | 7 +- drivers/staging/lustre/lustre/lvfs/lvfs_linux.c | 2 +- fs/9p/v9fs.h | 3 +- fs/9p/vfs_inode.c | 7 +- fs/affs/affs.h | 3 +- fs/affs/namei.c | 6 +- fs/afs/dir.c | 9 +- fs/bad_inode.c | 3 +- fs/bfs/dir.c | 6 +- fs/btrfs/inode.c | 6 +- fs/cachefiles/namei.c | 4 +- fs/ceph/dir.c | 6 +- fs/cifs/cifsfs.h | 2 +- fs/cifs/inode.c | 6 +- fs/coda/dir.c | 11 +- fs/dcache.c | 46 ++- fs/debugfs/inode.c | 2 +- fs/ecryptfs/inode.c | 8 +- fs/exofs/namei.c | 6 +- fs/ext2/namei.c | 8 +- fs/ext3/namei.c | 8 +- fs/ext4/namei.c | 379 ++++++++++++++------- fs/f2fs/namei.c | 6 +- fs/fat/namei_msdos.c | 6 +- fs/fat/namei_vfat.c | 6 +- fs/fuse/dir.c | 9 +- fs/gfs2/inode.c | 6 +- fs/hfs/dir.c | 6 +- fs/hfsplus/dir.c | 6 +- fs/hostfs/hostfs_kern.c | 8 +- fs/hpfs/namei.c | 6 +- fs/jffs2/dir.c | 8 +- fs/jfs/namei.c | 5 +- fs/libfs.c | 6 +- fs/logfs/dir.c | 6 +- fs/minix/namei.c | 8 +- fs/namei.c | 316 +++++++++-------- fs/ncpfs/dir.c | 8 +- fs/nfs/dir.c | 6 +- fs/nfs/internal.h | 3 +- fs/nfsd/vfs.c | 2 +- fs/nilfs2/namei.c | 6 +- fs/ocfs2/namei.c | 6 +- fs/omfs/dir.c | 6 +- fs/reiserfs/namei.c | 6 +- fs/sysv/namei.c | 8 +- fs/ubifs/dir.c | 6 +- fs/udf/namei.c | 6 +- fs/ufs/namei.c | 6 +- fs/xfs/xfs_iops.c | 6 +- include/linux/dcache.h | 8 +- include/linux/fs.h | 6 +- include/linux/security.h | 12 +- include/uapi/linux/fs.h | 3 + kernel/cgroup.c | 8 +- mm/shmem.c | 5 +- security/security.c | 22 +- 61 files changed, 732 insertions(+), 364 deletions(-) ^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH 01/11] vfs: add d_is_dir() 2013-11-20 13:01 [PATCH 00/11] cross rename v2 Miklos Szeredi @ 2013-11-20 13:01 ` Miklos Szeredi 2013-11-20 13:01 ` [PATCH 02/11] vfs: rename: move d_move() up Miklos Szeredi ` (9 subsequent siblings) 10 siblings, 0 replies; 22+ messages in thread From: Miklos Szeredi @ 2013-11-20 13:01 UTC (permalink / raw) To: viro Cc: torvalds, linux-fsdevel, linux-kernel, hch, akpm, dhowells, zab, jack, luto, mszeredi From: Miklos Szeredi <mszeredi@suse.cz> Add d_is_dir(dentry) helper which is analogous to S_ISDIR(). To avoid confusion, rename d_is_directory() to d_can_lookup(). Signed-off-by: Miklos Szeredi <mszeredi@suse.cz> --- fs/namei.c | 23 +++++++++++------------ include/linux/dcache.h | 7 ++++++- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/fs/namei.c b/fs/namei.c index e029a4cbff7d..0f2a7f8febee 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -1794,7 +1794,7 @@ static int link_path_walk(const char *name, struct nameidata *nd) if (err) return err; } - if (!d_is_directory(nd->path.dentry)) { + if (!d_can_lookup(nd->path.dentry)) { err = -ENOTDIR; break; } @@ -1815,7 +1815,7 @@ static int path_init(int dfd, const char *name, unsigned int flags, struct dentry *root = nd->root.dentry; struct inode *inode = root->d_inode; if (*name) { - if (!d_is_directory(root)) + if (!d_can_lookup(root)) return -ENOTDIR; retval = inode_permission(inode, MAY_EXEC); if (retval) @@ -1871,7 +1871,7 @@ static int path_init(int dfd, const char *name, unsigned int flags, dentry = f.file->f_path.dentry; if (*name) { - if (!d_is_directory(dentry)) { + if (!d_can_lookup(dentry)) { fdput(f); return -ENOTDIR; } @@ -1953,7 +1953,7 @@ static int path_lookupat(int dfd, const char *name, err = complete_walk(nd); if (!err && nd->flags & LOOKUP_DIRECTORY) { - if (!d_is_directory(nd->path.dentry)) { + if (!d_can_lookup(nd->path.dentry)) { path_put(&nd->path); err = -ENOTDIR; } @@ -2412,11 +2412,11 @@ static int may_delete(struct inode *dir, struct dentry *victim, bool isdir) IS_IMMUTABLE(inode) || IS_SWAPFILE(inode)) return -EPERM; if (isdir) { - if (!d_is_directory(victim) && !d_is_autodir(victim)) + if (!d_is_dir(victim)) return -ENOTDIR; if (IS_ROOT(victim)) return -EBUSY; - } else if (d_is_directory(victim) || d_is_autodir(victim)) + } else if (d_is_dir(victim)) return -EISDIR; if (IS_DEADDIR(dir)) return -ENOENT; @@ -3013,11 +3013,10 @@ finish_open: } audit_inode(name, nd->path.dentry, 0); error = -EISDIR; - if ((open_flag & O_CREAT) && - (d_is_directory(nd->path.dentry) || d_is_autodir(nd->path.dentry))) + if ((open_flag & O_CREAT) && d_is_dir(nd->path.dentry)) goto out; error = -ENOTDIR; - if ((nd->flags & LOOKUP_DIRECTORY) && !d_is_directory(nd->path.dentry)) + if ((nd->flags & LOOKUP_DIRECTORY) && !d_can_lookup(nd->path.dentry)) goto out; if (!S_ISREG(nd->inode->i_mode)) will_truncate = false; @@ -3741,7 +3740,7 @@ exit1: slashes: if (d_is_negative(dentry)) error = -ENOENT; - else if (d_is_directory(dentry) || d_is_autodir(dentry)) + else if (d_is_dir(dentry)) error = -EISDIR; else error = -ENOTDIR; @@ -4117,7 +4116,7 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry, struct inode **delegated_inode) { int error; - int is_dir = d_is_directory(old_dentry) || d_is_autodir(old_dentry); + int is_dir = d_is_dir(old_dentry); const unsigned char *old_name; if (old_dentry->d_inode == new_dentry->d_inode) @@ -4210,7 +4209,7 @@ retry_deleg: if (d_is_negative(old_dentry)) goto exit4; /* unless the source is a directory trailing slashes give -ENOTDIR */ - if (!d_is_directory(old_dentry) && !d_is_autodir(old_dentry)) { + if (!d_is_dir(old_dentry)) { error = -ENOTDIR; if (oldnd.last.name[oldnd.last.len]) goto exit4; diff --git a/include/linux/dcache.h b/include/linux/dcache.h index 57e87e749a48..901616910e0a 100644 --- a/include/linux/dcache.h +++ b/include/linux/dcache.h @@ -427,7 +427,7 @@ static inline unsigned __d_entry_type(const struct dentry *dentry) return dentry->d_flags & DCACHE_ENTRY_TYPE; } -static inline bool d_is_directory(const struct dentry *dentry) +static inline bool d_can_lookup(const struct dentry *dentry) { return __d_entry_type(dentry) == DCACHE_DIRECTORY_TYPE; } @@ -437,6 +437,11 @@ static inline bool d_is_autodir(const struct dentry *dentry) return __d_entry_type(dentry) == DCACHE_AUTODIR_TYPE; } +static inline bool d_is_dir(const struct dentry *dentry) +{ + return d_can_lookup(dentry) || d_is_autodir(dentry); +} + static inline bool d_is_symlink(const struct dentry *dentry) { return __d_entry_type(dentry) == DCACHE_SYMLINK_TYPE; -- 1.8.1.4 ^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH 02/11] vfs: rename: move d_move() up 2013-11-20 13:01 [PATCH 00/11] cross rename v2 Miklos Szeredi 2013-11-20 13:01 ` [PATCH 01/11] vfs: add d_is_dir() Miklos Szeredi @ 2013-11-20 13:01 ` Miklos Szeredi 2013-11-20 13:01 ` [PATCH 03/11] vfs: rename: use common code for dir and non-dir Miklos Szeredi ` (8 subsequent siblings) 10 siblings, 0 replies; 22+ messages in thread From: Miklos Szeredi @ 2013-11-20 13:01 UTC (permalink / raw) To: viro Cc: torvalds, linux-fsdevel, linux-kernel, hch, akpm, dhowells, zab, jack, luto, mszeredi From: Miklos Szeredi <mszeredi@suse.cz> Move the d_move() in vfs_rename_dir() up, similarly to how it's done in vfs_rename_other(). The next patch will consolidate these two functions and this is the only structural difference between them. I'm not sure if doing the d_move() after the dput is even valid. But there may be a logical explanation for that. But moving the d_move() before the dput() (and the mutex_unlock()) should definitely not hurt. Signed-off-by: Miklos Szeredi <mszeredi@suse.cz> --- fs/namei.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/fs/namei.c b/fs/namei.c index 0f2a7f8febee..c0446b916af0 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -4039,13 +4039,12 @@ static int vfs_rename_dir(struct inode *old_dir, struct dentry *old_dentry, target->i_flags |= S_DEAD; dont_mount(new_dentry); } + if (!(old_dir->i_sb->s_type->fs_flags & FS_RENAME_DOES_D_MOVE)) + d_move(old_dentry, new_dentry); out: if (target) mutex_unlock(&target->i_mutex); dput(new_dentry); - if (!error) - if (!(old_dir->i_sb->s_type->fs_flags & FS_RENAME_DOES_D_MOVE)) - d_move(old_dentry,new_dentry); return error; } -- 1.8.1.4 ^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH 03/11] vfs: rename: use common code for dir and non-dir 2013-11-20 13:01 [PATCH 00/11] cross rename v2 Miklos Szeredi 2013-11-20 13:01 ` [PATCH 01/11] vfs: add d_is_dir() Miklos Szeredi 2013-11-20 13:01 ` [PATCH 02/11] vfs: rename: move d_move() up Miklos Szeredi @ 2013-11-20 13:01 ` Miklos Szeredi 2013-11-20 13:01 ` [PATCH 04/11] vfs: add renameat2 syscall Miklos Szeredi ` (7 subsequent siblings) 10 siblings, 0 replies; 22+ messages in thread From: Miklos Szeredi @ 2013-11-20 13:01 UTC (permalink / raw) To: viro Cc: torvalds, linux-fsdevel, linux-kernel, hch, akpm, dhowells, zab, jack, luto, mszeredi From: Miklos Szeredi <mszeredi@suse.cz> There's actually very little difference between vfs_rename_dir() and vfs_rename_other() so move both inline into vfs_rename() which still stays reasonably readable. Signed-off-by: Miklos Szeredi <mszeredi@suse.cz> --- fs/namei.c | 187 +++++++++++++++++++++++++------------------------------------ 1 file changed, 75 insertions(+), 112 deletions(-) diff --git a/fs/namei.c b/fs/namei.c index c0446b916af0..7048339ad1b7 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -3967,7 +3967,27 @@ SYSCALL_DEFINE2(link, const char __user *, oldname, const char __user *, newname return sys_linkat(AT_FDCWD, oldname, AT_FDCWD, newname, 0); } -/* +/** + * vfs_rename - rename a filesystem object + * @old_dir: parent of source + * @old_dentry: source + * @new_dir: parent of destination + * @new_dentry: destination + * @delegated_inode: returns an inode needing a delegation break + * + * The caller must hold multiple mutexes--see lock_rename()). + * + * If vfs_rename discovers a delegation in need of breaking at either + * the source or destination, it will return -EWOULDBLOCK and return a + * reference to the inode in delegated_inode. The caller should then + * break the delegation and retry. Because breaking a delegation may + * take a long time, the caller should drop all locks before doing + * so. + * + * Alternatively, a caller may pass NULL for delegated_inode. This may + * be appropriate for callers that expect the underlying filesystem not + * to be NFS exported. + * * The worst of all namespace operations - renaming directory. "Perverted" * doesn't even start to describe it. Somebody in UCB had a heck of a trip... * Problems: @@ -3995,19 +4015,39 @@ SYSCALL_DEFINE2(link, const char __user *, oldname, const char __user *, newname * ->i_mutex on parents, which works but leads to some truly excessive * locking]. */ -static int vfs_rename_dir(struct inode *old_dir, struct dentry *old_dentry, - struct inode *new_dir, struct dentry *new_dentry) +int vfs_rename(struct inode *old_dir, struct dentry *old_dentry, + struct inode *new_dir, struct dentry *new_dentry, + struct inode **delegated_inode) { - int error = 0; + int error; + bool is_dir = d_is_dir(old_dentry); + const unsigned char *old_name; + struct inode *source = old_dentry->d_inode; struct inode *target = new_dentry->d_inode; - unsigned max_links = new_dir->i_sb->s_max_links; + + if (source == target) + return 0; + + error = may_delete(old_dir, old_dentry, is_dir); + if (error) + return error; + + if (!target) + error = may_create(new_dir, new_dentry); + else + error = may_delete(new_dir, new_dentry, is_dir); + if (error) + return error; + + if (!old_dir->i_op->rename) + return -EPERM; /* * If we are going to change the parent - check write permissions, * we'll need to flip '..'. */ - if (new_dir != old_dir) { - error = inode_permission(old_dentry->d_inode, MAY_WRITE); + if (is_dir && new_dir != old_dir) { + error = inode_permission(source, MAY_WRITE); if (error) return error; } @@ -4016,134 +4056,57 @@ static int vfs_rename_dir(struct inode *old_dir, struct dentry *old_dentry, if (error) return error; + old_name = fsnotify_oldname_init(old_dentry->d_name.name); dget(new_dentry); - if (target) + if (!is_dir) + lock_two_nondirectories(source, target); + else if (target) mutex_lock(&target->i_mutex); error = -EBUSY; if (d_mountpoint(old_dentry) || d_mountpoint(new_dentry)) goto out; - error = -EMLINK; - if (max_links && !target && new_dir != old_dir && - new_dir->i_nlink >= max_links) - goto out; - - if (target) - shrink_dcache_parent(new_dentry); - error = old_dir->i_op->rename(old_dir, old_dentry, new_dir, new_dentry); - if (error) - goto out; - - if (target) { - target->i_flags |= S_DEAD; - dont_mount(new_dentry); - } - if (!(old_dir->i_sb->s_type->fs_flags & FS_RENAME_DOES_D_MOVE)) - d_move(old_dentry, new_dentry); -out: - if (target) - mutex_unlock(&target->i_mutex); - dput(new_dentry); - return error; -} - -static int vfs_rename_other(struct inode *old_dir, struct dentry *old_dentry, - struct inode *new_dir, struct dentry *new_dentry, - struct inode **delegated_inode) -{ - struct inode *target = new_dentry->d_inode; - struct inode *source = old_dentry->d_inode; - int error; - - error = security_inode_rename(old_dir, old_dentry, new_dir, new_dentry); - if (error) - return error; - - dget(new_dentry); - lock_two_nondirectories(source, target); + if (is_dir) { + unsigned max_links = new_dir->i_sb->s_max_links; - error = -EBUSY; - if (d_mountpoint(old_dentry)||d_mountpoint(new_dentry)) - goto out; + error = -EMLINK; + if (max_links && !target && new_dir != old_dir && + new_dir->i_nlink >= max_links) + goto out; - error = try_break_deleg(source, delegated_inode); - if (error) - goto out; - if (target) { - error = try_break_deleg(target, delegated_inode); + if (target) + shrink_dcache_parent(new_dentry); + } else { + error = try_break_deleg(source, delegated_inode); if (error) goto out; + if (target) { + error = try_break_deleg(target, delegated_inode); + if (error) + goto out; + } } error = old_dir->i_op->rename(old_dir, old_dentry, new_dir, new_dentry); if (error) goto out; - if (target) + if (target) { + if (is_dir) + target->i_flags |= S_DEAD; dont_mount(new_dentry); + } if (!(old_dir->i_sb->s_type->fs_flags & FS_RENAME_DOES_D_MOVE)) d_move(old_dentry, new_dentry); out: - unlock_two_nondirectories(source, target); + if (!is_dir) + unlock_two_nondirectories(source, target); + else if (target) + mutex_unlock(&target->i_mutex); dput(new_dentry); - return error; -} - -/** - * vfs_rename - rename a filesystem object - * @old_dir: parent of source - * @old_dentry: source - * @new_dir: parent of destination - * @new_dentry: destination - * @delegated_inode: returns an inode needing a delegation break - * - * The caller must hold multiple mutexes--see lock_rename()). - * - * If vfs_rename discovers a delegation in need of breaking at either - * the source or destination, it will return -EWOULDBLOCK and return a - * reference to the inode in delegated_inode. The caller should then - * break the delegation and retry. Because breaking a delegation may - * take a long time, the caller should drop all locks before doing - * so. - * - * Alternatively, a caller may pass NULL for delegated_inode. This may - * be appropriate for callers that expect the underlying filesystem not - * to be NFS exported. - */ -int vfs_rename(struct inode *old_dir, struct dentry *old_dentry, - struct inode *new_dir, struct dentry *new_dentry, - struct inode **delegated_inode) -{ - int error; - int is_dir = d_is_dir(old_dentry); - const unsigned char *old_name; - - if (old_dentry->d_inode == new_dentry->d_inode) - return 0; - - error = may_delete(old_dir, old_dentry, is_dir); - if (error) - return error; - - if (!new_dentry->d_inode) - error = may_create(new_dir, new_dentry); - else - error = may_delete(new_dir, new_dentry, is_dir); - if (error) - return error; - - if (!old_dir->i_op->rename) - return -EPERM; - - old_name = fsnotify_oldname_init(old_dentry->d_name.name); - - if (is_dir) - error = vfs_rename_dir(old_dir,old_dentry,new_dir,new_dentry); - else - error = vfs_rename_other(old_dir,old_dentry,new_dir,new_dentry,delegated_inode); if (!error) fsnotify_move(old_dir, new_dir, old_name, is_dir, - new_dentry->d_inode, old_dentry); + target, old_dentry); fsnotify_oldname_free(old_name); return error; -- 1.8.1.4 ^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH 04/11] vfs: add renameat2 syscall 2013-11-20 13:01 [PATCH 00/11] cross rename v2 Miklos Szeredi ` (2 preceding siblings ...) 2013-11-20 13:01 ` [PATCH 03/11] vfs: rename: use common code for dir and non-dir Miklos Szeredi @ 2013-11-20 13:01 ` Miklos Szeredi 2013-11-20 16:34 ` Andy Lutomirski 2013-11-21 4:10 ` Stephen Rothwell 2013-11-20 13:01 ` [PATCH 05/11] vfs: add RENAME_NOREPLACE flag Miklos Szeredi ` (6 subsequent siblings) 10 siblings, 2 replies; 22+ messages in thread From: Miklos Szeredi @ 2013-11-20 13:01 UTC (permalink / raw) To: viro Cc: torvalds, linux-fsdevel, linux-kernel, hch, akpm, dhowells, zab, jack, luto, mszeredi From: Miklos Szeredi <mszeredi@suse.cz> Add new renameat2 syscall, which is the same as renameat with an added flags argument. Pass flags to vfs_rename() and to i_op->rename() as well. All filesystems check flags and return -EOPNOTSUPP for unsupported flags. Signed-off-by: Miklos Szeredi <mszeredi@suse.cz> --- Documentation/filesystems/Locking | 2 +- Documentation/filesystems/vfs.txt | 4 +++- arch/x86/syscalls/syscall_64.tbl | 1 + .../lustre/lustre/include/linux/lustre_compat25.h | 4 ++-- drivers/staging/lustre/lustre/llite/namei.c | 7 +++++- drivers/staging/lustre/lustre/lvfs/lvfs_linux.c | 2 +- fs/9p/v9fs.h | 3 ++- fs/9p/vfs_inode.c | 7 +++++- fs/affs/affs.h | 3 ++- fs/affs/namei.c | 6 ++++- fs/afs/dir.c | 9 ++++++-- fs/bad_inode.c | 3 ++- fs/bfs/dir.c | 6 ++++- fs/btrfs/inode.c | 6 ++++- fs/cachefiles/namei.c | 2 +- fs/ceph/dir.c | 6 ++++- fs/cifs/cifsfs.h | 2 +- fs/cifs/inode.c | 6 ++++- fs/coda/dir.c | 11 ++++++--- fs/debugfs/inode.c | 2 +- fs/ecryptfs/inode.c | 8 +++++-- fs/exofs/namei.c | 6 ++++- fs/ext2/namei.c | 8 +++++-- fs/ext3/namei.c | 8 +++++-- fs/ext4/namei.c | 6 ++++- fs/f2fs/namei.c | 6 ++++- fs/fat/namei_msdos.c | 6 ++++- fs/fat/namei_vfat.c | 6 ++++- fs/fuse/dir.c | 9 ++++++-- fs/gfs2/inode.c | 6 ++++- fs/hfs/dir.c | 6 ++++- fs/hfsplus/dir.c | 6 ++++- fs/hostfs/hostfs_kern.c | 8 +++++-- fs/hpfs/namei.c | 6 ++++- fs/jffs2/dir.c | 8 +++++-- fs/jfs/namei.c | 5 ++++- fs/libfs.c | 6 ++++- fs/logfs/dir.c | 6 ++++- fs/minix/namei.c | 8 +++++-- fs/namei.c | 26 ++++++++++++++++------ fs/ncpfs/dir.c | 8 +++++-- fs/nfs/dir.c | 6 ++++- fs/nfs/internal.h | 3 ++- fs/nfsd/vfs.c | 2 +- fs/nilfs2/namei.c | 6 ++++- fs/ocfs2/namei.c | 6 ++++- fs/omfs/dir.c | 6 ++++- fs/reiserfs/namei.c | 6 ++++- fs/sysv/namei.c | 8 +++++-- fs/ubifs/dir.c | 6 ++++- fs/udf/namei.c | 6 ++++- fs/ufs/namei.c | 6 ++++- fs/xfs/xfs_iops.c | 6 ++++- include/linux/fs.h | 6 ++--- kernel/cgroup.c | 8 +++++-- mm/shmem.c | 5 ++++- 56 files changed, 263 insertions(+), 77 deletions(-) diff --git a/Documentation/filesystems/Locking b/Documentation/filesystems/Locking index fe7afe225381..70e800c3d54b 100644 --- a/Documentation/filesystems/Locking +++ b/Documentation/filesystems/Locking @@ -46,7 +46,7 @@ prototypes: int (*rmdir) (struct inode *,struct dentry *); int (*mknod) (struct inode *,struct dentry *,umode_t,dev_t); int (*rename) (struct inode *, struct dentry *, - struct inode *, struct dentry *); + struct inode *, struct dentry *, unsigned int); int (*readlink) (struct dentry *, char __user *,int); void * (*follow_link) (struct dentry *, struct nameidata *); void (*put_link) (struct dentry *, struct nameidata *, void *); diff --git a/Documentation/filesystems/vfs.txt b/Documentation/filesystems/vfs.txt index deb48b5fd883..96ac6ab6357c 100644 --- a/Documentation/filesystems/vfs.txt +++ b/Documentation/filesystems/vfs.txt @@ -346,7 +346,7 @@ struct inode_operations { int (*rmdir) (struct inode *,struct dentry *); int (*mknod) (struct inode *,struct dentry *,umode_t,dev_t); int (*rename) (struct inode *, struct dentry *, - struct inode *, struct dentry *); + struct inode *, struct dentry *, unsigned int); int (*readlink) (struct dentry *, char __user *,int); void * (*follow_link) (struct dentry *, struct nameidata *); void (*put_link) (struct dentry *, struct nameidata *, void *); @@ -413,6 +413,8 @@ otherwise noted. rename: called by the rename(2) system call to rename the object to have the parent and name given by the second inode and dentry. + If a flag (passed as the fifth argument) is not supported by + the filesystem, the rename method should return -EOPNOTSUPP. readlink: called by the readlink(2) system call. Only required if you want to support reading symbolic links diff --git a/arch/x86/syscalls/syscall_64.tbl b/arch/x86/syscalls/syscall_64.tbl index 38ae65dfd14f..fafd73440655 100644 --- a/arch/x86/syscalls/syscall_64.tbl +++ b/arch/x86/syscalls/syscall_64.tbl @@ -320,6 +320,7 @@ 311 64 process_vm_writev sys_process_vm_writev 312 common kcmp sys_kcmp 313 common finit_module sys_finit_module +314 common renameat2 sys_renameat2 # # x32-specific system call numbers start at 512 to avoid cache impact diff --git a/drivers/staging/lustre/lustre/include/linux/lustre_compat25.h b/drivers/staging/lustre/lustre/include/linux/lustre_compat25.h index eefdb8d061b1..81cc7a0134bb 100644 --- a/drivers/staging/lustre/lustre/include/linux/lustre_compat25.h +++ b/drivers/staging/lustre/lustre/include/linux/lustre_compat25.h @@ -105,8 +105,8 @@ static inline void ll_set_fs_pwd(struct fs_struct *fs, struct vfsmount *mnt, #define ll_vfs_unlink(inode,entry,mnt) vfs_unlink(inode,entry) #define ll_vfs_mknod(dir,entry,mnt,mode,dev) vfs_mknod(dir,entry,mode,dev) #define ll_security_inode_unlink(dir,entry,mnt) security_inode_unlink(dir,entry) -#define ll_vfs_rename(old,old_dir,mnt,new,new_dir,mnt1,delegated_inode) \ - vfs_rename(old,old_dir,new,new_dir,delegated_inode) +#define ll_vfs_rename(old, old_dir, mnt, new, new_dir, mnt1) \ + vfs_rename(old, old_dir, new, new_dir, NULL, 0) #define cfs_bio_io_error(a,b) bio_io_error((a)) #define cfs_bio_endio(a,b,c) bio_endio((a),(c)) diff --git a/drivers/staging/lustre/lustre/llite/namei.c b/drivers/staging/lustre/lustre/llite/namei.c index 90bbdae824ac..cd24e469cc9a 100644 --- a/drivers/staging/lustre/lustre/llite/namei.c +++ b/drivers/staging/lustre/lustre/llite/namei.c @@ -1215,9 +1215,14 @@ static int ll_link(struct dentry *old_dentry, struct inode *dir, } static int ll_rename(struct inode *old_dir, struct dentry *old_dentry, - struct inode *new_dir, struct dentry *new_dentry) + struct inode *new_dir, struct dentry *new_dentry, + unsigned int flags) { int err; + + if (flags) + return -EOPNOTSUPP; + err = ll_rename_generic(old_dir, NULL, old_dentry, &old_dentry->d_name, new_dir, NULL, new_dentry, diff --git a/drivers/staging/lustre/lustre/lvfs/lvfs_linux.c b/drivers/staging/lustre/lustre/lvfs/lvfs_linux.c index 09474e7553dd..1ef06fea793b 100644 --- a/drivers/staging/lustre/lustre/lvfs/lvfs_linux.c +++ b/drivers/staging/lustre/lustre/lvfs/lvfs_linux.c @@ -224,7 +224,7 @@ int lustre_rename(struct dentry *dir, struct vfsmount *mnt, GOTO(put_old, err = PTR_ERR(dchild_new)); err = ll_vfs_rename(dir->d_inode, dchild_old, mnt, - dir->d_inode, dchild_new, mnt, NULL); + dir->d_inode, dchild_new, mnt); dput(dchild_new); put_old: diff --git a/fs/9p/v9fs.h b/fs/9p/v9fs.h index a8e127c89627..5d347d10cf55 100644 --- a/fs/9p/v9fs.h +++ b/fs/9p/v9fs.h @@ -148,7 +148,8 @@ extern struct dentry *v9fs_vfs_lookup(struct inode *dir, struct dentry *dentry, extern int v9fs_vfs_unlink(struct inode *i, struct dentry *d); extern int v9fs_vfs_rmdir(struct inode *i, struct dentry *d); extern int v9fs_vfs_rename(struct inode *old_dir, struct dentry *old_dentry, - struct inode *new_dir, struct dentry *new_dentry); + struct inode *new_dir, struct dentry *new_dentry, + unsigned int flags); extern void v9fs_vfs_put_link(struct dentry *dentry, struct nameidata *nd, void *p); extern struct inode *v9fs_inode_from_fid(struct v9fs_session_info *v9ses, diff --git a/fs/9p/vfs_inode.c b/fs/9p/vfs_inode.c index 4e65aa903345..6a75edabc645 100644 --- a/fs/9p/vfs_inode.c +++ b/fs/9p/vfs_inode.c @@ -945,12 +945,14 @@ int v9fs_vfs_rmdir(struct inode *i, struct dentry *d) * @old_dentry: old dentry * @new_dir: new dir inode * @new_dentry: new dentry + * @flags: rename flags * */ int v9fs_vfs_rename(struct inode *old_dir, struct dentry *old_dentry, - struct inode *new_dir, struct dentry *new_dentry) + struct inode *new_dir, struct dentry *new_dentry, + unsigned int flags) { int retval; struct inode *old_inode; @@ -961,6 +963,9 @@ v9fs_vfs_rename(struct inode *old_dir, struct dentry *old_dentry, struct p9_fid *newdirfid; struct p9_wstat wstat; + if (flags) + return -EOPNOTSUPP; + p9_debug(P9_DEBUG_VFS, "\n"); retval = 0; old_inode = old_dentry->d_inode; diff --git a/fs/affs/affs.h b/fs/affs/affs.h index 3952121f2f28..badf97e81250 100644 --- a/fs/affs/affs.h +++ b/fs/affs/affs.h @@ -163,7 +163,8 @@ extern int affs_link(struct dentry *olddentry, struct inode *dir, extern int affs_symlink(struct inode *dir, struct dentry *dentry, const char *symname); extern int affs_rename(struct inode *old_dir, struct dentry *old_dentry, - struct inode *new_dir, struct dentry *new_dentry); + struct inode *new_dir, struct dentry *new_dentry, + unsigned int flags); /* inode.c */ diff --git a/fs/affs/namei.c b/fs/affs/namei.c index c36cbb4537a2..4f958db92333 100644 --- a/fs/affs/namei.c +++ b/fs/affs/namei.c @@ -401,12 +401,16 @@ affs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *dentry) int affs_rename(struct inode *old_dir, struct dentry *old_dentry, - struct inode *new_dir, struct dentry *new_dentry) + struct inode *new_dir, struct dentry *new_dentry, + unsigned int flags) { struct super_block *sb = old_dir->i_sb; struct buffer_head *bh = NULL; int retval; + if (flags) + return -EOPNOTSUPP; + pr_debug("AFFS: rename(old=%u,\"%*s\" to new=%u,\"%*s\")\n", (u32)old_dir->i_ino, (int)old_dentry->d_name.len, old_dentry->d_name.name, (u32)new_dir->i_ino, (int)new_dentry->d_name.len, new_dentry->d_name.name); diff --git a/fs/afs/dir.c b/fs/afs/dir.c index 529300327f45..ccdd551a0766 100644 --- a/fs/afs/dir.c +++ b/fs/afs/dir.c @@ -38,7 +38,8 @@ static int afs_link(struct dentry *from, struct inode *dir, 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, - struct inode *new_dir, struct dentry *new_dentry); + struct inode *new_dir, struct dentry *new_dentry, + unsigned int flags); const struct file_operations afs_dir_file_operations = { .open = afs_dir_open, @@ -1088,12 +1089,16 @@ error: * rename a file in an AFS filesystem and/or move it between directories */ static int afs_rename(struct inode *old_dir, struct dentry *old_dentry, - struct inode *new_dir, struct dentry *new_dentry) + struct inode *new_dir, struct dentry *new_dentry, + unsigned int flags) { struct afs_vnode *orig_dvnode, *new_dvnode, *vnode; struct key *key; int ret; + if (flags) + return -EOPNOTSUPP; + vnode = AFS_FS_I(old_dentry->d_inode); orig_dvnode = AFS_FS_I(old_dir); new_dvnode = AFS_FS_I(new_dir); diff --git a/fs/bad_inode.c b/fs/bad_inode.c index 7c93953030fb..02673d57ceda 100644 --- a/fs/bad_inode.c +++ b/fs/bad_inode.c @@ -219,7 +219,8 @@ static int bad_inode_mknod (struct inode *dir, struct dentry *dentry, } static int bad_inode_rename (struct inode *old_dir, struct dentry *old_dentry, - struct inode *new_dir, struct dentry *new_dentry) + struct inode *new_dir, struct dentry *new_dentry, + unsigned int flags) { return -EIO; } diff --git a/fs/bfs/dir.c b/fs/bfs/dir.c index a399e6d9dc74..41529673fd42 100644 --- a/fs/bfs/dir.c +++ b/fs/bfs/dir.c @@ -209,7 +209,8 @@ out_brelse: } static int bfs_rename(struct inode *old_dir, struct dentry *old_dentry, - struct inode *new_dir, struct dentry *new_dentry) + struct inode *new_dir, struct dentry *new_dentry, + unsigned int flags) { struct inode *old_inode, *new_inode; struct buffer_head *old_bh, *new_bh; @@ -217,6 +218,9 @@ static int bfs_rename(struct inode *old_dir, struct dentry *old_dentry, struct bfs_sb_info *info; int error = -ENOENT; + if (flags) + return -EOPNOTSUPP; + old_bh = new_bh = NULL; old_inode = old_dentry->d_inode; if (S_ISDIR(old_inode->i_mode)) diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index da8d2f696ac5..2bfaebdd7e37 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -7982,7 +7982,8 @@ static int btrfs_getattr(struct vfsmount *mnt, } static int btrfs_rename(struct inode *old_dir, struct dentry *old_dentry, - struct inode *new_dir, struct dentry *new_dentry) + struct inode *new_dir, struct dentry *new_dentry, + unsigned int flags) { struct btrfs_trans_handle *trans; struct btrfs_root *root = BTRFS_I(old_dir)->root; @@ -7995,6 +7996,9 @@ static int btrfs_rename(struct inode *old_dir, struct dentry *old_dentry, int ret; u64 old_ino = btrfs_ino(old_inode); + if (flags) + return -EOPNOTSUPP; + if (btrfs_ino(new_dir) == BTRFS_EMPTY_SUBVOL_DIR_OBJECTID) return -EPERM; diff --git a/fs/cachefiles/namei.c b/fs/cachefiles/namei.c index ca65f39dc8dc..31088a969351 100644 --- a/fs/cachefiles/namei.c +++ b/fs/cachefiles/namei.c @@ -396,7 +396,7 @@ try_again: cachefiles_io_error(cache, "Rename security error %d", ret); } else { ret = vfs_rename(dir->d_inode, rep, - cache->graveyard->d_inode, grave, NULL); + cache->graveyard->d_inode, grave, NULL, 0); if (ret != 0 && ret != -ENOMEM) cachefiles_io_error(cache, "Rename failed with error %d", ret); diff --git a/fs/ceph/dir.c b/fs/ceph/dir.c index 868b61d56cac..fed45dba67ba 100644 --- a/fs/ceph/dir.c +++ b/fs/ceph/dir.c @@ -870,13 +870,17 @@ out: } static int ceph_rename(struct inode *old_dir, struct dentry *old_dentry, - struct inode *new_dir, struct dentry *new_dentry) + struct inode *new_dir, struct dentry *new_dentry, + unsigned int flags) { struct ceph_fs_client *fsc = ceph_sb_to_client(old_dir->i_sb); struct ceph_mds_client *mdsc = fsc->mdsc; struct ceph_mds_request *req; int err; + if (flags) + return -EOPNOTSUPP; + if (ceph_snap(old_dir) != ceph_snap(new_dir)) return -EXDEV; if (ceph_snap(old_dir) != CEPH_NOSNAP || diff --git a/fs/cifs/cifsfs.h b/fs/cifs/cifsfs.h index 26a754f49ba1..b9e81af57e46 100644 --- a/fs/cifs/cifsfs.h +++ b/fs/cifs/cifsfs.h @@ -61,7 +61,7 @@ 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 *); extern int cifs_rename(struct inode *, struct dentry *, struct inode *, - struct dentry *); + struct dentry *, unsigned int); extern int cifs_revalidate_file_attr(struct file *filp); extern int cifs_revalidate_dentry_attr(struct dentry *); extern int cifs_revalidate_file(struct file *filp); diff --git a/fs/cifs/inode.c b/fs/cifs/inode.c index 36f9ebb93ceb..6b061f082315 100644 --- a/fs/cifs/inode.c +++ b/fs/cifs/inode.c @@ -1593,7 +1593,8 @@ do_rename_exit: int cifs_rename(struct inode *source_dir, struct dentry *source_dentry, - struct inode *target_dir, struct dentry *target_dentry) + struct inode *target_dir, struct dentry *target_dentry, + unsigned int flags) { char *from_name = NULL; char *to_name = NULL; @@ -1605,6 +1606,9 @@ cifs_rename(struct inode *source_dir, struct dentry *source_dentry, unsigned int xid; int rc, tmprc; + if (flags) + return -EOPNOTSUPP; + cifs_sb = CIFS_SB(source_dir->i_sb); tlink = cifs_sb_tlink(cifs_sb); if (IS_ERR(tlink)) diff --git a/fs/coda/dir.c b/fs/coda/dir.c index 5efbb5ee0adc..f94838543f2d 100644 --- a/fs/coda/dir.c +++ b/fs/coda/dir.c @@ -39,8 +39,9 @@ static int coda_symlink(struct inode *dir_inode, struct dentry *entry, const char *symname); static int coda_mkdir(struct inode *dir_inode, struct dentry *entry, umode_t mode); static int coda_rmdir(struct inode *dir_inode, struct dentry *entry); -static int coda_rename(struct inode *old_inode, struct dentry *old_dentry, - struct inode *new_inode, struct dentry *new_dentry); +static int coda_rename(struct inode *old_inode, struct dentry *old_dentry, + struct inode *new_inode, struct dentry *new_dentry, + unsigned int flags); /* dir file-ops */ static int coda_readdir(struct file *file, struct dir_context *ctx); @@ -347,7 +348,8 @@ static int coda_rmdir(struct inode *dir, struct dentry *de) /* rename */ static int coda_rename(struct inode *old_dir, struct dentry *old_dentry, - struct inode *new_dir, struct dentry *new_dentry) + struct inode *new_dir, struct dentry *new_dentry, + unsigned int flags) { const char *old_name = old_dentry->d_name.name; const char *new_name = new_dentry->d_name.name; @@ -355,6 +357,9 @@ static int coda_rename(struct inode *old_dir, struct dentry *old_dentry, int new_length = new_dentry->d_name.len; int error; + if (flags) + return -EOPNOTSUPP; + error = venus_rename(old_dir->i_sb, coda_i2f(old_dir), coda_i2f(new_dir), old_length, new_length, (const char *) old_name, (const char *)new_name); diff --git a/fs/debugfs/inode.c b/fs/debugfs/inode.c index 9c0444cccbe1..70fc09be0ffd 100644 --- a/fs/debugfs/inode.c +++ b/fs/debugfs/inode.c @@ -618,7 +618,7 @@ struct dentry *debugfs_rename(struct dentry *old_dir, struct dentry *old_dentry, old_name = fsnotify_oldname_init(old_dentry->d_name.name); error = simple_rename(old_dir->d_inode, old_dentry, new_dir->d_inode, - dentry); + dentry, 0); if (error) { fsnotify_oldname_free(old_name); goto exit; diff --git a/fs/ecryptfs/inode.c b/fs/ecryptfs/inode.c index c36c44824471..0ce5a6b2ea9d 100644 --- a/fs/ecryptfs/inode.c +++ b/fs/ecryptfs/inode.c @@ -611,7 +611,8 @@ out: static int ecryptfs_rename(struct inode *old_dir, struct dentry *old_dentry, - struct inode *new_dir, struct dentry *new_dentry) + struct inode *new_dir, struct dentry *new_dentry, + unsigned int flags) { int rc; struct dentry *lower_old_dentry; @@ -621,6 +622,9 @@ ecryptfs_rename(struct inode *old_dir, struct dentry *old_dentry, struct dentry *trap = NULL; struct inode *target_inode; + if (flags) + return -EOPNOTSUPP; + lower_old_dentry = ecryptfs_dentry_to_lower(old_dentry); lower_new_dentry = ecryptfs_dentry_to_lower(new_dentry); dget(lower_old_dentry); @@ -641,7 +645,7 @@ ecryptfs_rename(struct inode *old_dir, struct dentry *old_dentry, } rc = vfs_rename(lower_old_dir_dentry->d_inode, lower_old_dentry, lower_new_dir_dentry->d_inode, lower_new_dentry, - NULL); + NULL, 0); if (rc) goto out_lock; if (target_inode) diff --git a/fs/exofs/namei.c b/fs/exofs/namei.c index 4731fd991efe..54db7e2192ca 100644 --- a/fs/exofs/namei.c +++ b/fs/exofs/namei.c @@ -228,7 +228,8 @@ static int exofs_rmdir(struct inode *dir, struct dentry *dentry) } static int exofs_rename(struct inode *old_dir, struct dentry *old_dentry, - struct inode *new_dir, struct dentry *new_dentry) + struct inode *new_dir, struct dentry *new_dentry, + unsigned int flags) { struct inode *old_inode = old_dentry->d_inode; struct inode *new_inode = new_dentry->d_inode; @@ -238,6 +239,9 @@ static int exofs_rename(struct inode *old_dir, struct dentry *old_dentry, struct exofs_dir_entry *old_de; int err = -ENOENT; + if (flags) + return -EOPNOTSUPP; + old_de = exofs_find_entry(old_dir, old_dentry, &old_page); if (!old_de) goto out; diff --git a/fs/ext2/namei.c b/fs/ext2/namei.c index 256dd5f4c1c4..d7da1dad9149 100644 --- a/fs/ext2/namei.c +++ b/fs/ext2/namei.c @@ -320,8 +320,9 @@ static int ext2_rmdir (struct inode * dir, struct dentry *dentry) return err; } -static int ext2_rename (struct inode * old_dir, struct dentry * old_dentry, - struct inode * new_dir, struct dentry * new_dentry ) +static int ext2_rename(struct inode *old_dir, struct dentry *old_dentry, + struct inode *new_dir, struct dentry *new_dentry, + unsigned int flags) { struct inode * old_inode = old_dentry->d_inode; struct inode * new_inode = new_dentry->d_inode; @@ -331,6 +332,9 @@ static int ext2_rename (struct inode * old_dir, struct dentry * old_dentry, struct ext2_dir_entry_2 * old_de; int err = -ENOENT; + if (flags) + return -EOPNOTSUPP; + dquot_initialize(old_dir); dquot_initialize(new_dir); diff --git a/fs/ext3/namei.c b/fs/ext3/namei.c index f8cde46de9cd..08ca534c2de4 100644 --- a/fs/ext3/namei.c +++ b/fs/ext3/namei.c @@ -2375,8 +2375,9 @@ retry: * Anybody can rename anything with this: the permission checks are left to the * higher-level routines. */ -static int ext3_rename (struct inode * old_dir, struct dentry *old_dentry, - struct inode * new_dir,struct dentry *new_dentry) +static int ext3_rename(struct inode *old_dir, struct dentry *old_dentry, + struct inode *new_dir, struct dentry *new_dentry, + unsigned int flags) { handle_t *handle; struct inode * old_inode, * new_inode; @@ -2384,6 +2385,9 @@ static int ext3_rename (struct inode * old_dir, struct dentry *old_dentry, struct ext3_dir_entry_2 * old_de, * new_de; int retval, flush_file = 0; + if (flags) + return -EOPNOTSUPP; + dquot_initialize(old_dir); dquot_initialize(new_dir); diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c index 5a0408d7b114..08c40f4e7eed 100644 --- a/fs/ext4/namei.c +++ b/fs/ext4/namei.c @@ -3010,7 +3010,8 @@ static struct buffer_head *ext4_get_first_dir_block(handle_t *handle, * This comes from rename(const char *oldpath, const char *newpath) */ static int ext4_rename(struct inode *old_dir, struct dentry *old_dentry, - struct inode *new_dir, struct dentry *new_dentry) + struct inode *new_dir, struct dentry *new_dentry, + unsigned int flags) { handle_t *handle = NULL; struct inode *old_inode, *new_inode; @@ -3020,6 +3021,9 @@ static int ext4_rename(struct inode *old_dir, struct dentry *old_dentry, int inlined = 0, new_inlined = 0; struct ext4_dir_entry_2 *parent_de; + if (flags) + return -EOPNOTSUPP; + dquot_initialize(old_dir); dquot_initialize(new_dir); diff --git a/fs/f2fs/namei.c b/fs/f2fs/namei.c index 575adac17f8b..13913085927c 100644 --- a/fs/f2fs/namei.c +++ b/fs/f2fs/namei.c @@ -374,7 +374,8 @@ out: } static int f2fs_rename(struct inode *old_dir, struct dentry *old_dentry, - struct inode *new_dir, struct dentry *new_dentry) + struct inode *new_dir, struct dentry *new_dentry, + unsigned int flags) { struct super_block *sb = old_dir->i_sb; struct f2fs_sb_info *sbi = F2FS_SB(sb); @@ -387,6 +388,9 @@ static int f2fs_rename(struct inode *old_dir, struct dentry *old_dentry, struct f2fs_dir_entry *new_entry; int err = -ENOENT; + if (flags) + return -EOPNOTSUPP; + f2fs_balance_fs(sbi); old_entry = f2fs_find_entry(old_dir, &old_dentry->d_name, &old_page); diff --git a/fs/fat/namei_msdos.c b/fs/fat/namei_msdos.c index a783b0e1272a..73db3d039ba7 100644 --- a/fs/fat/namei_msdos.c +++ b/fs/fat/namei_msdos.c @@ -598,12 +598,16 @@ error_inode: /***** Rename, a wrapper for rename_same_dir & rename_diff_dir */ static int msdos_rename(struct inode *old_dir, struct dentry *old_dentry, - struct inode *new_dir, struct dentry *new_dentry) + struct inode *new_dir, struct dentry *new_dentry, + unsigned int flags) { struct super_block *sb = old_dir->i_sb; unsigned char old_msdos_name[MSDOS_NAME], new_msdos_name[MSDOS_NAME]; int err, is_hid; + if (flags) + return -EOPNOTSUPP; + mutex_lock(&MSDOS_SB(sb)->s_lock); err = msdos_format_name(old_dentry->d_name.name, diff --git a/fs/fat/namei_vfat.c b/fs/fat/namei_vfat.c index 6df8d3d885e5..ef0fd0703c33 100644 --- a/fs/fat/namei_vfat.c +++ b/fs/fat/namei_vfat.c @@ -903,7 +903,8 @@ out: } static int vfat_rename(struct inode *old_dir, struct dentry *old_dentry, - struct inode *new_dir, struct dentry *new_dentry) + struct inode *new_dir, struct dentry *new_dentry, + unsigned int flags) { struct buffer_head *dotdot_bh; struct msdos_dir_entry *dotdot_de; @@ -914,6 +915,9 @@ static int vfat_rename(struct inode *old_dir, struct dentry *old_dentry, int err, is_dir, update_dotdot, corrupt = 0; struct super_block *sb = old_dir->i_sb; + if (flags) + return -EOPNOTSUPP; + old_sinfo.bh = sinfo.bh = dotdot_bh = NULL; old_inode = old_dentry->d_inode; new_inode = new_dentry->d_inode; diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index c3eb2c46c8f1..ffe2f46aa53e 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -734,13 +734,18 @@ static int fuse_rmdir(struct inode *dir, struct dentry *entry) } static int fuse_rename(struct inode *olddir, struct dentry *oldent, - struct inode *newdir, struct dentry *newent) + struct inode *newdir, struct dentry *newent, + unsigned int flags) { int err; struct fuse_rename_in inarg; struct fuse_conn *fc = get_fuse_conn(olddir); - struct fuse_req *req = fuse_get_req_nopages(fc); + struct fuse_req *req; + if (flags) + return -EOPNOTSUPP; + + req = fuse_get_req_nopages(fc); if (IS_ERR(req)) return PTR_ERR(req); diff --git a/fs/gfs2/inode.c b/fs/gfs2/inode.c index 1615df16cf4e..aa76f914e6b6 100644 --- a/fs/gfs2/inode.c +++ b/fs/gfs2/inode.c @@ -1240,7 +1240,8 @@ static int gfs2_ok_to_move(struct gfs2_inode *this, struct gfs2_inode *to) */ static int gfs2_rename(struct inode *odir, struct dentry *odentry, - struct inode *ndir, struct dentry *ndentry) + struct inode *ndir, struct dentry *ndentry, + unsigned int flags) { struct gfs2_inode *odip = GFS2_I(odir); struct gfs2_inode *ndip = GFS2_I(ndir); @@ -1255,6 +1256,9 @@ static int gfs2_rename(struct inode *odir, struct dentry *odentry, unsigned int x; int error; + if (flags) + return -EOPNOTSUPP; + if (ndentry->d_inode) { nip = GFS2_I(ndentry->d_inode); if (ip == nip) diff --git a/fs/hfs/dir.c b/fs/hfs/dir.c index 145566851e7a..540e8bcd331e 100644 --- a/fs/hfs/dir.c +++ b/fs/hfs/dir.c @@ -280,10 +280,14 @@ static int hfs_remove(struct inode *dir, struct dentry *dentry) * XXX: how do you handle must_be dir? */ static int hfs_rename(struct inode *old_dir, struct dentry *old_dentry, - struct inode *new_dir, struct dentry *new_dentry) + struct inode *new_dir, struct dentry *new_dentry, + unsigned int flags) { int res; + if (flags) + return -EOPNOTSUPP; + /* Unlink destination if it already exists */ if (new_dentry->d_inode) { res = hfs_remove(new_dir, new_dentry); diff --git a/fs/hfsplus/dir.c b/fs/hfsplus/dir.c index 4a4fea002673..802dce49af35 100644 --- a/fs/hfsplus/dir.c +++ b/fs/hfsplus/dir.c @@ -494,10 +494,14 @@ static int hfsplus_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode) } static int hfsplus_rename(struct inode *old_dir, struct dentry *old_dentry, - struct inode *new_dir, struct dentry *new_dentry) + struct inode *new_dir, struct dentry *new_dentry, + unsigned int flags) { int res; + if (flags) + return -EOPNOTSUPP; + /* Unlink destination if it already exists */ if (new_dentry->d_inode) { if (S_ISDIR(new_dentry->d_inode->i_mode)) diff --git a/fs/hostfs/hostfs_kern.c b/fs/hostfs/hostfs_kern.c index 25437280a207..957a0bd34b51 100644 --- a/fs/hostfs/hostfs_kern.c +++ b/fs/hostfs/hostfs_kern.c @@ -747,12 +747,16 @@ static int hostfs_mknod(struct inode *dir, struct dentry *dentry, umode_t mode, return err; } -int hostfs_rename(struct inode *from_ino, struct dentry *from, - struct inode *to_ino, struct dentry *to) +static int hostfs_rename(struct inode *from_ino, struct dentry *from, + struct inode *to_ino, struct dentry *to, + unsigned int flags) { char *from_name, *to_name; int err; + if (flags) + return -EOPNOTSUPP; + if ((from_name = dentry_name(from)) == NULL) return -ENOMEM; if ((to_name = dentry_name(to)) == NULL) { diff --git a/fs/hpfs/namei.c b/fs/hpfs/namei.c index 1b39afdd86fd..1bec8e2b4c74 100644 --- a/fs/hpfs/namei.c +++ b/fs/hpfs/namei.c @@ -516,7 +516,8 @@ const struct address_space_operations hpfs_symlink_aops = { }; static int hpfs_rename(struct inode *old_dir, struct dentry *old_dentry, - struct inode *new_dir, struct dentry *new_dentry) + struct inode *new_dir, struct dentry *new_dentry, + unsigned int flags) { const unsigned char *old_name = old_dentry->d_name.name; unsigned old_len = old_dentry->d_name.len; @@ -533,6 +534,9 @@ static int hpfs_rename(struct inode *old_dir, struct dentry *old_dentry, struct fnode *fnode; int err; + if (flags) + return -EOPNOTSUPP; + if ((err = hpfs_chk_name(new_name, &new_len))) return err; err = 0; hpfs_adjust_length(old_name, &old_len); diff --git a/fs/jffs2/dir.c b/fs/jffs2/dir.c index e3aac222472e..a23da79c1ed0 100644 --- a/fs/jffs2/dir.c +++ b/fs/jffs2/dir.c @@ -35,7 +35,7 @@ static int jffs2_mkdir (struct inode *,struct dentry *,umode_t); static int jffs2_rmdir (struct inode *,struct dentry *); static int jffs2_mknod (struct inode *,struct dentry *,umode_t,dev_t); static int jffs2_rename (struct inode *, struct dentry *, - struct inode *, struct dentry *); + struct inode *, struct dentry *, unsigned int); const struct file_operations jffs2_dir_operations = { @@ -756,7 +756,8 @@ static int jffs2_mknod (struct inode *dir_i, struct dentry *dentry, umode_t mode } static int jffs2_rename (struct inode *old_dir_i, struct dentry *old_dentry, - struct inode *new_dir_i, struct dentry *new_dentry) + struct inode *new_dir_i, struct dentry *new_dentry, + unsigned int flags) { int ret; struct jffs2_sb_info *c = JFFS2_SB_INFO(old_dir_i->i_sb); @@ -764,6 +765,9 @@ static int jffs2_rename (struct inode *old_dir_i, struct dentry *old_dentry, uint8_t type; uint32_t now; + if (flags) + return -EOPNOTSUPP; + /* The VFS will check for us and prevent trying to rename a * file over a directory and vice versa, but if it's a directory, * the VFS can't check whether the victim is empty. The filesystem diff --git a/fs/jfs/namei.c b/fs/jfs/namei.c index aa8a3370631b..06841bcaad79 100644 --- a/fs/jfs/namei.c +++ b/fs/jfs/namei.c @@ -1062,7 +1062,8 @@ static int jfs_symlink(struct inode *dip, struct dentry *dentry, * FUNCTION: rename a file or directory */ static int jfs_rename(struct inode *old_dir, struct dentry *old_dentry, - struct inode *new_dir, struct dentry *new_dentry) + struct inode *new_dir, struct dentry *new_dentry, + unsigned int flags) { struct btstack btstack; ino_t ino; @@ -1081,6 +1082,8 @@ static int jfs_rename(struct inode *old_dir, struct dentry *old_dentry, s64 new_size = 0; int commit_flag; + if (flags) + return -EOPNOTSUPP; jfs_info("jfs_rename: %s %s", old_dentry->d_name.name, new_dentry->d_name.name); diff --git a/fs/libfs.c b/fs/libfs.c index 5de06947ba5e..76016c6db205 100644 --- a/fs/libfs.c +++ b/fs/libfs.c @@ -323,11 +323,15 @@ int simple_rmdir(struct inode *dir, struct dentry *dentry) EXPORT_SYMBOL(simple_rmdir); int simple_rename(struct inode *old_dir, struct dentry *old_dentry, - struct inode *new_dir, struct dentry *new_dentry) + struct inode *new_dir, struct dentry *new_dentry, + unsigned int flags) { struct inode *inode = old_dentry->d_inode; int they_are_dirs = S_ISDIR(old_dentry->d_inode->i_mode); + if (flags) + return -EOPNOTSUPP; + if (!simple_empty(new_dentry)) return -ENOTEMPTY; diff --git a/fs/logfs/dir.c b/fs/logfs/dir.c index 6bdc347008f5..43b32e7004ee 100644 --- a/fs/logfs/dir.c +++ b/fs/logfs/dir.c @@ -717,8 +717,12 @@ out: } static int logfs_rename(struct inode *old_dir, struct dentry *old_dentry, - struct inode *new_dir, struct dentry *new_dentry) + struct inode *new_dir, struct dentry *new_dentry, + unsigned int flags) { + if (flags) + return -EOPNOTSUPP; + if (new_dentry->d_inode) return logfs_rename_target(old_dir, old_dentry, new_dir, new_dentry); diff --git a/fs/minix/namei.c b/fs/minix/namei.c index cd950e2331b6..19eb25202c0e 100644 --- a/fs/minix/namei.c +++ b/fs/minix/namei.c @@ -184,8 +184,9 @@ static int minix_rmdir(struct inode * dir, struct dentry *dentry) return err; } -static int minix_rename(struct inode * old_dir, struct dentry *old_dentry, - struct inode * new_dir, struct dentry *new_dentry) +static int minix_rename(struct inode *old_dir, struct dentry *old_dentry, + struct inode *new_dir, struct dentry *new_dentry, + unsigned int flags) { struct inode * old_inode = old_dentry->d_inode; struct inode * new_inode = new_dentry->d_inode; @@ -195,6 +196,9 @@ static int minix_rename(struct inode * old_dir, struct dentry *old_dentry, struct minix_dir_entry * old_de; int err = -ENOENT; + if (flags) + return -EOPNOTSUPP; + old_de = minix_find_entry(old_dentry, &old_page); if (!old_de) goto out; diff --git a/fs/namei.c b/fs/namei.c index 7048339ad1b7..9efeb16f685c 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -3974,6 +3974,7 @@ SYSCALL_DEFINE2(link, const char __user *, oldname, const char __user *, newname * @new_dir: parent of destination * @new_dentry: destination * @delegated_inode: returns an inode needing a delegation break + * @flags: rename flags * * The caller must hold multiple mutexes--see lock_rename()). * @@ -4017,7 +4018,7 @@ SYSCALL_DEFINE2(link, const char __user *, oldname, const char __user *, newname */ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry, - struct inode **delegated_inode) + struct inode **delegated_inode, unsigned int flags) { int error; bool is_dir = d_is_dir(old_dentry); @@ -4087,7 +4088,8 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry, goto out; } } - error = old_dir->i_op->rename(old_dir, old_dentry, new_dir, new_dentry); + error = old_dir->i_op->rename(old_dir, old_dentry, new_dir, new_dentry, + flags); if (error) goto out; @@ -4112,8 +4114,8 @@ out: return error; } -SYSCALL_DEFINE4(renameat, int, olddfd, const char __user *, oldname, - int, newdfd, const char __user *, newname) +SYSCALL_DEFINE5(renameat2, int, olddfd, const char __user *, oldname, + int, newdfd, const char __user *, newname, unsigned int, flags) { struct dentry *old_dir, *new_dir; struct dentry *old_dentry, *new_dentry; @@ -4125,6 +4127,10 @@ SYSCALL_DEFINE4(renameat, int, olddfd, const char __user *, oldname, unsigned int lookup_flags = 0; bool should_retry = false; int error; + + if (flags) + return -EOPNOTSUPP; + retry: from = user_path_parent(olddfd, oldname, &oldnd, lookup_flags); if (IS_ERR(from)) { @@ -4196,8 +4202,8 @@ retry_deleg: if (error) goto exit5; error = vfs_rename(old_dir->d_inode, old_dentry, - new_dir->d_inode, new_dentry, - &delegated_inode); + new_dir->d_inode, new_dentry, + &delegated_inode, flags); exit5: dput(new_dentry); exit4: @@ -4227,9 +4233,15 @@ exit: return error; } +SYSCALL_DEFINE4(renameat, int, olddfd, const char __user *, oldname, + int, newdfd, const char __user *, newname) +{ + return sys_renameat2(olddfd, oldname, newdfd, newname, 0); +} + SYSCALL_DEFINE2(rename, const char __user *, oldname, const char __user *, newname) { - return sys_renameat(AT_FDCWD, oldname, AT_FDCWD, newname); + return sys_renameat2(AT_FDCWD, oldname, AT_FDCWD, newname, 0); } int vfs_readlink(struct dentry *dentry, char __user *buffer, int buflen, const char *link) diff --git a/fs/ncpfs/dir.c b/fs/ncpfs/dir.c index c320ac52353e..d4bf4f42d156 100644 --- a/fs/ncpfs/dir.c +++ b/fs/ncpfs/dir.c @@ -36,7 +36,7 @@ static int ncp_unlink(struct inode *, struct dentry *); static int ncp_mkdir(struct inode *, struct dentry *, umode_t); static int ncp_rmdir(struct inode *, struct dentry *); static int ncp_rename(struct inode *, struct dentry *, - struct inode *, struct dentry *); + struct inode *, struct dentry *, unsigned int); static int ncp_mknod(struct inode * dir, struct dentry *dentry, umode_t mode, dev_t rdev); #if defined(CONFIG_NCPFS_EXTRAS) || defined(CONFIG_NCPFS_NFS_NS) @@ -1113,13 +1113,17 @@ static int ncp_unlink(struct inode *dir, struct dentry *dentry) } static int ncp_rename(struct inode *old_dir, struct dentry *old_dentry, - struct inode *new_dir, struct dentry *new_dentry) + struct inode *new_dir, struct dentry *new_dentry, + unsigned int flags) { struct ncp_server *server = NCP_SERVER(old_dir); int error; int old_len, new_len; __u8 __old_name[NCP_MAXPATHLEN + 1], __new_name[NCP_MAXPATHLEN + 1]; + if (flags) + return -EOPNOTSUPP; + DPRINTK("ncp_rename: %pd2 to %pd2\n", old_dentry, new_dentry); ncp_age_dentry(server, old_dentry); diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c index 812154aff981..09881925d0e6 100644 --- a/fs/nfs/dir.c +++ b/fs/nfs/dir.c @@ -1892,13 +1892,17 @@ EXPORT_SYMBOL_GPL(nfs_link); * the rename. */ int nfs_rename(struct inode *old_dir, struct dentry *old_dentry, - struct inode *new_dir, struct dentry *new_dentry) + struct inode *new_dir, struct dentry *new_dentry, + unsigned int flags) { struct inode *old_inode = old_dentry->d_inode; struct inode *new_inode = new_dentry->d_inode; struct dentry *dentry = NULL, *rehash = NULL; int error = -EBUSY; + if (flags) + return -EOPNOTSUPP; + dfprintk(VFS, "NFS: rename(%pd2 -> %pd2, ct=%d)\n", old_dentry, new_dentry, d_count(new_dentry)); diff --git a/fs/nfs/internal.h b/fs/nfs/internal.h index bca6a3e3c49c..1ec08f9db3d5 100644 --- a/fs/nfs/internal.h +++ b/fs/nfs/internal.h @@ -288,7 +288,8 @@ 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_mknod(struct inode *, struct dentry *, umode_t, dev_t); -int nfs_rename(struct inode *, struct dentry *, struct inode *, struct dentry *); +int nfs_rename(struct inode *, struct dentry *, struct inode *, struct dentry *, + unsigned int); /* file.c */ int nfs_file_fsync_commit(struct file *, loff_t, loff_t, int); diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c index 94b5f5d2bfed..416103593c9b 100644 --- a/fs/nfsd/vfs.c +++ b/fs/nfsd/vfs.c @@ -1841,7 +1841,7 @@ nfsd_rename(struct svc_rqst *rqstp, struct svc_fh *ffhp, char *fname, int flen, if (host_err) goto out_dput_new; } - host_err = vfs_rename(fdir, odentry, tdir, ndentry, NULL); + host_err = vfs_rename(fdir, odentry, tdir, ndentry, NULL, 0); if (!host_err) { host_err = commit_metadata(tfhp); if (!host_err) diff --git a/fs/nilfs2/namei.c b/fs/nilfs2/namei.c index 9de78f08989e..6f49b9bcefc4 100644 --- a/fs/nilfs2/namei.c +++ b/fs/nilfs2/namei.c @@ -347,7 +347,8 @@ static int nilfs_rmdir(struct inode *dir, struct dentry *dentry) } static int nilfs_rename(struct inode *old_dir, struct dentry *old_dentry, - struct inode *new_dir, struct dentry *new_dentry) + struct inode *new_dir, struct dentry *new_dentry, + unsigned int flags) { struct inode *old_inode = old_dentry->d_inode; struct inode *new_inode = new_dentry->d_inode; @@ -358,6 +359,9 @@ static int nilfs_rename(struct inode *old_dir, struct dentry *old_dentry, struct nilfs_transaction_info ti; int err; + if (flags) + return -EOPNOTSUPP; + err = nilfs_transaction_begin(old_dir->i_sb, &ti, 1); if (unlikely(err)) return err; diff --git a/fs/ocfs2/namei.c b/fs/ocfs2/namei.c index 4f791f6d27d0..d117184c1eea 100644 --- a/fs/ocfs2/namei.c +++ b/fs/ocfs2/namei.c @@ -1038,7 +1038,8 @@ static void ocfs2_double_unlock(struct inode *inode1, struct inode *inode2) static int ocfs2_rename(struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, - struct dentry *new_dentry) + struct dentry *new_dentry, + unsigned int flags) { int status = 0, rename_lock = 0, parents_locked = 0, target_exists = 0; int old_child_locked = 0, new_child_locked = 0, update_dot_dot = 0; @@ -1062,6 +1063,9 @@ static int ocfs2_rename(struct inode *old_dir, struct ocfs2_dir_lookup_result orphan_insert = { NULL, }; struct ocfs2_dir_lookup_result target_insert = { NULL, }; + if (flags) + return -EOPNOTSUPP; + /* At some point it might be nice to break this function up a * bit. */ diff --git a/fs/omfs/dir.c b/fs/omfs/dir.c index 1b8e9e8405b2..15e821af0c80 100644 --- a/fs/omfs/dir.c +++ b/fs/omfs/dir.c @@ -371,12 +371,16 @@ static bool omfs_fill_chain(struct inode *dir, struct dir_context *ctx, } static int omfs_rename(struct inode *old_dir, struct dentry *old_dentry, - struct inode *new_dir, struct dentry *new_dentry) + struct inode *new_dir, struct dentry *new_dentry, + unsigned int flags) { struct inode *new_inode = new_dentry->d_inode; struct inode *old_inode = old_dentry->d_inode; int err; + if (flags) + return -EOPNOTSUPP; + if (new_inode) { /* overwriting existing file/dir */ err = omfs_remove(new_dir, new_dentry); diff --git a/fs/reiserfs/namei.c b/fs/reiserfs/namei.c index dc5236f6de1b..e6d6a9cbb171 100644 --- a/fs/reiserfs/namei.c +++ b/fs/reiserfs/namei.c @@ -1202,7 +1202,8 @@ static void set_ino_in_dir_entry(struct reiserfs_dir_entry *de, * get_empty_nodes or its clones */ static int reiserfs_rename(struct inode *old_dir, struct dentry *old_dentry, - struct inode *new_dir, struct dentry *new_dentry) + struct inode *new_dir, struct dentry *new_dentry, + unsigned int flags) { int retval; INITIALIZE_PATH(old_entry_path); @@ -1217,6 +1218,9 @@ static int reiserfs_rename(struct inode *old_dir, struct dentry *old_dentry, unsigned long savelink = 1; struct timespec ctime; + if (flags) + return -EOPNOTSUPP; + /* three balancings: (1) old name removal, (2) new name insertion and (3) maybe "save" link insertion stat data updates: (1) old directory, diff --git a/fs/sysv/namei.c b/fs/sysv/namei.c index 731b2bbcaab3..b8811197c529 100644 --- a/fs/sysv/namei.c +++ b/fs/sysv/namei.c @@ -205,8 +205,9 @@ static int sysv_rmdir(struct inode * dir, struct dentry * dentry) * Anybody can rename anything with this: the permission checks are left to the * higher-level routines. */ -static int sysv_rename(struct inode * old_dir, struct dentry * old_dentry, - struct inode * new_dir, struct dentry * new_dentry) +static int sysv_rename(struct inode *old_dir, struct dentry *old_dentry, + struct inode *new_dir, struct dentry *new_dentry, + unsigned int flags) { struct inode * old_inode = old_dentry->d_inode; struct inode * new_inode = new_dentry->d_inode; @@ -216,6 +217,9 @@ static int sysv_rename(struct inode * old_dir, struct dentry * old_dentry, struct sysv_dir_entry * old_de; int err = -ENOENT; + if (flags) + return -EOPNOTSUPP; + old_de = sysv_find_entry(old_dentry, &old_page); if (!old_de) goto out; diff --git a/fs/ubifs/dir.c b/fs/ubifs/dir.c index ea41649e4ca5..49d815460bb9 100644 --- a/fs/ubifs/dir.c +++ b/fs/ubifs/dir.c @@ -950,7 +950,8 @@ static void unlock_3_inodes(struct inode *inode1, struct inode *inode2, } static int ubifs_rename(struct inode *old_dir, struct dentry *old_dentry, - struct inode *new_dir, struct dentry *new_dentry) + struct inode *new_dir, struct dentry *new_dentry, + unsigned int flags) { struct ubifs_info *c = old_dir->i_sb->s_fs_info; struct inode *old_inode = old_dentry->d_inode; @@ -968,6 +969,9 @@ static int ubifs_rename(struct inode *old_dir, struct dentry *old_dentry, struct timespec time; unsigned int uninitialized_var(saved_nlink); + if (flags) + return -EOPNOTSUPP; + /* * Budget request settings: deletion direntry, new direntry, removing * the old inode, and changing old and new parent directory inodes. diff --git a/fs/udf/namei.c b/fs/udf/namei.c index 5f6fc17d6bc5..fd33cc096fda 100644 --- a/fs/udf/namei.c +++ b/fs/udf/namei.c @@ -1079,7 +1079,8 @@ static int udf_link(struct dentry *old_dentry, struct inode *dir, * higher-level routines. */ static int udf_rename(struct inode *old_dir, struct dentry *old_dentry, - struct inode *new_dir, struct dentry *new_dentry) + struct inode *new_dir, struct dentry *new_dentry, + unsigned int flags) { struct inode *old_inode = old_dentry->d_inode; struct inode *new_inode = new_dentry->d_inode; @@ -1091,6 +1092,9 @@ static int udf_rename(struct inode *old_dir, struct dentry *old_dentry, struct kernel_lb_addr tloc; struct udf_inode_info *old_iinfo = UDF_I(old_inode); + if (flags) + return -EOPNOTSUPP; + ofi = udf_find_entry(old_dir, &old_dentry->d_name, &ofibh, &ocfi); if (ofi) { if (ofibh.sbh != ofibh.ebh) diff --git a/fs/ufs/namei.c b/fs/ufs/namei.c index 90d74b8f8eba..73ce50ee9e85 100644 --- a/fs/ufs/namei.c +++ b/fs/ufs/namei.c @@ -259,7 +259,8 @@ static int ufs_rmdir (struct inode * dir, struct dentry *dentry) } static int ufs_rename(struct inode *old_dir, struct dentry *old_dentry, - struct inode *new_dir, struct dentry *new_dentry) + struct inode *new_dir, struct dentry *new_dentry, + unsigned int flags) { struct inode *old_inode = old_dentry->d_inode; struct inode *new_inode = new_dentry->d_inode; @@ -269,6 +270,9 @@ static int ufs_rename(struct inode *old_dir, struct dentry *old_dentry, struct ufs_dir_entry *old_de; int err = -ENOENT; + if (flags) + return -EOPNOTSUPP; + old_de = ufs_find_entry(old_dir, &old_dentry->d_name, &old_page); if (!old_de) goto out; diff --git a/fs/xfs/xfs_iops.c b/fs/xfs/xfs_iops.c index 27e0e544e963..cdc141f8cc30 100644 --- a/fs/xfs/xfs_iops.c +++ b/fs/xfs/xfs_iops.c @@ -346,12 +346,16 @@ xfs_vn_rename( struct inode *odir, struct dentry *odentry, struct inode *ndir, - struct dentry *ndentry) + struct dentry *ndentry, + unsigned int flags) { struct inode *new_inode = ndentry->d_inode; struct xfs_name oname; struct xfs_name nname; + if (flags) + return -EOPNOTSUPP; + xfs_dentry_to_name(&oname, odentry, 0); xfs_dentry_to_name(&nname, ndentry, odentry->d_inode->i_mode); diff --git a/include/linux/fs.h b/include/linux/fs.h index bf5d574ebdf4..a62dbe15d2b1 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -1456,7 +1456,7 @@ 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_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 **); +extern int vfs_rename(struct inode *, struct dentry *, struct inode *, struct dentry *, struct inode **, unsigned int); /* * VFS dentry helper functions. @@ -1566,7 +1566,7 @@ struct inode_operations { int (*rmdir) (struct inode *,struct dentry *); int (*mknod) (struct inode *,struct dentry *,umode_t,dev_t); int (*rename) (struct inode *, struct dentry *, - struct inode *, struct dentry *); + struct inode *, struct dentry *, unsigned int); int (*setattr) (struct dentry *, struct iattr *); int (*getattr) (struct vfsmount *mnt, struct dentry *, struct kstat *); int (*setxattr) (struct dentry *, const char *,const void *,size_t,int); @@ -2612,7 +2612,7 @@ extern int simple_open(struct inode *inode, struct file *file); extern int simple_link(struct dentry *, struct inode *, struct dentry *); extern int simple_unlink(struct inode *, struct dentry *); extern int simple_rmdir(struct inode *, struct dentry *); -extern int simple_rename(struct inode *, struct dentry *, struct inode *, struct dentry *); +extern int simple_rename(struct inode *, struct dentry *, struct inode *, struct dentry *, unsigned int); extern int noop_fsync(struct file *, loff_t, loff_t, int); extern int simple_empty(struct dentry *); extern int simple_readpage(struct file *file, struct page *page); diff --git a/kernel/cgroup.c b/kernel/cgroup.c index e0839bcd48c8..3385a09d1b58 100644 --- a/kernel/cgroup.c +++ b/kernel/cgroup.c @@ -2494,12 +2494,16 @@ static int cgroup_file_release(struct inode *inode, struct file *file) * cgroup_rename - Only allow simple rename of directories in place. */ static int cgroup_rename(struct inode *old_dir, struct dentry *old_dentry, - struct inode *new_dir, struct dentry *new_dentry) + struct inode *new_dir, struct dentry *new_dentry, + unsigned int flags) { int ret; struct cgroup_name *name, *old_name; struct cgroup *cgrp; + if (flags) + return -EOPNOTSUPP; + /* * It's convinient to use parent dir's i_mutex to protected * cgrp->name. @@ -2526,7 +2530,7 @@ static int cgroup_rename(struct inode *old_dir, struct dentry *old_dentry, if (!name) return -ENOMEM; - ret = simple_rename(old_dir, old_dentry, new_dir, new_dentry); + ret = simple_rename(old_dir, old_dentry, new_dir, new_dentry, 0); if (ret) { kfree(name); return ret; diff --git a/mm/shmem.c b/mm/shmem.c index 8297623fcaed..d7c3e32945f9 100644 --- a/mm/shmem.c +++ b/mm/shmem.c @@ -2067,11 +2067,14 @@ static int shmem_rmdir(struct inode *dir, struct dentry *dentry) * it exists so that the VFS layer correctly free's it when it * gets overwritten. */ -static int shmem_rename(struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry) +static int shmem_rename(struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry, unsigned int flags) { struct inode *inode = old_dentry->d_inode; int they_are_dirs = S_ISDIR(inode->i_mode); + if (flags) + return -EOPNOTSUPP; + if (!simple_empty(new_dentry)) return -ENOTEMPTY; -- 1.8.1.4 ^ permalink raw reply related [flat|nested] 22+ messages in thread
* Re: [PATCH 04/11] vfs: add renameat2 syscall 2013-11-20 13:01 ` [PATCH 04/11] vfs: add renameat2 syscall Miklos Szeredi @ 2013-11-20 16:34 ` Andy Lutomirski 2013-11-21 4:10 ` Stephen Rothwell 1 sibling, 0 replies; 22+ messages in thread From: Andy Lutomirski @ 2013-11-20 16:34 UTC (permalink / raw) To: Miklos Szeredi Cc: Al Viro, Linus Torvalds, Linux FS Devel, linux-kernel@vger.kernel.org, Christoph Hellwig, Andrew Morton, David Howells, Zach Brown, Jan Kara, mszeredi On Wed, Nov 20, 2013 at 5:01 AM, Miklos Szeredi <miklos@szeredi.hu> wrote: > From: Miklos Szeredi <mszeredi@suse.cz> > > Add new renameat2 syscall, which is the same as renameat with an added > flags argument. FWIW, I like this interface. --Andy ^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH 04/11] vfs: add renameat2 syscall 2013-11-20 13:01 ` [PATCH 04/11] vfs: add renameat2 syscall Miklos Szeredi 2013-11-20 16:34 ` Andy Lutomirski @ 2013-11-21 4:10 ` Stephen Rothwell 2013-11-21 12:37 ` Miklos Szeredi 1 sibling, 1 reply; 22+ messages in thread From: Stephen Rothwell @ 2013-11-21 4:10 UTC (permalink / raw) To: Miklos Szeredi Cc: viro, torvalds, linux-fsdevel, linux-kernel, hch, akpm, dhowells, zab, jack, luto, mszeredi [-- Attachment #1: Type: text/plain, Size: 837 bytes --] Hi all, On Wed, 20 Nov 2013 14:01:45 +0100 Miklos Szeredi <miklos@szeredi.hu> wrote: > > From: Miklos Szeredi <mszeredi@suse.cz> > > Add new renameat2 syscall, which is the same as renameat with an added > flags argument. > > Pass flags to vfs_rename() and to i_op->rename() as well. > > All filesystems check flags and return -EOPNOTSUPP for unsupported flags. Can we please consider doing this slightly slower (and avoiding conflicts/missed new uses etc) by creating a new i_op callback and only implementing this when necessary? From past experience, I can just about guarantee that the patch as it is will cause conflicts (for me in linux-next if noone else) and a new use case will turn up before (or during) the next merge window. -- Cheers, Stephen Rothwell sfr@canb.auug.org.au [-- Attachment #2: Type: application/pgp-signature, Size: 836 bytes --] ^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH 04/11] vfs: add renameat2 syscall 2013-11-21 4:10 ` Stephen Rothwell @ 2013-11-21 12:37 ` Miklos Szeredi 0 siblings, 0 replies; 22+ messages in thread From: Miklos Szeredi @ 2013-11-21 12:37 UTC (permalink / raw) To: Stephen Rothwell Cc: Al Viro, Linus Torvalds, Linux-Fsdevel, Kernel Mailing List, Christoph Hellwig, Andrew Morton, David Howells, Zach Brown, Jan Kara, Andy Lutomirski, mszeredi@suse.cz On Thu, Nov 21, 2013 at 5:10 AM, Stephen Rothwell <sfr@canb.auug.org.au> wrote: > Hi all, > > On Wed, 20 Nov 2013 14:01:45 +0100 Miklos Szeredi <miklos@szeredi.hu> wrote: >> >> From: Miklos Szeredi <mszeredi@suse.cz> >> >> Add new renameat2 syscall, which is the same as renameat with an added >> flags argument. >> >> Pass flags to vfs_rename() and to i_op->rename() as well. >> >> All filesystems check flags and return -EOPNOTSUPP for unsupported flags. > > Can we please consider doing this slightly slower (and avoiding > conflicts/missed new uses etc) by creating a new i_op callback and only > implementing this when necessary? > > From past experience, I can just about guarantee that the patch as it is > will cause conflicts (for me in linux-next if noone else) and a new use > case will turn up before (or during) the next merge window. I understand, but it makes little sense to have two i_op's with almost exactly the same functionality, so at some point they are bound to be merged, resulting in the same pain. So Linus, how about applying the patches 1-4 at the end of this merge window? These are simple with no functional changes. And then the meat of the patchset can go into the next merge window without fear of conflicts. Thanks, Miklos ^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH 05/11] vfs: add RENAME_NOREPLACE flag 2013-11-20 13:01 [PATCH 00/11] cross rename v2 Miklos Szeredi ` (3 preceding siblings ...) 2013-11-20 13:01 ` [PATCH 04/11] vfs: add renameat2 syscall Miklos Szeredi @ 2013-11-20 13:01 ` Miklos Szeredi 2013-11-20 13:01 ` [PATCH 06/11] security: add flags to rename hooks Miklos Szeredi ` (5 subsequent siblings) 10 siblings, 0 replies; 22+ messages in thread From: Miklos Szeredi @ 2013-11-20 13:01 UTC (permalink / raw) To: viro Cc: torvalds, linux-fsdevel, linux-kernel, hch, akpm, dhowells, zab, jack, luto, mszeredi From: Miklos Szeredi <mszeredi@suse.cz> If this flag is specified and the target of the rename exists then the rename syscall fails with EEXIST. The VFS does the existence checking, so it is trivial to enable for most local filesystems. This patch only enables it in ext4. For network filesystems the VFS check is not enough as there may be a race between a remote create and the rename, so these filesystems need to handle this flag in their ->rename() implementations to ensure atomicity. Suggested-by: Andy Lutomirski <luto@amacapital.net> Signed-off-by: Miklos Szeredi <mszeredi@suse.cz> --- fs/ext4/namei.c | 2 +- fs/namei.c | 21 +++++++++++++-------- include/uapi/linux/fs.h | 2 ++ 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c index 08c40f4e7eed..e0129b6e74cf 100644 --- a/fs/ext4/namei.c +++ b/fs/ext4/namei.c @@ -3021,7 +3021,7 @@ static int ext4_rename(struct inode *old_dir, struct dentry *old_dentry, int inlined = 0, new_inlined = 0; struct ext4_dir_entry_2 *parent_de; - if (flags) + if (flags & ~RENAME_NOREPLACE) return -EOPNOTSUPP; dquot_initialize(old_dir); diff --git a/fs/namei.c b/fs/namei.c index 9efeb16f685c..042f83acd28e 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -4128,7 +4128,7 @@ SYSCALL_DEFINE5(renameat2, int, olddfd, const char __user *, oldname, bool should_retry = false; int error; - if (flags) + if (flags & ~RENAME_NOREPLACE) return -EOPNOTSUPP; retry: @@ -4154,6 +4154,8 @@ retry: goto exit2; new_dir = newnd.path.dentry; + if (flags & RENAME_NOREPLACE) + error = -EEXIST; if (newnd.last_type != LAST_NORM) goto exit2; @@ -4176,22 +4178,25 @@ retry_deleg: error = -ENOENT; if (d_is_negative(old_dentry)) goto exit4; + new_dentry = lookup_hash(&newnd); + error = PTR_ERR(new_dentry); + if (IS_ERR(new_dentry)) + goto exit4; + error = -EEXIST; + if ((flags & RENAME_NOREPLACE) && d_is_positive(new_dentry)) + goto exit5; /* unless the source is a directory trailing slashes give -ENOTDIR */ if (!d_is_dir(old_dentry)) { error = -ENOTDIR; if (oldnd.last.name[oldnd.last.len]) - goto exit4; + goto exit5; if (newnd.last.name[newnd.last.len]) - goto exit4; + goto exit5; } /* source should not be ancestor of target */ error = -EINVAL; if (old_dentry == trap) - goto exit4; - new_dentry = lookup_hash(&newnd); - error = PTR_ERR(new_dentry); - if (IS_ERR(new_dentry)) - goto exit4; + goto exit5; /* target should not be an ancestor of source */ error = -ENOTEMPTY; if (new_dentry == trap) diff --git a/include/uapi/linux/fs.h b/include/uapi/linux/fs.h index 6c28b61bb690..9250f4dd7d96 100644 --- a/include/uapi/linux/fs.h +++ b/include/uapi/linux/fs.h @@ -35,6 +35,8 @@ #define SEEK_HOLE 4 /* seek to the next hole */ #define SEEK_MAX SEEK_HOLE +#define RENAME_NOREPLACE (1 << 0) /* Don't overwrite target */ + struct fstrim_range { __u64 start; __u64 len; -- 1.8.1.4 ^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH 06/11] security: add flags to rename hooks 2013-11-20 13:01 [PATCH 00/11] cross rename v2 Miklos Szeredi ` (4 preceding siblings ...) 2013-11-20 13:01 ` [PATCH 05/11] vfs: add RENAME_NOREPLACE flag Miklos Szeredi @ 2013-11-20 13:01 ` Miklos Szeredi 2013-11-20 13:01 ` [PATCH 07/11] vfs: add cross-rename Miklos Szeredi ` (4 subsequent siblings) 10 siblings, 0 replies; 22+ messages in thread From: Miklos Szeredi @ 2013-11-20 13:01 UTC (permalink / raw) To: viro Cc: torvalds, linux-fsdevel, linux-kernel, hch, akpm, dhowells, zab, jack, luto, mszeredi From: Miklos Szeredi <mszeredi@suse.cz> Add flags to security_path_rename() and security_inode_rename() hooks. Signed-off-by: Miklos Szeredi <mszeredi@suse.cz> --- fs/cachefiles/namei.c | 2 +- fs/namei.c | 5 +++-- include/linux/security.h | 12 ++++++++---- security/security.c | 6 ++++-- 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/fs/cachefiles/namei.c b/fs/cachefiles/namei.c index 31088a969351..6494d9f673aa 100644 --- a/fs/cachefiles/namei.c +++ b/fs/cachefiles/namei.c @@ -391,7 +391,7 @@ try_again: path.dentry = dir; path_to_graveyard.mnt = cache->mnt; path_to_graveyard.dentry = cache->graveyard; - ret = security_path_rename(&path, rep, &path_to_graveyard, grave); + ret = security_path_rename(&path, rep, &path_to_graveyard, grave, 0); if (ret < 0) { cachefiles_io_error(cache, "Rename security error %d", ret); } else { diff --git a/fs/namei.c b/fs/namei.c index 042f83acd28e..5b41d4bfecd1 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -4053,7 +4053,8 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry, return error; } - error = security_inode_rename(old_dir, old_dentry, new_dir, new_dentry); + error = security_inode_rename(old_dir, old_dentry, new_dir, new_dentry, + flags); if (error) return error; @@ -4203,7 +4204,7 @@ retry_deleg: goto exit5; error = security_path_rename(&oldnd.path, old_dentry, - &newnd.path, new_dentry); + &newnd.path, new_dentry, flags); if (error) goto exit5; error = vfs_rename(old_dir->d_inode, old_dentry, diff --git a/include/linux/security.h b/include/linux/security.h index 9d37e2b9d3ec..0fcf9d110217 100644 --- a/include/linux/security.h +++ b/include/linux/security.h @@ -1782,7 +1782,8 @@ int security_inode_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode) int security_inode_rmdir(struct inode *dir, struct dentry *dentry); int security_inode_mknod(struct inode *dir, struct dentry *dentry, umode_t mode, dev_t dev); int security_inode_rename(struct inode *old_dir, struct dentry *old_dentry, - struct inode *new_dir, struct dentry *new_dentry); + struct inode *new_dir, struct dentry *new_dentry, + unsigned int flags); int security_inode_readlink(struct dentry *dentry); int security_inode_follow_link(struct dentry *dentry, struct nameidata *nd); int security_inode_permission(struct inode *inode, int mask); @@ -2150,7 +2151,8 @@ static inline int security_inode_mknod(struct inode *dir, static inline int security_inode_rename(struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, - struct dentry *new_dentry) + struct dentry *new_dentry, + unsigned int flags) { return 0; } @@ -2941,7 +2943,8 @@ int security_path_symlink(struct path *dir, struct dentry *dentry, int security_path_link(struct dentry *old_dentry, struct path *new_dir, struct dentry *new_dentry); int security_path_rename(struct path *old_dir, struct dentry *old_dentry, - struct path *new_dir, struct dentry *new_dentry); + struct path *new_dir, struct dentry *new_dentry, + unsigned int flags); int security_path_chmod(struct path *path, umode_t mode); int security_path_chown(struct path *path, kuid_t uid, kgid_t gid); int security_path_chroot(struct path *path); @@ -2989,7 +2992,8 @@ static inline int security_path_link(struct dentry *old_dentry, static inline int security_path_rename(struct path *old_dir, struct dentry *old_dentry, struct path *new_dir, - struct dentry *new_dentry) + struct dentry *new_dentry, + unsigned int flags) { return 0; } diff --git a/security/security.c b/security/security.c index 4dc31f4f2700..d00ae28dea76 100644 --- a/security/security.c +++ b/security/security.c @@ -433,7 +433,8 @@ int security_path_link(struct dentry *old_dentry, struct path *new_dir, } int security_path_rename(struct path *old_dir, struct dentry *old_dentry, - struct path *new_dir, struct dentry *new_dentry) + struct path *new_dir, struct dentry *new_dentry, + unsigned int flags) { if (unlikely(IS_PRIVATE(old_dentry->d_inode) || (new_dentry->d_inode && IS_PRIVATE(new_dentry->d_inode)))) @@ -524,7 +525,8 @@ int security_inode_mknod(struct inode *dir, struct dentry *dentry, umode_t mode, } int security_inode_rename(struct inode *old_dir, struct dentry *old_dentry, - struct inode *new_dir, struct dentry *new_dentry) + struct inode *new_dir, struct dentry *new_dentry, + unsigned int flags) { if (unlikely(IS_PRIVATE(old_dentry->d_inode) || (new_dentry->d_inode && IS_PRIVATE(new_dentry->d_inode)))) -- 1.8.1.4 ^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH 07/11] vfs: add cross-rename 2013-11-20 13:01 [PATCH 00/11] cross rename v2 Miklos Szeredi ` (5 preceding siblings ...) 2013-11-20 13:01 ` [PATCH 06/11] security: add flags to rename hooks Miklos Szeredi @ 2013-11-20 13:01 ` Miklos Szeredi 2013-11-20 16:39 ` Andy Lutomirski 2013-11-20 13:01 ` [PATCH 08/11] ext4: rename: create ext4_renament structure for local vars Miklos Szeredi ` (3 subsequent siblings) 10 siblings, 1 reply; 22+ messages in thread From: Miklos Szeredi @ 2013-11-20 13:01 UTC (permalink / raw) To: viro Cc: torvalds, linux-fsdevel, linux-kernel, hch, akpm, dhowells, zab, jack, luto, mszeredi From: Miklos Szeredi <mszeredi@suse.cz> If flags contain RENAME_EXCHANGE then exchange source and destination files. There's no restriction on the type of the files; e.g. a directory can be exchanged with a symlink. Signed-off-by: Miklos Szeredi <mszeredi@suse.cz> --- fs/dcache.c | 46 +++++++++++++++++---- fs/namei.c | 107 +++++++++++++++++++++++++++++++++--------------- include/linux/dcache.h | 1 + include/uapi/linux/fs.h | 1 + security/security.c | 16 ++++++++ 5 files changed, 131 insertions(+), 40 deletions(-) diff --git a/fs/dcache.c b/fs/dcache.c index 0a38ef8d7f00..ea2fca1a2ec9 100644 --- a/fs/dcache.c +++ b/fs/dcache.c @@ -2527,12 +2527,14 @@ static void switch_names(struct dentry *dentry, struct dentry *target) dentry->d_name.name = dentry->d_iname; } else { /* - * Both are internal. Just copy target to dentry + * Both are internal. */ - memcpy(dentry->d_iname, target->d_name.name, - target->d_name.len + 1); - dentry->d_name.len = target->d_name.len; - return; + unsigned int i; + BUILD_BUG_ON(!IS_ALIGNED(DNAME_INLINE_LEN, sizeof(long))); + for (i = 0; i < DNAME_INLINE_LEN / sizeof(long); i++) { + swap(((long *) &dentry->d_iname)[i], + ((long *) &target->d_iname)[i]); + } } } swap(dentry->d_name.len, target->d_name.len); @@ -2589,13 +2591,15 @@ static void dentry_unlock_parents_for_move(struct dentry *dentry, * __d_move - move a dentry * @dentry: entry to move * @target: new dentry + * @exchange: exchange the two dentries * * Update the dcache to reflect the move of a file name. Negative * dcache entries should not be moved in this way. Caller must hold * rename_lock, the i_mutex of the source and target directories, * and the sb->s_vfs_rename_mutex if they differ. See lock_rename(). */ -static void __d_move(struct dentry * dentry, struct dentry * target) +static void __d_move(struct dentry *dentry, struct dentry *target, + bool exchange) { if (!dentry->d_inode) printk(KERN_WARNING "VFS: moving negative dcache entry\n"); @@ -2619,6 +2623,10 @@ static void __d_move(struct dentry * dentry, struct dentry * target) /* Unhash the target: dput() will then get rid of it */ __d_drop(target); + if (exchange) { + __d_rehash(target, + d_hash(dentry->d_parent, dentry->d_name.hash)); + } list_del(&dentry->d_u.d_child); list_del(&target->d_u.d_child); @@ -2645,6 +2653,8 @@ static void __d_move(struct dentry * dentry, struct dentry * target) write_seqcount_end(&dentry->d_seq); dentry_unlock_parents_for_move(dentry, target); + if (exchange) + fsnotify_d_move(target); spin_unlock(&target->d_lock); fsnotify_d_move(dentry); spin_unlock(&dentry->d_lock); @@ -2662,11 +2672,31 @@ static void __d_move(struct dentry * dentry, struct dentry * target) void d_move(struct dentry *dentry, struct dentry *target) { write_seqlock(&rename_lock); - __d_move(dentry, target); + __d_move(dentry, target, false); write_sequnlock(&rename_lock); } EXPORT_SYMBOL(d_move); +/* + * d_exchange - exchange two dentries + * @dentry1: first dentry + * @dentry2: second dentry + */ +void d_exchange(struct dentry *dentry1, struct dentry *dentry2) +{ + write_seqlock(&rename_lock); + + WARN_ON(!dentry1->d_inode); + WARN_ON(!dentry2->d_inode); + WARN_ON(IS_ROOT(dentry1)); + WARN_ON(IS_ROOT(dentry2)); + + __d_move(dentry1, dentry2, true); + + write_sequnlock(&rename_lock); +} + + /** * d_ancestor - search for an ancestor * @p1: ancestor dentry @@ -2714,7 +2744,7 @@ static struct dentry *__d_unalias(struct inode *inode, m2 = &alias->d_parent->d_inode->i_mutex; out_unalias: if (likely(!d_mountpoint(alias))) { - __d_move(alias, dentry); + __d_move(alias, dentry, false); ret = alias; } out_err: diff --git a/fs/namei.c b/fs/namei.c index 5b41d4bfecd1..c23621255df0 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -4025,6 +4025,8 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry, const unsigned char *old_name; struct inode *source = old_dentry->d_inode; struct inode *target = new_dentry->d_inode; + bool new_is_dir = false; + unsigned max_links = new_dir->i_sb->s_max_links; if (source == target) return 0; @@ -4033,10 +4035,16 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry, if (error) return error; - if (!target) + if (!target) { error = may_create(new_dir, new_dentry); - else - error = may_delete(new_dir, new_dentry, is_dir); + } else { + new_is_dir = d_is_dir(new_dentry); + + if (!(flags & RENAME_EXCHANGE)) + error = may_delete(new_dir, new_dentry, is_dir); + else + error = may_delete(new_dir, new_dentry, new_is_dir); + } if (error) return error; @@ -4047,10 +4055,17 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry, * If we are going to change the parent - check write permissions, * we'll need to flip '..'. */ - if (is_dir && new_dir != old_dir) { - error = inode_permission(source, MAY_WRITE); - if (error) - return error; + if (new_dir != old_dir) { + if (is_dir) { + error = inode_permission(source, MAY_WRITE); + if (error) + return error; + } + if ((flags & RENAME_EXCHANGE) && new_is_dir) { + error = inode_permission(target, MAY_WRITE); + if (error) + return error; + } } error = security_inode_rename(old_dir, old_dentry, new_dir, new_dentry, @@ -4060,25 +4075,24 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry, old_name = fsnotify_oldname_init(old_dentry->d_name.name); dget(new_dentry); - if (!is_dir) - lock_two_nondirectories(source, target); - else if (target) - mutex_lock(&target->i_mutex); + if (!(flags & RENAME_EXCHANGE)) { + if (!is_dir) + lock_two_nondirectories(source, target); + else if (target) + mutex_lock(&target->i_mutex); + } error = -EBUSY; if (d_mountpoint(old_dentry) || d_mountpoint(new_dentry)) goto out; - if (is_dir) { - unsigned max_links = new_dir->i_sb->s_max_links; - + if (max_links && new_dir != old_dir) { error = -EMLINK; - if (max_links && !target && new_dir != old_dir && - new_dir->i_nlink >= max_links) + if (is_dir && !new_is_dir && new_dir->i_nlink >= max_links) + goto out; + if ((flags & RENAME_EXCHANGE) && !is_dir && new_is_dir && + old_dir->i_nlink > max_links) goto out; - - if (target) - shrink_dcache_parent(new_dentry); } else { error = try_break_deleg(source, delegated_inode); if (error) @@ -4089,27 +4103,40 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry, goto out; } } + if (is_dir && !(flags & RENAME_EXCHANGE) && target) + shrink_dcache_parent(new_dentry); error = old_dir->i_op->rename(old_dir, old_dentry, new_dir, new_dentry, flags); if (error) goto out; - if (target) { + if (!(flags & RENAME_EXCHANGE) && target) { if (is_dir) target->i_flags |= S_DEAD; dont_mount(new_dentry); } - if (!(old_dir->i_sb->s_type->fs_flags & FS_RENAME_DOES_D_MOVE)) - d_move(old_dentry, new_dentry); + if (!(old_dir->i_sb->s_type->fs_flags & FS_RENAME_DOES_D_MOVE)) { + if (!(flags & RENAME_EXCHANGE)) + d_move(old_dentry, new_dentry); + else + d_exchange(old_dentry, new_dentry); + } out: - if (!is_dir) - unlock_two_nondirectories(source, target); - else if (target) - mutex_unlock(&target->i_mutex); + if (!(flags & RENAME_EXCHANGE)) { + if (!is_dir) + unlock_two_nondirectories(source, target); + else if (target) + mutex_unlock(&target->i_mutex); + } dput(new_dentry); - if (!error) + if (!error) { fsnotify_move(old_dir, new_dir, old_name, is_dir, - target, old_dentry); + !(flags & RENAME_EXCHANGE) ? target : NULL, old_dentry); + if (flags & RENAME_EXCHANGE) { + fsnotify_move(new_dir, old_dir, old_dentry->d_name.name, + new_is_dir, NULL, new_dentry); + } + } fsnotify_oldname_free(old_name); return error; @@ -4129,9 +4156,12 @@ SYSCALL_DEFINE5(renameat2, int, olddfd, const char __user *, oldname, bool should_retry = false; int error; - if (flags & ~RENAME_NOREPLACE) + if (flags & ~(RENAME_NOREPLACE | RENAME_EXCHANGE)) return -EOPNOTSUPP; + if ((flags & RENAME_NOREPLACE) && (flags & RENAME_EXCHANGE)) + return -EINVAL; + retry: from = user_path_parent(olddfd, oldname, &oldnd, lookup_flags); if (IS_ERR(from)) { @@ -4166,7 +4196,8 @@ retry: oldnd.flags &= ~LOOKUP_PARENT; newnd.flags &= ~LOOKUP_PARENT; - newnd.flags |= LOOKUP_RENAME_TARGET; + if (!(flags & RENAME_EXCHANGE)) + newnd.flags |= LOOKUP_RENAME_TARGET; retry_deleg: trap = lock_rename(new_dir, old_dir); @@ -4186,12 +4217,23 @@ retry_deleg: error = -EEXIST; if ((flags & RENAME_NOREPLACE) && d_is_positive(new_dentry)) goto exit5; + if (flags & RENAME_EXCHANGE) { + error = -ENOENT; + if (!new_dentry->d_inode) + goto exit5; + + if (!d_is_dir(new_dentry)) { + error = -ENOTDIR; + if (newnd.last.name[newnd.last.len]) + goto exit5; + } + } /* unless the source is a directory trailing slashes give -ENOTDIR */ if (!d_is_dir(old_dentry)) { error = -ENOTDIR; if (oldnd.last.name[oldnd.last.len]) goto exit5; - if (newnd.last.name[newnd.last.len]) + if (!(flags & RENAME_EXCHANGE) && newnd.last.name[newnd.last.len]) goto exit5; } /* source should not be ancestor of target */ @@ -4199,7 +4241,8 @@ retry_deleg: if (old_dentry == trap) goto exit5; /* target should not be an ancestor of source */ - error = -ENOTEMPTY; + if (!(flags & RENAME_EXCHANGE)) + error = -ENOTEMPTY; if (new_dentry == trap) goto exit5; diff --git a/include/linux/dcache.h b/include/linux/dcache.h index 901616910e0a..87e14e698f89 100644 --- a/include/linux/dcache.h +++ b/include/linux/dcache.h @@ -306,6 +306,7 @@ extern void dentry_update_name_case(struct dentry *, struct qstr *); /* used for rename() and baskets */ extern void d_move(struct dentry *, struct dentry *); +extern void d_exchange(struct dentry *, struct dentry *); extern struct dentry *d_ancestor(struct dentry *, struct dentry *); /* appendix may either be NULL or be used for transname suffixes */ diff --git a/include/uapi/linux/fs.h b/include/uapi/linux/fs.h index 9250f4dd7d96..ca1a11bb4443 100644 --- a/include/uapi/linux/fs.h +++ b/include/uapi/linux/fs.h @@ -36,6 +36,7 @@ #define SEEK_MAX SEEK_HOLE #define RENAME_NOREPLACE (1 << 0) /* Don't overwrite target */ +#define RENAME_EXCHANGE (1 << 1) /* Exchange source and dest */ struct fstrim_range { __u64 start; diff --git a/security/security.c b/security/security.c index d00ae28dea76..0ef72cd2255f 100644 --- a/security/security.c +++ b/security/security.c @@ -439,6 +439,14 @@ int security_path_rename(struct path *old_dir, struct dentry *old_dentry, if (unlikely(IS_PRIVATE(old_dentry->d_inode) || (new_dentry->d_inode && IS_PRIVATE(new_dentry->d_inode)))) return 0; + + if (flags & RENAME_EXCHANGE) { + int err = security_ops->path_rename(new_dir, new_dentry, + old_dir, old_dentry); + if (err) + return err; + } + return security_ops->path_rename(old_dir, old_dentry, new_dir, new_dentry); } @@ -531,6 +539,14 @@ int security_inode_rename(struct inode *old_dir, struct dentry *old_dentry, if (unlikely(IS_PRIVATE(old_dentry->d_inode) || (new_dentry->d_inode && IS_PRIVATE(new_dentry->d_inode)))) return 0; + + if (flags & RENAME_EXCHANGE) { + int err = security_ops->inode_rename(new_dir, new_dentry, + old_dir, old_dentry); + if (err) + return err; + } + return security_ops->inode_rename(old_dir, old_dentry, new_dir, new_dentry); } -- 1.8.1.4 ^ permalink raw reply related [flat|nested] 22+ messages in thread
* Re: [PATCH 07/11] vfs: add cross-rename 2013-11-20 13:01 ` [PATCH 07/11] vfs: add cross-rename Miklos Szeredi @ 2013-11-20 16:39 ` Andy Lutomirski 2013-11-20 16:44 ` Miklos Szeredi 0 siblings, 1 reply; 22+ messages in thread From: Andy Lutomirski @ 2013-11-20 16:39 UTC (permalink / raw) To: Miklos Szeredi Cc: Al Viro, Linus Torvalds, Linux FS Devel, linux-kernel@vger.kernel.org, Christoph Hellwig, Andrew Morton, David Howells, Zach Brown, Jan Kara, mszeredi On Wed, Nov 20, 2013 at 5:01 AM, Miklos Szeredi <miklos@szeredi.hu> wrote: > From: Miklos Szeredi <mszeredi@suse.cz> > > If flags contain RENAME_EXCHANGE then exchange source and destination files. > There's no restriction on the type of the files; e.g. a directory can be > exchanged with a symlink. What happens if both RENAME_EXCHANGE and RENAME_NOREPLACE are set? --Andy > > Signed-off-by: Miklos Szeredi <mszeredi@suse.cz> > --- > fs/dcache.c | 46 +++++++++++++++++---- > fs/namei.c | 107 +++++++++++++++++++++++++++++++++--------------- > include/linux/dcache.h | 1 + > include/uapi/linux/fs.h | 1 + > security/security.c | 16 ++++++++ > 5 files changed, 131 insertions(+), 40 deletions(-) > > diff --git a/fs/dcache.c b/fs/dcache.c > index 0a38ef8d7f00..ea2fca1a2ec9 100644 > --- a/fs/dcache.c > +++ b/fs/dcache.c > @@ -2527,12 +2527,14 @@ static void switch_names(struct dentry *dentry, struct dentry *target) > dentry->d_name.name = dentry->d_iname; > } else { > /* > - * Both are internal. Just copy target to dentry > + * Both are internal. > */ > - memcpy(dentry->d_iname, target->d_name.name, > - target->d_name.len + 1); > - dentry->d_name.len = target->d_name.len; > - return; > + unsigned int i; > + BUILD_BUG_ON(!IS_ALIGNED(DNAME_INLINE_LEN, sizeof(long))); > + for (i = 0; i < DNAME_INLINE_LEN / sizeof(long); i++) { > + swap(((long *) &dentry->d_iname)[i], > + ((long *) &target->d_iname)[i]); > + } > } > } > swap(dentry->d_name.len, target->d_name.len); > @@ -2589,13 +2591,15 @@ static void dentry_unlock_parents_for_move(struct dentry *dentry, > * __d_move - move a dentry > * @dentry: entry to move > * @target: new dentry > + * @exchange: exchange the two dentries > * > * Update the dcache to reflect the move of a file name. Negative > * dcache entries should not be moved in this way. Caller must hold > * rename_lock, the i_mutex of the source and target directories, > * and the sb->s_vfs_rename_mutex if they differ. See lock_rename(). > */ > -static void __d_move(struct dentry * dentry, struct dentry * target) > +static void __d_move(struct dentry *dentry, struct dentry *target, > + bool exchange) > { > if (!dentry->d_inode) > printk(KERN_WARNING "VFS: moving negative dcache entry\n"); > @@ -2619,6 +2623,10 @@ static void __d_move(struct dentry * dentry, struct dentry * target) > > /* Unhash the target: dput() will then get rid of it */ > __d_drop(target); > + if (exchange) { > + __d_rehash(target, > + d_hash(dentry->d_parent, dentry->d_name.hash)); > + } > > list_del(&dentry->d_u.d_child); > list_del(&target->d_u.d_child); > @@ -2645,6 +2653,8 @@ static void __d_move(struct dentry * dentry, struct dentry * target) > write_seqcount_end(&dentry->d_seq); > > dentry_unlock_parents_for_move(dentry, target); > + if (exchange) > + fsnotify_d_move(target); > spin_unlock(&target->d_lock); > fsnotify_d_move(dentry); > spin_unlock(&dentry->d_lock); > @@ -2662,11 +2672,31 @@ static void __d_move(struct dentry * dentry, struct dentry * target) > void d_move(struct dentry *dentry, struct dentry *target) > { > write_seqlock(&rename_lock); > - __d_move(dentry, target); > + __d_move(dentry, target, false); > write_sequnlock(&rename_lock); > } > EXPORT_SYMBOL(d_move); > > +/* > + * d_exchange - exchange two dentries > + * @dentry1: first dentry > + * @dentry2: second dentry > + */ > +void d_exchange(struct dentry *dentry1, struct dentry *dentry2) > +{ > + write_seqlock(&rename_lock); > + > + WARN_ON(!dentry1->d_inode); > + WARN_ON(!dentry2->d_inode); > + WARN_ON(IS_ROOT(dentry1)); > + WARN_ON(IS_ROOT(dentry2)); > + > + __d_move(dentry1, dentry2, true); > + > + write_sequnlock(&rename_lock); > +} > + > + > /** > * d_ancestor - search for an ancestor > * @p1: ancestor dentry > @@ -2714,7 +2744,7 @@ static struct dentry *__d_unalias(struct inode *inode, > m2 = &alias->d_parent->d_inode->i_mutex; > out_unalias: > if (likely(!d_mountpoint(alias))) { > - __d_move(alias, dentry); > + __d_move(alias, dentry, false); > ret = alias; > } > out_err: > diff --git a/fs/namei.c b/fs/namei.c > index 5b41d4bfecd1..c23621255df0 100644 > --- a/fs/namei.c > +++ b/fs/namei.c > @@ -4025,6 +4025,8 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry, > const unsigned char *old_name; > struct inode *source = old_dentry->d_inode; > struct inode *target = new_dentry->d_inode; > + bool new_is_dir = false; > + unsigned max_links = new_dir->i_sb->s_max_links; > > if (source == target) > return 0; > @@ -4033,10 +4035,16 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry, > if (error) > return error; > > - if (!target) > + if (!target) { > error = may_create(new_dir, new_dentry); > - else > - error = may_delete(new_dir, new_dentry, is_dir); > + } else { > + new_is_dir = d_is_dir(new_dentry); > + > + if (!(flags & RENAME_EXCHANGE)) > + error = may_delete(new_dir, new_dentry, is_dir); > + else > + error = may_delete(new_dir, new_dentry, new_is_dir); > + } > if (error) > return error; > > @@ -4047,10 +4055,17 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry, > * If we are going to change the parent - check write permissions, > * we'll need to flip '..'. > */ > - if (is_dir && new_dir != old_dir) { > - error = inode_permission(source, MAY_WRITE); > - if (error) > - return error; > + if (new_dir != old_dir) { > + if (is_dir) { > + error = inode_permission(source, MAY_WRITE); > + if (error) > + return error; > + } > + if ((flags & RENAME_EXCHANGE) && new_is_dir) { > + error = inode_permission(target, MAY_WRITE); > + if (error) > + return error; > + } > } > > error = security_inode_rename(old_dir, old_dentry, new_dir, new_dentry, > @@ -4060,25 +4075,24 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry, > > old_name = fsnotify_oldname_init(old_dentry->d_name.name); > dget(new_dentry); > - if (!is_dir) > - lock_two_nondirectories(source, target); > - else if (target) > - mutex_lock(&target->i_mutex); > + if (!(flags & RENAME_EXCHANGE)) { > + if (!is_dir) > + lock_two_nondirectories(source, target); > + else if (target) > + mutex_lock(&target->i_mutex); > + } > > error = -EBUSY; > if (d_mountpoint(old_dentry) || d_mountpoint(new_dentry)) > goto out; > > - if (is_dir) { > - unsigned max_links = new_dir->i_sb->s_max_links; > - > + if (max_links && new_dir != old_dir) { > error = -EMLINK; > - if (max_links && !target && new_dir != old_dir && > - new_dir->i_nlink >= max_links) > + if (is_dir && !new_is_dir && new_dir->i_nlink >= max_links) > + goto out; > + if ((flags & RENAME_EXCHANGE) && !is_dir && new_is_dir && > + old_dir->i_nlink > max_links) > goto out; > - > - if (target) > - shrink_dcache_parent(new_dentry); > } else { > error = try_break_deleg(source, delegated_inode); > if (error) > @@ -4089,27 +4103,40 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry, > goto out; > } > } > + if (is_dir && !(flags & RENAME_EXCHANGE) && target) > + shrink_dcache_parent(new_dentry); > error = old_dir->i_op->rename(old_dir, old_dentry, new_dir, new_dentry, > flags); > if (error) > goto out; > > - if (target) { > + if (!(flags & RENAME_EXCHANGE) && target) { > if (is_dir) > target->i_flags |= S_DEAD; > dont_mount(new_dentry); > } > - if (!(old_dir->i_sb->s_type->fs_flags & FS_RENAME_DOES_D_MOVE)) > - d_move(old_dentry, new_dentry); > + if (!(old_dir->i_sb->s_type->fs_flags & FS_RENAME_DOES_D_MOVE)) { > + if (!(flags & RENAME_EXCHANGE)) > + d_move(old_dentry, new_dentry); > + else > + d_exchange(old_dentry, new_dentry); > + } > out: > - if (!is_dir) > - unlock_two_nondirectories(source, target); > - else if (target) > - mutex_unlock(&target->i_mutex); > + if (!(flags & RENAME_EXCHANGE)) { > + if (!is_dir) > + unlock_two_nondirectories(source, target); > + else if (target) > + mutex_unlock(&target->i_mutex); > + } > dput(new_dentry); > - if (!error) > + if (!error) { > fsnotify_move(old_dir, new_dir, old_name, is_dir, > - target, old_dentry); > + !(flags & RENAME_EXCHANGE) ? target : NULL, old_dentry); > + if (flags & RENAME_EXCHANGE) { > + fsnotify_move(new_dir, old_dir, old_dentry->d_name.name, > + new_is_dir, NULL, new_dentry); > + } > + } > fsnotify_oldname_free(old_name); > > return error; > @@ -4129,9 +4156,12 @@ SYSCALL_DEFINE5(renameat2, int, olddfd, const char __user *, oldname, > bool should_retry = false; > int error; > > - if (flags & ~RENAME_NOREPLACE) > + if (flags & ~(RENAME_NOREPLACE | RENAME_EXCHANGE)) > return -EOPNOTSUPP; > > + if ((flags & RENAME_NOREPLACE) && (flags & RENAME_EXCHANGE)) > + return -EINVAL; > + > retry: > from = user_path_parent(olddfd, oldname, &oldnd, lookup_flags); > if (IS_ERR(from)) { > @@ -4166,7 +4196,8 @@ retry: > > oldnd.flags &= ~LOOKUP_PARENT; > newnd.flags &= ~LOOKUP_PARENT; > - newnd.flags |= LOOKUP_RENAME_TARGET; > + if (!(flags & RENAME_EXCHANGE)) > + newnd.flags |= LOOKUP_RENAME_TARGET; > > retry_deleg: > trap = lock_rename(new_dir, old_dir); > @@ -4186,12 +4217,23 @@ retry_deleg: > error = -EEXIST; > if ((flags & RENAME_NOREPLACE) && d_is_positive(new_dentry)) > goto exit5; > + if (flags & RENAME_EXCHANGE) { > + error = -ENOENT; > + if (!new_dentry->d_inode) > + goto exit5; > + > + if (!d_is_dir(new_dentry)) { > + error = -ENOTDIR; > + if (newnd.last.name[newnd.last.len]) > + goto exit5; > + } > + } > /* unless the source is a directory trailing slashes give -ENOTDIR */ > if (!d_is_dir(old_dentry)) { > error = -ENOTDIR; > if (oldnd.last.name[oldnd.last.len]) > goto exit5; > - if (newnd.last.name[newnd.last.len]) > + if (!(flags & RENAME_EXCHANGE) && newnd.last.name[newnd.last.len]) > goto exit5; > } > /* source should not be ancestor of target */ > @@ -4199,7 +4241,8 @@ retry_deleg: > if (old_dentry == trap) > goto exit5; > /* target should not be an ancestor of source */ > - error = -ENOTEMPTY; > + if (!(flags & RENAME_EXCHANGE)) > + error = -ENOTEMPTY; > if (new_dentry == trap) > goto exit5; > > diff --git a/include/linux/dcache.h b/include/linux/dcache.h > index 901616910e0a..87e14e698f89 100644 > --- a/include/linux/dcache.h > +++ b/include/linux/dcache.h > @@ -306,6 +306,7 @@ extern void dentry_update_name_case(struct dentry *, struct qstr *); > > /* used for rename() and baskets */ > extern void d_move(struct dentry *, struct dentry *); > +extern void d_exchange(struct dentry *, struct dentry *); > extern struct dentry *d_ancestor(struct dentry *, struct dentry *); > > /* appendix may either be NULL or be used for transname suffixes */ > diff --git a/include/uapi/linux/fs.h b/include/uapi/linux/fs.h > index 9250f4dd7d96..ca1a11bb4443 100644 > --- a/include/uapi/linux/fs.h > +++ b/include/uapi/linux/fs.h > @@ -36,6 +36,7 @@ > #define SEEK_MAX SEEK_HOLE > > #define RENAME_NOREPLACE (1 << 0) /* Don't overwrite target */ > +#define RENAME_EXCHANGE (1 << 1) /* Exchange source and dest */ > > struct fstrim_range { > __u64 start; > diff --git a/security/security.c b/security/security.c > index d00ae28dea76..0ef72cd2255f 100644 > --- a/security/security.c > +++ b/security/security.c > @@ -439,6 +439,14 @@ int security_path_rename(struct path *old_dir, struct dentry *old_dentry, > if (unlikely(IS_PRIVATE(old_dentry->d_inode) || > (new_dentry->d_inode && IS_PRIVATE(new_dentry->d_inode)))) > return 0; > + > + if (flags & RENAME_EXCHANGE) { > + int err = security_ops->path_rename(new_dir, new_dentry, > + old_dir, old_dentry); > + if (err) > + return err; > + } > + > return security_ops->path_rename(old_dir, old_dentry, new_dir, > new_dentry); > } > @@ -531,6 +539,14 @@ int security_inode_rename(struct inode *old_dir, struct dentry *old_dentry, > if (unlikely(IS_PRIVATE(old_dentry->d_inode) || > (new_dentry->d_inode && IS_PRIVATE(new_dentry->d_inode)))) > return 0; > + > + if (flags & RENAME_EXCHANGE) { > + int err = security_ops->inode_rename(new_dir, new_dentry, > + old_dir, old_dentry); > + if (err) > + return err; > + } > + > return security_ops->inode_rename(old_dir, old_dentry, > new_dir, new_dentry); > } > -- > 1.8.1.4 > -- Andy Lutomirski AMA Capital Management, LLC ^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH 07/11] vfs: add cross-rename 2013-11-20 16:39 ` Andy Lutomirski @ 2013-11-20 16:44 ` Miklos Szeredi 2013-11-20 16:51 ` Andy Lutomirski 0 siblings, 1 reply; 22+ messages in thread From: Miklos Szeredi @ 2013-11-20 16:44 UTC (permalink / raw) To: Andy Lutomirski Cc: Al Viro, Linus Torvalds, Linux FS Devel, linux-kernel@vger.kernel.org, Christoph Hellwig, Andrew Morton, David Howells, Zach Brown, Jan Kara, mszeredi@suse.cz On Wed, Nov 20, 2013 at 5:39 PM, Andy Lutomirski <luto@amacapital.net> wrote: > On Wed, Nov 20, 2013 at 5:01 AM, Miklos Szeredi <miklos@szeredi.hu> wrote: >> From: Miklos Szeredi <mszeredi@suse.cz> >> >> If flags contain RENAME_EXCHANGE then exchange source and destination files. >> There's no restriction on the type of the files; e.g. a directory can be >> exchanged with a symlink. > > What happens if both RENAME_EXCHANGE and RENAME_NOREPLACE are set? It fails with EINVAL, since it's a nonsensical combination. RENAME_EXCHANGE requires the target to exist, RENAME_NOREPLACE requires the target to _not_ exist. Thanks, Miklos > > --Andy > >> >> Signed-off-by: Miklos Szeredi <mszeredi@suse.cz> >> --- >> fs/dcache.c | 46 +++++++++++++++++---- >> fs/namei.c | 107 +++++++++++++++++++++++++++++++++--------------- >> include/linux/dcache.h | 1 + >> include/uapi/linux/fs.h | 1 + >> security/security.c | 16 ++++++++ >> 5 files changed, 131 insertions(+), 40 deletions(-) >> >> diff --git a/fs/dcache.c b/fs/dcache.c >> index 0a38ef8d7f00..ea2fca1a2ec9 100644 >> --- a/fs/dcache.c >> +++ b/fs/dcache.c >> @@ -2527,12 +2527,14 @@ static void switch_names(struct dentry *dentry, struct dentry *target) >> dentry->d_name.name = dentry->d_iname; >> } else { >> /* >> - * Both are internal. Just copy target to dentry >> + * Both are internal. >> */ >> - memcpy(dentry->d_iname, target->d_name.name, >> - target->d_name.len + 1); >> - dentry->d_name.len = target->d_name.len; >> - return; >> + unsigned int i; >> + BUILD_BUG_ON(!IS_ALIGNED(DNAME_INLINE_LEN, sizeof(long))); >> + for (i = 0; i < DNAME_INLINE_LEN / sizeof(long); i++) { >> + swap(((long *) &dentry->d_iname)[i], >> + ((long *) &target->d_iname)[i]); >> + } >> } >> } >> swap(dentry->d_name.len, target->d_name.len); >> @@ -2589,13 +2591,15 @@ static void dentry_unlock_parents_for_move(struct dentry *dentry, >> * __d_move - move a dentry >> * @dentry: entry to move >> * @target: new dentry >> + * @exchange: exchange the two dentries >> * >> * Update the dcache to reflect the move of a file name. Negative >> * dcache entries should not be moved in this way. Caller must hold >> * rename_lock, the i_mutex of the source and target directories, >> * and the sb->s_vfs_rename_mutex if they differ. See lock_rename(). >> */ >> -static void __d_move(struct dentry * dentry, struct dentry * target) >> +static void __d_move(struct dentry *dentry, struct dentry *target, >> + bool exchange) >> { >> if (!dentry->d_inode) >> printk(KERN_WARNING "VFS: moving negative dcache entry\n"); >> @@ -2619,6 +2623,10 @@ static void __d_move(struct dentry * dentry, struct dentry * target) >> >> /* Unhash the target: dput() will then get rid of it */ >> __d_drop(target); >> + if (exchange) { >> + __d_rehash(target, >> + d_hash(dentry->d_parent, dentry->d_name.hash)); >> + } >> >> list_del(&dentry->d_u.d_child); >> list_del(&target->d_u.d_child); >> @@ -2645,6 +2653,8 @@ static void __d_move(struct dentry * dentry, struct dentry * target) >> write_seqcount_end(&dentry->d_seq); >> >> dentry_unlock_parents_for_move(dentry, target); >> + if (exchange) >> + fsnotify_d_move(target); >> spin_unlock(&target->d_lock); >> fsnotify_d_move(dentry); >> spin_unlock(&dentry->d_lock); >> @@ -2662,11 +2672,31 @@ static void __d_move(struct dentry * dentry, struct dentry * target) >> void d_move(struct dentry *dentry, struct dentry *target) >> { >> write_seqlock(&rename_lock); >> - __d_move(dentry, target); >> + __d_move(dentry, target, false); >> write_sequnlock(&rename_lock); >> } >> EXPORT_SYMBOL(d_move); >> >> +/* >> + * d_exchange - exchange two dentries >> + * @dentry1: first dentry >> + * @dentry2: second dentry >> + */ >> +void d_exchange(struct dentry *dentry1, struct dentry *dentry2) >> +{ >> + write_seqlock(&rename_lock); >> + >> + WARN_ON(!dentry1->d_inode); >> + WARN_ON(!dentry2->d_inode); >> + WARN_ON(IS_ROOT(dentry1)); >> + WARN_ON(IS_ROOT(dentry2)); >> + >> + __d_move(dentry1, dentry2, true); >> + >> + write_sequnlock(&rename_lock); >> +} >> + >> + >> /** >> * d_ancestor - search for an ancestor >> * @p1: ancestor dentry >> @@ -2714,7 +2744,7 @@ static struct dentry *__d_unalias(struct inode *inode, >> m2 = &alias->d_parent->d_inode->i_mutex; >> out_unalias: >> if (likely(!d_mountpoint(alias))) { >> - __d_move(alias, dentry); >> + __d_move(alias, dentry, false); >> ret = alias; >> } >> out_err: >> diff --git a/fs/namei.c b/fs/namei.c >> index 5b41d4bfecd1..c23621255df0 100644 >> --- a/fs/namei.c >> +++ b/fs/namei.c >> @@ -4025,6 +4025,8 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry, >> const unsigned char *old_name; >> struct inode *source = old_dentry->d_inode; >> struct inode *target = new_dentry->d_inode; >> + bool new_is_dir = false; >> + unsigned max_links = new_dir->i_sb->s_max_links; >> >> if (source == target) >> return 0; >> @@ -4033,10 +4035,16 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry, >> if (error) >> return error; >> >> - if (!target) >> + if (!target) { >> error = may_create(new_dir, new_dentry); >> - else >> - error = may_delete(new_dir, new_dentry, is_dir); >> + } else { >> + new_is_dir = d_is_dir(new_dentry); >> + >> + if (!(flags & RENAME_EXCHANGE)) >> + error = may_delete(new_dir, new_dentry, is_dir); >> + else >> + error = may_delete(new_dir, new_dentry, new_is_dir); >> + } >> if (error) >> return error; >> >> @@ -4047,10 +4055,17 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry, >> * If we are going to change the parent - check write permissions, >> * we'll need to flip '..'. >> */ >> - if (is_dir && new_dir != old_dir) { >> - error = inode_permission(source, MAY_WRITE); >> - if (error) >> - return error; >> + if (new_dir != old_dir) { >> + if (is_dir) { >> + error = inode_permission(source, MAY_WRITE); >> + if (error) >> + return error; >> + } >> + if ((flags & RENAME_EXCHANGE) && new_is_dir) { >> + error = inode_permission(target, MAY_WRITE); >> + if (error) >> + return error; >> + } >> } >> >> error = security_inode_rename(old_dir, old_dentry, new_dir, new_dentry, >> @@ -4060,25 +4075,24 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry, >> >> old_name = fsnotify_oldname_init(old_dentry->d_name.name); >> dget(new_dentry); >> - if (!is_dir) >> - lock_two_nondirectories(source, target); >> - else if (target) >> - mutex_lock(&target->i_mutex); >> + if (!(flags & RENAME_EXCHANGE)) { >> + if (!is_dir) >> + lock_two_nondirectories(source, target); >> + else if (target) >> + mutex_lock(&target->i_mutex); >> + } >> >> error = -EBUSY; >> if (d_mountpoint(old_dentry) || d_mountpoint(new_dentry)) >> goto out; >> >> - if (is_dir) { >> - unsigned max_links = new_dir->i_sb->s_max_links; >> - >> + if (max_links && new_dir != old_dir) { >> error = -EMLINK; >> - if (max_links && !target && new_dir != old_dir && >> - new_dir->i_nlink >= max_links) >> + if (is_dir && !new_is_dir && new_dir->i_nlink >= max_links) >> + goto out; >> + if ((flags & RENAME_EXCHANGE) && !is_dir && new_is_dir && >> + old_dir->i_nlink > max_links) >> goto out; >> - >> - if (target) >> - shrink_dcache_parent(new_dentry); >> } else { >> error = try_break_deleg(source, delegated_inode); >> if (error) >> @@ -4089,27 +4103,40 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry, >> goto out; >> } >> } >> + if (is_dir && !(flags & RENAME_EXCHANGE) && target) >> + shrink_dcache_parent(new_dentry); >> error = old_dir->i_op->rename(old_dir, old_dentry, new_dir, new_dentry, >> flags); >> if (error) >> goto out; >> >> - if (target) { >> + if (!(flags & RENAME_EXCHANGE) && target) { >> if (is_dir) >> target->i_flags |= S_DEAD; >> dont_mount(new_dentry); >> } >> - if (!(old_dir->i_sb->s_type->fs_flags & FS_RENAME_DOES_D_MOVE)) >> - d_move(old_dentry, new_dentry); >> + if (!(old_dir->i_sb->s_type->fs_flags & FS_RENAME_DOES_D_MOVE)) { >> + if (!(flags & RENAME_EXCHANGE)) >> + d_move(old_dentry, new_dentry); >> + else >> + d_exchange(old_dentry, new_dentry); >> + } >> out: >> - if (!is_dir) >> - unlock_two_nondirectories(source, target); >> - else if (target) >> - mutex_unlock(&target->i_mutex); >> + if (!(flags & RENAME_EXCHANGE)) { >> + if (!is_dir) >> + unlock_two_nondirectories(source, target); >> + else if (target) >> + mutex_unlock(&target->i_mutex); >> + } >> dput(new_dentry); >> - if (!error) >> + if (!error) { >> fsnotify_move(old_dir, new_dir, old_name, is_dir, >> - target, old_dentry); >> + !(flags & RENAME_EXCHANGE) ? target : NULL, old_dentry); >> + if (flags & RENAME_EXCHANGE) { >> + fsnotify_move(new_dir, old_dir, old_dentry->d_name.name, >> + new_is_dir, NULL, new_dentry); >> + } >> + } >> fsnotify_oldname_free(old_name); >> >> return error; >> @@ -4129,9 +4156,12 @@ SYSCALL_DEFINE5(renameat2, int, olddfd, const char __user *, oldname, >> bool should_retry = false; >> int error; >> >> - if (flags & ~RENAME_NOREPLACE) >> + if (flags & ~(RENAME_NOREPLACE | RENAME_EXCHANGE)) >> return -EOPNOTSUPP; >> >> + if ((flags & RENAME_NOREPLACE) && (flags & RENAME_EXCHANGE)) >> + return -EINVAL; >> + >> retry: >> from = user_path_parent(olddfd, oldname, &oldnd, lookup_flags); >> if (IS_ERR(from)) { >> @@ -4166,7 +4196,8 @@ retry: >> >> oldnd.flags &= ~LOOKUP_PARENT; >> newnd.flags &= ~LOOKUP_PARENT; >> - newnd.flags |= LOOKUP_RENAME_TARGET; >> + if (!(flags & RENAME_EXCHANGE)) >> + newnd.flags |= LOOKUP_RENAME_TARGET; >> >> retry_deleg: >> trap = lock_rename(new_dir, old_dir); >> @@ -4186,12 +4217,23 @@ retry_deleg: >> error = -EEXIST; >> if ((flags & RENAME_NOREPLACE) && d_is_positive(new_dentry)) >> goto exit5; >> + if (flags & RENAME_EXCHANGE) { >> + error = -ENOENT; >> + if (!new_dentry->d_inode) >> + goto exit5; >> + >> + if (!d_is_dir(new_dentry)) { >> + error = -ENOTDIR; >> + if (newnd.last.name[newnd.last.len]) >> + goto exit5; >> + } >> + } >> /* unless the source is a directory trailing slashes give -ENOTDIR */ >> if (!d_is_dir(old_dentry)) { >> error = -ENOTDIR; >> if (oldnd.last.name[oldnd.last.len]) >> goto exit5; >> - if (newnd.last.name[newnd.last.len]) >> + if (!(flags & RENAME_EXCHANGE) && newnd.last.name[newnd.last.len]) >> goto exit5; >> } >> /* source should not be ancestor of target */ >> @@ -4199,7 +4241,8 @@ retry_deleg: >> if (old_dentry == trap) >> goto exit5; >> /* target should not be an ancestor of source */ >> - error = -ENOTEMPTY; >> + if (!(flags & RENAME_EXCHANGE)) >> + error = -ENOTEMPTY; >> if (new_dentry == trap) >> goto exit5; >> >> diff --git a/include/linux/dcache.h b/include/linux/dcache.h >> index 901616910e0a..87e14e698f89 100644 >> --- a/include/linux/dcache.h >> +++ b/include/linux/dcache.h >> @@ -306,6 +306,7 @@ extern void dentry_update_name_case(struct dentry *, struct qstr *); >> >> /* used for rename() and baskets */ >> extern void d_move(struct dentry *, struct dentry *); >> +extern void d_exchange(struct dentry *, struct dentry *); >> extern struct dentry *d_ancestor(struct dentry *, struct dentry *); >> >> /* appendix may either be NULL or be used for transname suffixes */ >> diff --git a/include/uapi/linux/fs.h b/include/uapi/linux/fs.h >> index 9250f4dd7d96..ca1a11bb4443 100644 >> --- a/include/uapi/linux/fs.h >> +++ b/include/uapi/linux/fs.h >> @@ -36,6 +36,7 @@ >> #define SEEK_MAX SEEK_HOLE >> >> #define RENAME_NOREPLACE (1 << 0) /* Don't overwrite target */ >> +#define RENAME_EXCHANGE (1 << 1) /* Exchange source and dest */ >> >> struct fstrim_range { >> __u64 start; >> diff --git a/security/security.c b/security/security.c >> index d00ae28dea76..0ef72cd2255f 100644 >> --- a/security/security.c >> +++ b/security/security.c >> @@ -439,6 +439,14 @@ int security_path_rename(struct path *old_dir, struct dentry *old_dentry, >> if (unlikely(IS_PRIVATE(old_dentry->d_inode) || >> (new_dentry->d_inode && IS_PRIVATE(new_dentry->d_inode)))) >> return 0; >> + >> + if (flags & RENAME_EXCHANGE) { >> + int err = security_ops->path_rename(new_dir, new_dentry, >> + old_dir, old_dentry); >> + if (err) >> + return err; >> + } >> + >> return security_ops->path_rename(old_dir, old_dentry, new_dir, >> new_dentry); >> } >> @@ -531,6 +539,14 @@ int security_inode_rename(struct inode *old_dir, struct dentry *old_dentry, >> if (unlikely(IS_PRIVATE(old_dentry->d_inode) || >> (new_dentry->d_inode && IS_PRIVATE(new_dentry->d_inode)))) >> return 0; >> + >> + if (flags & RENAME_EXCHANGE) { >> + int err = security_ops->inode_rename(new_dir, new_dentry, >> + old_dir, old_dentry); >> + if (err) >> + return err; >> + } >> + >> return security_ops->inode_rename(old_dir, old_dentry, >> new_dir, new_dentry); >> } >> -- >> 1.8.1.4 >> > > > > -- > Andy Lutomirski > AMA Capital Management, LLC ^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH 07/11] vfs: add cross-rename 2013-11-20 16:44 ` Miklos Szeredi @ 2013-11-20 16:51 ` Andy Lutomirski 0 siblings, 0 replies; 22+ messages in thread From: Andy Lutomirski @ 2013-11-20 16:51 UTC (permalink / raw) To: Miklos Szeredi Cc: Al Viro, Linus Torvalds, Linux FS Devel, linux-kernel@vger.kernel.org, Christoph Hellwig, Andrew Morton, David Howells, Zach Brown, Jan Kara, mszeredi@suse.cz On Wed, Nov 20, 2013 at 8:44 AM, Miklos Szeredi <miklos@szeredi.hu> wrote: > On Wed, Nov 20, 2013 at 5:39 PM, Andy Lutomirski <luto@amacapital.net> wrote: >> On Wed, Nov 20, 2013 at 5:01 AM, Miklos Szeredi <miklos@szeredi.hu> wrote: >>> From: Miklos Szeredi <mszeredi@suse.cz> >>> >>> If flags contain RENAME_EXCHANGE then exchange source and destination files. >>> There's no restriction on the type of the files; e.g. a directory can be >>> exchanged with a symlink. >> >> What happens if both RENAME_EXCHANGE and RENAME_NOREPLACE are set? > > It fails with EINVAL, since it's a nonsensical combination. > RENAME_EXCHANGE requires the target to exist, RENAME_NOREPLACE > requires the target to _not_ exist. *facepalm* I looked for that and didn't find it. I'm not sure how I missed the explicit check. --Andy > > Thanks, > Miklos > >> >> --Andy >> >>> >>> Signed-off-by: Miklos Szeredi <mszeredi@suse.cz> >>> --- >>> fs/dcache.c | 46 +++++++++++++++++---- >>> fs/namei.c | 107 +++++++++++++++++++++++++++++++++--------------- >>> include/linux/dcache.h | 1 + >>> include/uapi/linux/fs.h | 1 + >>> security/security.c | 16 ++++++++ >>> 5 files changed, 131 insertions(+), 40 deletions(-) >>> >>> diff --git a/fs/dcache.c b/fs/dcache.c >>> index 0a38ef8d7f00..ea2fca1a2ec9 100644 >>> --- a/fs/dcache.c >>> +++ b/fs/dcache.c >>> @@ -2527,12 +2527,14 @@ static void switch_names(struct dentry *dentry, struct dentry *target) >>> dentry->d_name.name = dentry->d_iname; >>> } else { >>> /* >>> - * Both are internal. Just copy target to dentry >>> + * Both are internal. >>> */ >>> - memcpy(dentry->d_iname, target->d_name.name, >>> - target->d_name.len + 1); >>> - dentry->d_name.len = target->d_name.len; >>> - return; >>> + unsigned int i; >>> + BUILD_BUG_ON(!IS_ALIGNED(DNAME_INLINE_LEN, sizeof(long))); >>> + for (i = 0; i < DNAME_INLINE_LEN / sizeof(long); i++) { >>> + swap(((long *) &dentry->d_iname)[i], >>> + ((long *) &target->d_iname)[i]); >>> + } >>> } >>> } >>> swap(dentry->d_name.len, target->d_name.len); >>> @@ -2589,13 +2591,15 @@ static void dentry_unlock_parents_for_move(struct dentry *dentry, >>> * __d_move - move a dentry >>> * @dentry: entry to move >>> * @target: new dentry >>> + * @exchange: exchange the two dentries >>> * >>> * Update the dcache to reflect the move of a file name. Negative >>> * dcache entries should not be moved in this way. Caller must hold >>> * rename_lock, the i_mutex of the source and target directories, >>> * and the sb->s_vfs_rename_mutex if they differ. See lock_rename(). >>> */ >>> -static void __d_move(struct dentry * dentry, struct dentry * target) >>> +static void __d_move(struct dentry *dentry, struct dentry *target, >>> + bool exchange) >>> { >>> if (!dentry->d_inode) >>> printk(KERN_WARNING "VFS: moving negative dcache entry\n"); >>> @@ -2619,6 +2623,10 @@ static void __d_move(struct dentry * dentry, struct dentry * target) >>> >>> /* Unhash the target: dput() will then get rid of it */ >>> __d_drop(target); >>> + if (exchange) { >>> + __d_rehash(target, >>> + d_hash(dentry->d_parent, dentry->d_name.hash)); >>> + } >>> >>> list_del(&dentry->d_u.d_child); >>> list_del(&target->d_u.d_child); >>> @@ -2645,6 +2653,8 @@ static void __d_move(struct dentry * dentry, struct dentry * target) >>> write_seqcount_end(&dentry->d_seq); >>> >>> dentry_unlock_parents_for_move(dentry, target); >>> + if (exchange) >>> + fsnotify_d_move(target); >>> spin_unlock(&target->d_lock); >>> fsnotify_d_move(dentry); >>> spin_unlock(&dentry->d_lock); >>> @@ -2662,11 +2672,31 @@ static void __d_move(struct dentry * dentry, struct dentry * target) >>> void d_move(struct dentry *dentry, struct dentry *target) >>> { >>> write_seqlock(&rename_lock); >>> - __d_move(dentry, target); >>> + __d_move(dentry, target, false); >>> write_sequnlock(&rename_lock); >>> } >>> EXPORT_SYMBOL(d_move); >>> >>> +/* >>> + * d_exchange - exchange two dentries >>> + * @dentry1: first dentry >>> + * @dentry2: second dentry >>> + */ >>> +void d_exchange(struct dentry *dentry1, struct dentry *dentry2) >>> +{ >>> + write_seqlock(&rename_lock); >>> + >>> + WARN_ON(!dentry1->d_inode); >>> + WARN_ON(!dentry2->d_inode); >>> + WARN_ON(IS_ROOT(dentry1)); >>> + WARN_ON(IS_ROOT(dentry2)); >>> + >>> + __d_move(dentry1, dentry2, true); >>> + >>> + write_sequnlock(&rename_lock); >>> +} >>> + >>> + >>> /** >>> * d_ancestor - search for an ancestor >>> * @p1: ancestor dentry >>> @@ -2714,7 +2744,7 @@ static struct dentry *__d_unalias(struct inode *inode, >>> m2 = &alias->d_parent->d_inode->i_mutex; >>> out_unalias: >>> if (likely(!d_mountpoint(alias))) { >>> - __d_move(alias, dentry); >>> + __d_move(alias, dentry, false); >>> ret = alias; >>> } >>> out_err: >>> diff --git a/fs/namei.c b/fs/namei.c >>> index 5b41d4bfecd1..c23621255df0 100644 >>> --- a/fs/namei.c >>> +++ b/fs/namei.c >>> @@ -4025,6 +4025,8 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry, >>> const unsigned char *old_name; >>> struct inode *source = old_dentry->d_inode; >>> struct inode *target = new_dentry->d_inode; >>> + bool new_is_dir = false; >>> + unsigned max_links = new_dir->i_sb->s_max_links; >>> >>> if (source == target) >>> return 0; >>> @@ -4033,10 +4035,16 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry, >>> if (error) >>> return error; >>> >>> - if (!target) >>> + if (!target) { >>> error = may_create(new_dir, new_dentry); >>> - else >>> - error = may_delete(new_dir, new_dentry, is_dir); >>> + } else { >>> + new_is_dir = d_is_dir(new_dentry); >>> + >>> + if (!(flags & RENAME_EXCHANGE)) >>> + error = may_delete(new_dir, new_dentry, is_dir); >>> + else >>> + error = may_delete(new_dir, new_dentry, new_is_dir); >>> + } >>> if (error) >>> return error; >>> >>> @@ -4047,10 +4055,17 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry, >>> * If we are going to change the parent - check write permissions, >>> * we'll need to flip '..'. >>> */ >>> - if (is_dir && new_dir != old_dir) { >>> - error = inode_permission(source, MAY_WRITE); >>> - if (error) >>> - return error; >>> + if (new_dir != old_dir) { >>> + if (is_dir) { >>> + error = inode_permission(source, MAY_WRITE); >>> + if (error) >>> + return error; >>> + } >>> + if ((flags & RENAME_EXCHANGE) && new_is_dir) { >>> + error = inode_permission(target, MAY_WRITE); >>> + if (error) >>> + return error; >>> + } >>> } >>> >>> error = security_inode_rename(old_dir, old_dentry, new_dir, new_dentry, >>> @@ -4060,25 +4075,24 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry, >>> >>> old_name = fsnotify_oldname_init(old_dentry->d_name.name); >>> dget(new_dentry); >>> - if (!is_dir) >>> - lock_two_nondirectories(source, target); >>> - else if (target) >>> - mutex_lock(&target->i_mutex); >>> + if (!(flags & RENAME_EXCHANGE)) { >>> + if (!is_dir) >>> + lock_two_nondirectories(source, target); >>> + else if (target) >>> + mutex_lock(&target->i_mutex); >>> + } >>> >>> error = -EBUSY; >>> if (d_mountpoint(old_dentry) || d_mountpoint(new_dentry)) >>> goto out; >>> >>> - if (is_dir) { >>> - unsigned max_links = new_dir->i_sb->s_max_links; >>> - >>> + if (max_links && new_dir != old_dir) { >>> error = -EMLINK; >>> - if (max_links && !target && new_dir != old_dir && >>> - new_dir->i_nlink >= max_links) >>> + if (is_dir && !new_is_dir && new_dir->i_nlink >= max_links) >>> + goto out; >>> + if ((flags & RENAME_EXCHANGE) && !is_dir && new_is_dir && >>> + old_dir->i_nlink > max_links) >>> goto out; >>> - >>> - if (target) >>> - shrink_dcache_parent(new_dentry); >>> } else { >>> error = try_break_deleg(source, delegated_inode); >>> if (error) >>> @@ -4089,27 +4103,40 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry, >>> goto out; >>> } >>> } >>> + if (is_dir && !(flags & RENAME_EXCHANGE) && target) >>> + shrink_dcache_parent(new_dentry); >>> error = old_dir->i_op->rename(old_dir, old_dentry, new_dir, new_dentry, >>> flags); >>> if (error) >>> goto out; >>> >>> - if (target) { >>> + if (!(flags & RENAME_EXCHANGE) && target) { >>> if (is_dir) >>> target->i_flags |= S_DEAD; >>> dont_mount(new_dentry); >>> } >>> - if (!(old_dir->i_sb->s_type->fs_flags & FS_RENAME_DOES_D_MOVE)) >>> - d_move(old_dentry, new_dentry); >>> + if (!(old_dir->i_sb->s_type->fs_flags & FS_RENAME_DOES_D_MOVE)) { >>> + if (!(flags & RENAME_EXCHANGE)) >>> + d_move(old_dentry, new_dentry); >>> + else >>> + d_exchange(old_dentry, new_dentry); >>> + } >>> out: >>> - if (!is_dir) >>> - unlock_two_nondirectories(source, target); >>> - else if (target) >>> - mutex_unlock(&target->i_mutex); >>> + if (!(flags & RENAME_EXCHANGE)) { >>> + if (!is_dir) >>> + unlock_two_nondirectories(source, target); >>> + else if (target) >>> + mutex_unlock(&target->i_mutex); >>> + } >>> dput(new_dentry); >>> - if (!error) >>> + if (!error) { >>> fsnotify_move(old_dir, new_dir, old_name, is_dir, >>> - target, old_dentry); >>> + !(flags & RENAME_EXCHANGE) ? target : NULL, old_dentry); >>> + if (flags & RENAME_EXCHANGE) { >>> + fsnotify_move(new_dir, old_dir, old_dentry->d_name.name, >>> + new_is_dir, NULL, new_dentry); >>> + } >>> + } >>> fsnotify_oldname_free(old_name); >>> >>> return error; >>> @@ -4129,9 +4156,12 @@ SYSCALL_DEFINE5(renameat2, int, olddfd, const char __user *, oldname, >>> bool should_retry = false; >>> int error; >>> >>> - if (flags & ~RENAME_NOREPLACE) >>> + if (flags & ~(RENAME_NOREPLACE | RENAME_EXCHANGE)) >>> return -EOPNOTSUPP; >>> >>> + if ((flags & RENAME_NOREPLACE) && (flags & RENAME_EXCHANGE)) >>> + return -EINVAL; >>> + >>> retry: >>> from = user_path_parent(olddfd, oldname, &oldnd, lookup_flags); >>> if (IS_ERR(from)) { >>> @@ -4166,7 +4196,8 @@ retry: >>> >>> oldnd.flags &= ~LOOKUP_PARENT; >>> newnd.flags &= ~LOOKUP_PARENT; >>> - newnd.flags |= LOOKUP_RENAME_TARGET; >>> + if (!(flags & RENAME_EXCHANGE)) >>> + newnd.flags |= LOOKUP_RENAME_TARGET; >>> >>> retry_deleg: >>> trap = lock_rename(new_dir, old_dir); >>> @@ -4186,12 +4217,23 @@ retry_deleg: >>> error = -EEXIST; >>> if ((flags & RENAME_NOREPLACE) && d_is_positive(new_dentry)) >>> goto exit5; >>> + if (flags & RENAME_EXCHANGE) { >>> + error = -ENOENT; >>> + if (!new_dentry->d_inode) >>> + goto exit5; >>> + >>> + if (!d_is_dir(new_dentry)) { >>> + error = -ENOTDIR; >>> + if (newnd.last.name[newnd.last.len]) >>> + goto exit5; >>> + } >>> + } >>> /* unless the source is a directory trailing slashes give -ENOTDIR */ >>> if (!d_is_dir(old_dentry)) { >>> error = -ENOTDIR; >>> if (oldnd.last.name[oldnd.last.len]) >>> goto exit5; >>> - if (newnd.last.name[newnd.last.len]) >>> + if (!(flags & RENAME_EXCHANGE) && newnd.last.name[newnd.last.len]) >>> goto exit5; >>> } >>> /* source should not be ancestor of target */ >>> @@ -4199,7 +4241,8 @@ retry_deleg: >>> if (old_dentry == trap) >>> goto exit5; >>> /* target should not be an ancestor of source */ >>> - error = -ENOTEMPTY; >>> + if (!(flags & RENAME_EXCHANGE)) >>> + error = -ENOTEMPTY; >>> if (new_dentry == trap) >>> goto exit5; >>> >>> diff --git a/include/linux/dcache.h b/include/linux/dcache.h >>> index 901616910e0a..87e14e698f89 100644 >>> --- a/include/linux/dcache.h >>> +++ b/include/linux/dcache.h >>> @@ -306,6 +306,7 @@ extern void dentry_update_name_case(struct dentry *, struct qstr *); >>> >>> /* used for rename() and baskets */ >>> extern void d_move(struct dentry *, struct dentry *); >>> +extern void d_exchange(struct dentry *, struct dentry *); >>> extern struct dentry *d_ancestor(struct dentry *, struct dentry *); >>> >>> /* appendix may either be NULL or be used for transname suffixes */ >>> diff --git a/include/uapi/linux/fs.h b/include/uapi/linux/fs.h >>> index 9250f4dd7d96..ca1a11bb4443 100644 >>> --- a/include/uapi/linux/fs.h >>> +++ b/include/uapi/linux/fs.h >>> @@ -36,6 +36,7 @@ >>> #define SEEK_MAX SEEK_HOLE >>> >>> #define RENAME_NOREPLACE (1 << 0) /* Don't overwrite target */ >>> +#define RENAME_EXCHANGE (1 << 1) /* Exchange source and dest */ >>> >>> struct fstrim_range { >>> __u64 start; >>> diff --git a/security/security.c b/security/security.c >>> index d00ae28dea76..0ef72cd2255f 100644 >>> --- a/security/security.c >>> +++ b/security/security.c >>> @@ -439,6 +439,14 @@ int security_path_rename(struct path *old_dir, struct dentry *old_dentry, >>> if (unlikely(IS_PRIVATE(old_dentry->d_inode) || >>> (new_dentry->d_inode && IS_PRIVATE(new_dentry->d_inode)))) >>> return 0; >>> + >>> + if (flags & RENAME_EXCHANGE) { >>> + int err = security_ops->path_rename(new_dir, new_dentry, >>> + old_dir, old_dentry); >>> + if (err) >>> + return err; >>> + } >>> + >>> return security_ops->path_rename(old_dir, old_dentry, new_dir, >>> new_dentry); >>> } >>> @@ -531,6 +539,14 @@ int security_inode_rename(struct inode *old_dir, struct dentry *old_dentry, >>> if (unlikely(IS_PRIVATE(old_dentry->d_inode) || >>> (new_dentry->d_inode && IS_PRIVATE(new_dentry->d_inode)))) >>> return 0; >>> + >>> + if (flags & RENAME_EXCHANGE) { >>> + int err = security_ops->inode_rename(new_dir, new_dentry, >>> + old_dir, old_dentry); >>> + if (err) >>> + return err; >>> + } >>> + >>> return security_ops->inode_rename(old_dir, old_dentry, >>> new_dir, new_dentry); >>> } >>> -- >>> 1.8.1.4 >>> >> >> >> >> -- >> Andy Lutomirski >> AMA Capital Management, LLC -- Andy Lutomirski AMA Capital Management, LLC ^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH 08/11] ext4: rename: create ext4_renament structure for local vars 2013-11-20 13:01 [PATCH 00/11] cross rename v2 Miklos Szeredi ` (6 preceding siblings ...) 2013-11-20 13:01 ` [PATCH 07/11] vfs: add cross-rename Miklos Szeredi @ 2013-11-20 13:01 ` Miklos Szeredi 2013-11-26 8:04 ` Jan Kara 2013-11-20 13:01 ` [PATCH 09/11] ext4: rename: move EMLINK check up Miklos Szeredi ` (2 subsequent siblings) 10 siblings, 1 reply; 22+ messages in thread From: Miklos Szeredi @ 2013-11-20 13:01 UTC (permalink / raw) To: viro Cc: torvalds, linux-fsdevel, linux-kernel, hch, akpm, dhowells, zab, jack, luto, mszeredi From: Miklos Szeredi <mszeredi@suse.cz> Need to split up ext4_rename() into helpers but there are two many local variables involved, so create a new structure. This also, apparently, makes the generated code size slightly smaller. Signed-off-by: Miklos Szeredi <mszeredi@suse.cz> --- fs/ext4/namei.c | 211 ++++++++++++++++++++++++++++++-------------------------- 1 file changed, 114 insertions(+), 97 deletions(-) diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c index e0129b6e74cf..c9e38bd30e8b 100644 --- a/fs/ext4/namei.c +++ b/fs/ext4/namei.c @@ -3001,6 +3001,22 @@ static struct buffer_head *ext4_get_first_dir_block(handle_t *handle, return ext4_get_first_inline_block(inode, parent_de, retval); } +struct ext4_renament { + struct inode *dir; + struct dentry *dentry; + struct inode *inode; + + /* entry for "dentry" */ + struct buffer_head *bh; + struct ext4_dir_entry_2 *de; + int inlined; + + /* entry for parent block of inode if it's a directory */ + struct buffer_head *dir_bh; + struct ext4_dir_entry_2 *parent_de; + int dir_inlined; +}; + /* * Anybody can rename anything with this: the permission checks are left to the * higher-level routines. @@ -3014,196 +3030,197 @@ static int ext4_rename(struct inode *old_dir, struct dentry *old_dentry, unsigned int flags) { handle_t *handle = NULL; - struct inode *old_inode, *new_inode; - struct buffer_head *old_bh, *new_bh, *dir_bh; - struct ext4_dir_entry_2 *old_de, *new_de; + struct ext4_renament old = { + .dir = old_dir, + .dentry = old_dentry, + }; + struct ext4_renament new = { + .dir = new_dir, + .dentry = new_dentry, + }; int retval; - int inlined = 0, new_inlined = 0; - struct ext4_dir_entry_2 *parent_de; if (flags & ~RENAME_NOREPLACE) return -EOPNOTSUPP; - dquot_initialize(old_dir); - dquot_initialize(new_dir); - - old_bh = new_bh = dir_bh = NULL; + dquot_initialize(old.dir); + dquot_initialize(new.dir); /* Initialize quotas before so that eventual writes go * in separate transaction */ - if (new_dentry->d_inode) - dquot_initialize(new_dentry->d_inode); + if (new.dentry->d_inode) + dquot_initialize(new.dentry->d_inode); - old_bh = ext4_find_entry(old_dir, &old_dentry->d_name, &old_de, NULL); + old.bh = ext4_find_entry(old.dir, &old.dentry->d_name, &old.de, NULL); /* * Check for inode number is _not_ due to possible IO errors. * We might rmdir the source, keep it as pwd of some process * and merrily kill the link to whatever was created under the * same name. Goodbye sticky bit ;-< */ - old_inode = old_dentry->d_inode; + old.inode = old.dentry->d_inode; retval = -ENOENT; - if (!old_bh || le32_to_cpu(old_de->inode) != old_inode->i_ino) + if (!old.bh || le32_to_cpu(old.de->inode) != old.inode->i_ino) goto end_rename; - new_inode = new_dentry->d_inode; - new_bh = ext4_find_entry(new_dir, &new_dentry->d_name, - &new_de, &new_inlined); - if (new_bh) { - if (!new_inode) { - brelse(new_bh); - new_bh = NULL; + new.inode = new.dentry->d_inode; + new.bh = ext4_find_entry(new.dir, &new.dentry->d_name, + &new.de, &new.inlined); + if (new.bh) { + if (!new.inode) { + brelse(new.bh); + new.bh = NULL; } } - if (new_inode && !test_opt(new_dir->i_sb, NO_AUTO_DA_ALLOC)) - ext4_alloc_da_blocks(old_inode); + if (new.inode && !test_opt(new.dir->i_sb, NO_AUTO_DA_ALLOC)) + ext4_alloc_da_blocks(old.inode); - handle = ext4_journal_start(old_dir, EXT4_HT_DIR, - (2 * EXT4_DATA_TRANS_BLOCKS(old_dir->i_sb) + + handle = ext4_journal_start(old.dir, EXT4_HT_DIR, + (2 * EXT4_DATA_TRANS_BLOCKS(old.dir->i_sb) + EXT4_INDEX_EXTRA_TRANS_BLOCKS + 2)); if (IS_ERR(handle)) return PTR_ERR(handle); - if (IS_DIRSYNC(old_dir) || IS_DIRSYNC(new_dir)) + if (IS_DIRSYNC(old.dir) || IS_DIRSYNC(new.dir)) ext4_handle_sync(handle); - if (S_ISDIR(old_inode->i_mode)) { - if (new_inode) { + if (S_ISDIR(old.inode->i_mode)) { + if (new.inode) { retval = -ENOTEMPTY; - if (!empty_dir(new_inode)) + if (!empty_dir(new.inode)) goto end_rename; } retval = -EIO; - dir_bh = ext4_get_first_dir_block(handle, old_inode, - &retval, &parent_de, - &inlined); - if (!dir_bh) + old.dir_bh = ext4_get_first_dir_block(handle, old.inode, + &retval, &old.parent_de, + &old.dir_inlined); + if (!old.dir_bh) goto end_rename; - if (le32_to_cpu(parent_de->inode) != old_dir->i_ino) + if (le32_to_cpu(old.parent_de->inode) != old.dir->i_ino) goto end_rename; retval = -EMLINK; - if (!new_inode && new_dir != old_dir && - EXT4_DIR_LINK_MAX(new_dir)) + if (!new.inode && new.dir != old.dir && + EXT4_DIR_LINK_MAX(new.dir)) goto end_rename; - BUFFER_TRACE(dir_bh, "get_write_access"); - retval = ext4_journal_get_write_access(handle, dir_bh); + BUFFER_TRACE(old.dir_bh, "get_write_access"); + retval = ext4_journal_get_write_access(handle, old.dir_bh); if (retval) goto end_rename; } - if (!new_bh) { - retval = ext4_add_entry(handle, new_dentry, old_inode); + if (!new.bh) { + retval = ext4_add_entry(handle, new.dentry, old.inode); if (retval) goto end_rename; } else { - BUFFER_TRACE(new_bh, "get write access"); - retval = ext4_journal_get_write_access(handle, new_bh); + BUFFER_TRACE(new.bh, "get write access"); + retval = ext4_journal_get_write_access(handle, new.bh); if (retval) goto end_rename; - new_de->inode = cpu_to_le32(old_inode->i_ino); - if (EXT4_HAS_INCOMPAT_FEATURE(new_dir->i_sb, + new.de->inode = cpu_to_le32(old.inode->i_ino); + if (EXT4_HAS_INCOMPAT_FEATURE(new.dir->i_sb, EXT4_FEATURE_INCOMPAT_FILETYPE)) - new_de->file_type = old_de->file_type; - new_dir->i_version++; - new_dir->i_ctime = new_dir->i_mtime = - ext4_current_time(new_dir); - ext4_mark_inode_dirty(handle, new_dir); - BUFFER_TRACE(new_bh, "call ext4_handle_dirty_metadata"); - if (!new_inlined) { + new.de->file_type = old.de->file_type; + new.dir->i_version++; + new.dir->i_ctime = new.dir->i_mtime = + ext4_current_time(new.dir); + ext4_mark_inode_dirty(handle, new.dir); + BUFFER_TRACE(new.bh, "call ext4_handle_dirty_metadata"); + if (!new.inlined) { retval = ext4_handle_dirty_dirent_node(handle, - new_dir, new_bh); + new.dir, new.bh); if (unlikely(retval)) { - ext4_std_error(new_dir->i_sb, retval); + ext4_std_error(new.dir->i_sb, retval); goto end_rename; } } - brelse(new_bh); - new_bh = NULL; + brelse(new.bh); + new.bh = NULL; } /* * Like most other Unix systems, set the ctime for inodes on a * rename. */ - old_inode->i_ctime = ext4_current_time(old_inode); - ext4_mark_inode_dirty(handle, old_inode); + old.inode->i_ctime = ext4_current_time(old.inode); + ext4_mark_inode_dirty(handle, old.inode); /* * ok, that's it */ - if (le32_to_cpu(old_de->inode) != old_inode->i_ino || - old_de->name_len != old_dentry->d_name.len || - strncmp(old_de->name, old_dentry->d_name.name, old_de->name_len) || - (retval = ext4_delete_entry(handle, old_dir, - old_de, old_bh)) == -ENOENT) { - /* old_de could have moved from under us during htree split, so + if (le32_to_cpu(old.de->inode) != old.inode->i_ino || + old.de->name_len != old.dentry->d_name.len || + strncmp(old.de->name, old.dentry->d_name.name, old.de->name_len) || + (retval = ext4_delete_entry(handle, old.dir, + old.de, old.bh)) == -ENOENT) { + /* old.de could have moved from under us during htree split, so * make sure that we are deleting the right entry. We might * also be pointing to a stale entry in the unused part of - * old_bh so just checking inum and the name isn't enough. */ + * old.bh so just checking inum and the name isn't enough. */ struct buffer_head *old_bh2; struct ext4_dir_entry_2 *old_de2; - old_bh2 = ext4_find_entry(old_dir, &old_dentry->d_name, + old_bh2 = ext4_find_entry(old.dir, &old.dentry->d_name, &old_de2, NULL); if (old_bh2) { - retval = ext4_delete_entry(handle, old_dir, + retval = ext4_delete_entry(handle, old.dir, old_de2, old_bh2); brelse(old_bh2); } } if (retval) { - ext4_warning(old_dir->i_sb, + ext4_warning(old.dir->i_sb, "Deleting old file (%lu), %d, error=%d", - old_dir->i_ino, old_dir->i_nlink, retval); + old.dir->i_ino, old.dir->i_nlink, retval); } - if (new_inode) { - ext4_dec_count(handle, new_inode); - new_inode->i_ctime = ext4_current_time(new_inode); + if (new.inode) { + ext4_dec_count(handle, new.inode); + new.inode->i_ctime = ext4_current_time(new.inode); } - old_dir->i_ctime = old_dir->i_mtime = ext4_current_time(old_dir); - ext4_update_dx_flag(old_dir); - if (dir_bh) { - parent_de->inode = cpu_to_le32(new_dir->i_ino); - BUFFER_TRACE(dir_bh, "call ext4_handle_dirty_metadata"); - if (!inlined) { - if (is_dx(old_inode)) { + old.dir->i_ctime = old.dir->i_mtime = ext4_current_time(old.dir); + ext4_update_dx_flag(old.dir); + if (old.dir_bh) { + old.parent_de->inode = cpu_to_le32(new.dir->i_ino); + BUFFER_TRACE(old.dir_bh, "call ext4_handle_dirty_metadata"); + if (!old.dir_inlined) { + if (is_dx(old.inode)) { retval = ext4_handle_dirty_dx_node(handle, - old_inode, - dir_bh); + old.inode, + old.dir_bh); } else { retval = ext4_handle_dirty_dirent_node(handle, - old_inode, dir_bh); + old.inode, old.dir_bh); } } else { - retval = ext4_mark_inode_dirty(handle, old_inode); + retval = ext4_mark_inode_dirty(handle, old.inode); } if (retval) { - ext4_std_error(old_dir->i_sb, retval); + ext4_std_error(old.dir->i_sb, retval); goto end_rename; } - ext4_dec_count(handle, old_dir); - if (new_inode) { + ext4_dec_count(handle, old.dir); + if (new.inode) { /* checked empty_dir above, can't have another parent, * ext4_dec_count() won't work for many-linked dirs */ - clear_nlink(new_inode); + clear_nlink(new.inode); } else { - ext4_inc_count(handle, new_dir); - ext4_update_dx_flag(new_dir); - ext4_mark_inode_dirty(handle, new_dir); + ext4_inc_count(handle, new.dir); + ext4_update_dx_flag(new.dir); + ext4_mark_inode_dirty(handle, new.dir); } } - ext4_mark_inode_dirty(handle, old_dir); - if (new_inode) { - ext4_mark_inode_dirty(handle, new_inode); - if (!new_inode->i_nlink) - ext4_orphan_add(handle, new_inode); + ext4_mark_inode_dirty(handle, old.dir); + if (new.inode) { + ext4_mark_inode_dirty(handle, new.inode); + if (!new.inode->i_nlink) + ext4_orphan_add(handle, new.inode); } retval = 0; end_rename: - brelse(dir_bh); - brelse(old_bh); - brelse(new_bh); + brelse(old.dir_bh); + brelse(old.bh); + brelse(new.bh); if (handle) ext4_journal_stop(handle); return retval; -- 1.8.1.4 ^ permalink raw reply related [flat|nested] 22+ messages in thread
* Re: [PATCH 08/11] ext4: rename: create ext4_renament structure for local vars 2013-11-20 13:01 ` [PATCH 08/11] ext4: rename: create ext4_renament structure for local vars Miklos Szeredi @ 2013-11-26 8:04 ` Jan Kara 0 siblings, 0 replies; 22+ messages in thread From: Jan Kara @ 2013-11-26 8:04 UTC (permalink / raw) To: Miklos Szeredi Cc: viro, torvalds, linux-fsdevel, linux-kernel, hch, akpm, dhowells, zab, jack, luto, mszeredi On Wed 20-11-13 14:01:49, Miklos Szeredi wrote: > From: Miklos Szeredi <mszeredi@suse.cz> > > Need to split up ext4_rename() into helpers but there are two many local ^^ too > variables involved, so create a new structure. This also, apparently, > makes the generated code size slightly smaller. > > Signed-off-by: Miklos Szeredi <mszeredi@suse.cz> > --- > fs/ext4/namei.c | 211 ++++++++++++++++++++++++++++++-------------------------- > 1 file changed, 114 insertions(+), 97 deletions(-) > > diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c > index e0129b6e74cf..c9e38bd30e8b 100644 > --- a/fs/ext4/namei.c > +++ b/fs/ext4/namei.c > @@ -3001,6 +3001,22 @@ static struct buffer_head *ext4_get_first_dir_block(handle_t *handle, > return ext4_get_first_inline_block(inode, parent_de, retval); > } > > +struct ext4_renament { > + struct inode *dir; > + struct dentry *dentry; > + struct inode *inode; > + > + /* entry for "dentry" */ > + struct buffer_head *bh; > + struct ext4_dir_entry_2 *de; > + int inlined; > + > + /* entry for parent block of inode if it's a directory */ Maybe "entry for '..' in inode if it is a directory"? It looks more comprehensible to me. > + struct buffer_head *dir_bh; > + struct ext4_dir_entry_2 *parent_de; > + int dir_inlined; > +}; > + > /* > * Anybody can rename anything with this: the permission checks are left to the > * higher-level routines. > @@ -3014,196 +3030,197 @@ static int ext4_rename(struct inode *old_dir, struct dentry *old_dentry, > unsigned int flags) > { > handle_t *handle = NULL; > - struct inode *old_inode, *new_inode; > - struct buffer_head *old_bh, *new_bh, *dir_bh; > - struct ext4_dir_entry_2 *old_de, *new_de; > + struct ext4_renament old = { > + .dir = old_dir, > + .dentry = old_dentry, > + }; > + struct ext4_renament new = { > + .dir = new_dir, > + .dentry = new_dentry, > + }; The code would look slightly more logical if we initialized .inode as well. Not sure if it is worth an extra patch or if you just fold it here... Otherwise the patch looks good. You can add: Reviewed-by: Jan Kara <jack@suse.cz> Honza -- Jan Kara <jack@suse.cz> SUSE Labs, CR ^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH 09/11] ext4: rename: move EMLINK check up 2013-11-20 13:01 [PATCH 00/11] cross rename v2 Miklos Szeredi ` (7 preceding siblings ...) 2013-11-20 13:01 ` [PATCH 08/11] ext4: rename: create ext4_renament structure for local vars Miklos Szeredi @ 2013-11-20 13:01 ` Miklos Szeredi 2013-11-20 13:01 ` [PATCH 10/11] ext4: rename: split out helper functions Miklos Szeredi 2013-11-20 13:01 ` [PATCH 11/11] ext4: add cross rename support Miklos Szeredi 10 siblings, 0 replies; 22+ messages in thread From: Miklos Szeredi @ 2013-11-20 13:01 UTC (permalink / raw) To: viro Cc: torvalds, linux-fsdevel, linux-kernel, hch, akpm, dhowells, zab, jack, luto, mszeredi From: Miklos Szeredi <mszeredi@suse.cz> Move checking i_nlink from after ext4_get_first_dir_block() to before. The check doesn't rely on the result of that function and the function only fails on fs corruption, so the order shouldn't matter. Signed-off-by: Miklos Szeredi <mszeredi@suse.cz> Reviewed-by: Jan Kara <jack@suse.cz> --- fs/ext4/namei.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c index c9e38bd30e8b..94583a4bd875 100644 --- a/fs/ext4/namei.c +++ b/fs/ext4/namei.c @@ -3089,6 +3089,10 @@ static int ext4_rename(struct inode *old_dir, struct dentry *old_dentry, retval = -ENOTEMPTY; if (!empty_dir(new.inode)) goto end_rename; + } else { + retval = -EMLINK; + if (new.dir != old.dir && EXT4_DIR_LINK_MAX(new.dir)) + goto end_rename; } retval = -EIO; old.dir_bh = ext4_get_first_dir_block(handle, old.inode, @@ -3098,10 +3102,6 @@ static int ext4_rename(struct inode *old_dir, struct dentry *old_dentry, goto end_rename; if (le32_to_cpu(old.parent_de->inode) != old.dir->i_ino) goto end_rename; - retval = -EMLINK; - if (!new.inode && new.dir != old.dir && - EXT4_DIR_LINK_MAX(new.dir)) - goto end_rename; BUFFER_TRACE(old.dir_bh, "get_write_access"); retval = ext4_journal_get_write_access(handle, old.dir_bh); if (retval) -- 1.8.1.4 ^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH 10/11] ext4: rename: split out helper functions 2013-11-20 13:01 [PATCH 00/11] cross rename v2 Miklos Szeredi ` (8 preceding siblings ...) 2013-11-20 13:01 ` [PATCH 09/11] ext4: rename: move EMLINK check up Miklos Szeredi @ 2013-11-20 13:01 ` Miklos Szeredi 2013-11-20 13:01 ` [PATCH 11/11] ext4: add cross rename support Miklos Szeredi 10 siblings, 0 replies; 22+ messages in thread From: Miklos Szeredi @ 2013-11-20 13:01 UTC (permalink / raw) To: viro Cc: torvalds, linux-fsdevel, linux-kernel, hch, akpm, dhowells, zab, jack, luto, mszeredi From: Miklos Szeredi <mszeredi@suse.cz> Cross rename (exchange source and dest) will need to call some of these helpers for both source and dest, while overwriting rename currently only calls them for one or the other. This also makes the code easier to follow. Signed-off-by: Miklos Szeredi <mszeredi@suse.cz> Reviewed-by: Jan Kara <jack@suse.cz> --- fs/ext4/namei.c | 199 +++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 126 insertions(+), 73 deletions(-) diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c index 94583a4bd875..d258b354b937 100644 --- a/fs/ext4/namei.c +++ b/fs/ext4/namei.c @@ -3017,6 +3017,125 @@ struct ext4_renament { int dir_inlined; }; +static int ext4_rename_dir_prepare(handle_t *handle, struct ext4_renament *ent) +{ + int retval; + + ent->dir_bh = ext4_get_first_dir_block(handle, ent->inode, + &retval, &ent->parent_de, + &ent->dir_inlined); + if (!ent->dir_bh) + return retval; + if (le32_to_cpu(ent->parent_de->inode) != ent->dir->i_ino) + return -EIO; + BUFFER_TRACE(ent->dir_bh, "get_write_access"); + return ext4_journal_get_write_access(handle, ent->dir_bh); +} + +static int ext4_rename_dir_finish(handle_t *handle, struct ext4_renament *ent, + unsigned dir_ino) +{ + int retval; + + ent->parent_de->inode = cpu_to_le32(dir_ino); + BUFFER_TRACE(ent->dir_bh, "call ext4_handle_dirty_metadata"); + if (!ent->dir_inlined) { + if (is_dx(ent->inode)) { + retval = ext4_handle_dirty_dx_node(handle, + ent->inode, + ent->dir_bh); + } else { + retval = ext4_handle_dirty_dirent_node(handle, + ent->inode, + ent->dir_bh); + } + } else { + retval = ext4_mark_inode_dirty(handle, ent->inode); + } + if (retval) { + ext4_std_error(ent->dir->i_sb, retval); + return retval; + } + return 0; +} + +static int ext4_setent(handle_t *handle, struct ext4_renament *ent, + unsigned ino, unsigned file_type) +{ + int retval; + + BUFFER_TRACE(ent->bh, "get write access"); + retval = ext4_journal_get_write_access(handle, ent->bh); + if (retval) + return retval; + ent->de->inode = cpu_to_le32(ino); + if (EXT4_HAS_INCOMPAT_FEATURE(ent->dir->i_sb, + EXT4_FEATURE_INCOMPAT_FILETYPE)) + ent->de->file_type = file_type; + ent->dir->i_version++; + ent->dir->i_ctime = ent->dir->i_mtime = + ext4_current_time(ent->dir); + ext4_mark_inode_dirty(handle, ent->dir); + BUFFER_TRACE(ent->bh, "call ext4_handle_dirty_metadata"); + if (!ent->inlined) { + retval = ext4_handle_dirty_dirent_node(handle, + ent->dir, ent->bh); + if (unlikely(retval)) { + ext4_std_error(ent->dir->i_sb, retval); + return retval; + } + } + brelse(ent->bh); + ent->bh = NULL; + + return 0; +} + +static int ext4_find_delete_entry(handle_t *handle, struct inode *dir, + const struct qstr *d_name) +{ + int retval = -ENOENT; + struct buffer_head *bh; + struct ext4_dir_entry_2 *de; + + bh = ext4_find_entry(dir, d_name, &de, NULL); + if (bh) { + retval = ext4_delete_entry(handle, dir, de, bh); + brelse(bh); + } + return retval; +} + +static void ext4_rename_delete(handle_t *handle, struct ext4_renament *ent) +{ + int retval; + /* + * ent->de could have moved from under us during htree split, so make + * sure that we are deleting the right entry. We might also be pointing + * to a stale entry in the unused part of ent->bh so just checking inum + * and the name isn't enough. + */ + if (le32_to_cpu(ent->de->inode) != ent->inode->i_ino || + ent->de->name_len != ent->dentry->d_name.len || + strncmp(ent->de->name, ent->dentry->d_name.name, + ent->de->name_len)) { + retval = ext4_find_delete_entry(handle, ent->dir, + &ent->dentry->d_name); + } else { + retval = ext4_delete_entry(handle, ent->dir, ent->de, ent->bh); + if (retval == -ENOENT) { + retval = ext4_find_delete_entry(handle, ent->dir, + &ent->dentry->d_name); + } + } + + if (retval) { + ext4_warning(ent->dir->i_sb, + "Deleting old file (%lu), %d, error=%d", + ent->dir->i_ino, ent->dir->i_nlink, retval); + } +} + /* * Anybody can rename anything with this: the permission checks are left to the * higher-level routines. @@ -3094,16 +3213,7 @@ static int ext4_rename(struct inode *old_dir, struct dentry *old_dentry, if (new.dir != old.dir && EXT4_DIR_LINK_MAX(new.dir)) goto end_rename; } - retval = -EIO; - old.dir_bh = ext4_get_first_dir_block(handle, old.inode, - &retval, &old.parent_de, - &old.dir_inlined); - if (!old.dir_bh) - goto end_rename; - if (le32_to_cpu(old.parent_de->inode) != old.dir->i_ino) - goto end_rename; - BUFFER_TRACE(old.dir_bh, "get_write_access"); - retval = ext4_journal_get_write_access(handle, old.dir_bh); + retval = ext4_rename_dir_prepare(handle, &old); if (retval) goto end_rename; } @@ -3112,29 +3222,10 @@ static int ext4_rename(struct inode *old_dir, struct dentry *old_dentry, if (retval) goto end_rename; } else { - BUFFER_TRACE(new.bh, "get write access"); - retval = ext4_journal_get_write_access(handle, new.bh); + retval = ext4_setent(handle, &new, + old.inode->i_ino, old.de->file_type); if (retval) goto end_rename; - new.de->inode = cpu_to_le32(old.inode->i_ino); - if (EXT4_HAS_INCOMPAT_FEATURE(new.dir->i_sb, - EXT4_FEATURE_INCOMPAT_FILETYPE)) - new.de->file_type = old.de->file_type; - new.dir->i_version++; - new.dir->i_ctime = new.dir->i_mtime = - ext4_current_time(new.dir); - ext4_mark_inode_dirty(handle, new.dir); - BUFFER_TRACE(new.bh, "call ext4_handle_dirty_metadata"); - if (!new.inlined) { - retval = ext4_handle_dirty_dirent_node(handle, - new.dir, new.bh); - if (unlikely(retval)) { - ext4_std_error(new.dir->i_sb, retval); - goto end_rename; - } - } - brelse(new.bh); - new.bh = NULL; } /* @@ -3147,31 +3238,7 @@ static int ext4_rename(struct inode *old_dir, struct dentry *old_dentry, /* * ok, that's it */ - if (le32_to_cpu(old.de->inode) != old.inode->i_ino || - old.de->name_len != old.dentry->d_name.len || - strncmp(old.de->name, old.dentry->d_name.name, old.de->name_len) || - (retval = ext4_delete_entry(handle, old.dir, - old.de, old.bh)) == -ENOENT) { - /* old.de could have moved from under us during htree split, so - * make sure that we are deleting the right entry. We might - * also be pointing to a stale entry in the unused part of - * old.bh so just checking inum and the name isn't enough. */ - struct buffer_head *old_bh2; - struct ext4_dir_entry_2 *old_de2; - - old_bh2 = ext4_find_entry(old.dir, &old.dentry->d_name, - &old_de2, NULL); - if (old_bh2) { - retval = ext4_delete_entry(handle, old.dir, - old_de2, old_bh2); - brelse(old_bh2); - } - } - if (retval) { - ext4_warning(old.dir->i_sb, - "Deleting old file (%lu), %d, error=%d", - old.dir->i_ino, old.dir->i_nlink, retval); - } + ext4_rename_delete(handle, &old); if (new.inode) { ext4_dec_count(handle, new.inode); @@ -3180,24 +3247,10 @@ static int ext4_rename(struct inode *old_dir, struct dentry *old_dentry, old.dir->i_ctime = old.dir->i_mtime = ext4_current_time(old.dir); ext4_update_dx_flag(old.dir); if (old.dir_bh) { - old.parent_de->inode = cpu_to_le32(new.dir->i_ino); - BUFFER_TRACE(old.dir_bh, "call ext4_handle_dirty_metadata"); - if (!old.dir_inlined) { - if (is_dx(old.inode)) { - retval = ext4_handle_dirty_dx_node(handle, - old.inode, - old.dir_bh); - } else { - retval = ext4_handle_dirty_dirent_node(handle, - old.inode, old.dir_bh); - } - } else { - retval = ext4_mark_inode_dirty(handle, old.inode); - } - if (retval) { - ext4_std_error(old.dir->i_sb, retval); + retval = ext4_rename_dir_finish(handle, &old, new.dir->i_ino); + if (retval) goto end_rename; - } + ext4_dec_count(handle, old.dir); if (new.inode) { /* checked empty_dir above, can't have another parent, -- 1.8.1.4 ^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH 11/11] ext4: add cross rename support 2013-11-20 13:01 [PATCH 00/11] cross rename v2 Miklos Szeredi ` (9 preceding siblings ...) 2013-11-20 13:01 ` [PATCH 10/11] ext4: rename: split out helper functions Miklos Szeredi @ 2013-11-20 13:01 ` Miklos Szeredi 2013-11-26 9:51 ` Jan Kara 10 siblings, 1 reply; 22+ messages in thread From: Miklos Szeredi @ 2013-11-20 13:01 UTC (permalink / raw) To: viro Cc: torvalds, linux-fsdevel, linux-kernel, hch, akpm, dhowells, zab, jack, luto, mszeredi From: Miklos Szeredi <mszeredi@suse.cz> Implement RENAME_EXCHANGE flag in renameat2 syscall. Signed-off-by: Miklos Szeredi <mszeredi@suse.cz> --- fs/ext4/namei.c | 97 ++++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 69 insertions(+), 28 deletions(-) diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c index d258b354b937..5307e482f403 100644 --- a/fs/ext4/namei.c +++ b/fs/ext4/namei.c @@ -3159,7 +3159,7 @@ static int ext4_rename(struct inode *old_dir, struct dentry *old_dentry, }; int retval; - if (flags & ~RENAME_NOREPLACE) + if (flags & ~(RENAME_NOREPLACE | RENAME_EXCHANGE)) return -EOPNOTSUPP; dquot_initialize(old.dir); @@ -3167,10 +3167,11 @@ static int ext4_rename(struct inode *old_dir, struct dentry *old_dentry, /* Initialize quotas before so that eventual writes go * in separate transaction */ - if (new.dentry->d_inode) + if (!(flags & RENAME_EXCHANGE) && new.dentry->d_inode) dquot_initialize(new.dentry->d_inode); - old.bh = ext4_find_entry(old.dir, &old.dentry->d_name, &old.de, NULL); + old.bh = ext4_find_entry(old.dir, &old.dentry->d_name, + &old.de, &old.inlined); /* * Check for inode number is _not_ due to possible IO errors. * We might rmdir the source, keep it as pwd of some process @@ -3185,18 +3186,22 @@ static int ext4_rename(struct inode *old_dir, struct dentry *old_dentry, new.inode = new.dentry->d_inode; new.bh = ext4_find_entry(new.dir, &new.dentry->d_name, &new.de, &new.inlined); - if (new.bh) { - if (!new.inode) { - brelse(new.bh); - new.bh = NULL; + if (!(flags & RENAME_EXCHANGE)) { + if (new.bh) { + if (!new.inode) { + brelse(new.bh); + new.bh = NULL; + } } + if (new.inode && !test_opt(new.dir->i_sb, NO_AUTO_DA_ALLOC)) + ext4_alloc_da_blocks(old.inode); + } else if (!new.bh || le32_to_cpu(new.de->inode) != new.inode->i_ino) { + goto end_rename; } - if (new.inode && !test_opt(new.dir->i_sb, NO_AUTO_DA_ALLOC)) - ext4_alloc_da_blocks(old.inode); handle = ext4_journal_start(old.dir, EXT4_HT_DIR, (2 * EXT4_DATA_TRANS_BLOCKS(old.dir->i_sb) + - EXT4_INDEX_EXTRA_TRANS_BLOCKS + 2)); + 2 * EXT4_INDEX_EXTRA_TRANS_BLOCKS + 2)); if (IS_ERR(handle)) return PTR_ERR(handle); @@ -3204,11 +3209,12 @@ static int ext4_rename(struct inode *old_dir, struct dentry *old_dentry, ext4_handle_sync(handle); if (S_ISDIR(old.inode->i_mode)) { - if (new.inode) { + if (!(flags & RENAME_EXCHANGE) && new.inode) { retval = -ENOTEMPTY; if (!empty_dir(new.inode)) goto end_rename; - } else { + } + if (!new.inode || !S_ISDIR(new.inode->i_mode)) { retval = -EMLINK; if (new.dir != old.dir && EXT4_DIR_LINK_MAX(new.dir)) goto end_rename; @@ -3217,15 +3223,34 @@ static int ext4_rename(struct inode *old_dir, struct dentry *old_dentry, if (retval) goto end_rename; } + if ((flags & RENAME_EXCHANGE) && S_ISDIR(new.inode->i_mode)) { + if (!S_ISDIR(old.inode->i_mode)) { + retval = -EMLINK; + if (new.dir != old.dir && EXT4_DIR_LINK_MAX(old.dir)) + goto end_rename; + } + retval = ext4_rename_dir_prepare(handle, &new); + if (retval) + goto end_rename; + } + if (!new.bh) { retval = ext4_add_entry(handle, new.dentry, old.inode); if (retval) goto end_rename; } else { + u8 new_file_type = new.de->file_type; retval = ext4_setent(handle, &new, old.inode->i_ino, old.de->file_type); if (retval) goto end_rename; + + if (flags & RENAME_EXCHANGE) { + retval = ext4_setent(handle, &old, + new.inode->i_ino, new_file_type); + if (retval) + goto end_rename; + } } /* @@ -3235,35 +3260,51 @@ static int ext4_rename(struct inode *old_dir, struct dentry *old_dentry, old.inode->i_ctime = ext4_current_time(old.inode); ext4_mark_inode_dirty(handle, old.inode); - /* - * ok, that's it - */ - ext4_rename_delete(handle, &old); + if (!(flags & RENAME_EXCHANGE)) { + /* + * ok, that's it + */ + ext4_rename_delete(handle, &old); - if (new.inode) { - ext4_dec_count(handle, new.inode); - new.inode->i_ctime = ext4_current_time(new.inode); + old.dir->i_ctime = old.dir->i_mtime = ext4_current_time(old.dir); + ext4_update_dx_flag(old.dir); } - old.dir->i_ctime = old.dir->i_mtime = ext4_current_time(old.dir); - ext4_update_dx_flag(old.dir); + /* S_ISDIR(old.inode->i_mode */ if (old.dir_bh) { retval = ext4_rename_dir_finish(handle, &old, new.dir->i_ino); if (retval) goto end_rename; - ext4_dec_count(handle, old.dir); - if (new.inode) { - /* checked empty_dir above, can't have another parent, - * ext4_dec_count() won't work for many-linked dirs */ - clear_nlink(new.inode); - } else { + if (!(flags & RENAME_EXCHANGE) || !S_ISDIR(new.inode->i_mode)) + ext4_dec_count(handle, old.dir); + + if (!new.inode || !S_ISDIR(new.inode->i_mode)) { ext4_inc_count(handle, new.dir); ext4_update_dx_flag(new.dir); ext4_mark_inode_dirty(handle, new.dir); } } + /* (flags & RENAME_EXCHANGE) && S_ISDIR(new.inode->i_mode */ + if (new.dir_bh) { + retval = ext4_rename_dir_finish(handle, &new, old.dir->i_ino); + if (retval) + goto end_rename; + + if (!S_ISDIR(old.inode->i_mode)) { + ext4_dec_count(handle, new.dir); + ext4_inc_count(handle, old.dir); + ext4_mark_inode_dirty(handle, new.dir); + } + } ext4_mark_inode_dirty(handle, old.dir); - if (new.inode) { + if (!(flags & RENAME_EXCHANGE) && new.inode) { + ext4_dec_count(handle, new.inode); + new.inode->i_ctime = ext4_current_time(new.inode); + if (S_ISDIR(old.inode->i_mode)) { + /* checked empty_dir above, can't have another parent, + * ext4_dec_count() won't work for many-linked dirs */ + clear_nlink(new.inode); + } ext4_mark_inode_dirty(handle, new.inode); if (!new.inode->i_nlink) ext4_orphan_add(handle, new.inode); -- 1.8.1.4 ^ permalink raw reply related [flat|nested] 22+ messages in thread
* Re: [PATCH 11/11] ext4: add cross rename support 2013-11-20 13:01 ` [PATCH 11/11] ext4: add cross rename support Miklos Szeredi @ 2013-11-26 9:51 ` Jan Kara 2013-11-26 13:47 ` Miklos Szeredi 0 siblings, 1 reply; 22+ messages in thread From: Jan Kara @ 2013-11-26 9:51 UTC (permalink / raw) To: Miklos Szeredi Cc: viro, torvalds, linux-fsdevel, linux-kernel, hch, akpm, dhowells, zab, jack, luto, mszeredi On Wed 20-11-13 14:01:52, Miklos Szeredi wrote: > From: Miklos Szeredi <mszeredi@suse.cz> > > Implement RENAME_EXCHANGE flag in renameat2 syscall. > > Signed-off-by: Miklos Szeredi <mszeredi@suse.cz> > --- > fs/ext4/namei.c | 97 ++++++++++++++++++++++++++++++++++++++++----------------- > 1 file changed, 69 insertions(+), 28 deletions(-) > > diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c > index d258b354b937..5307e482f403 100644 > --- a/fs/ext4/namei.c > +++ b/fs/ext4/namei.c ... > - old.dir->i_ctime = old.dir->i_mtime = ext4_current_time(old.dir); > - ext4_update_dx_flag(old.dir); > + /* S_ISDIR(old.inode->i_mode */ > if (old.dir_bh) { > retval = ext4_rename_dir_finish(handle, &old, new.dir->i_ino); > if (retval) > goto end_rename; > > - ext4_dec_count(handle, old.dir); > - if (new.inode) { > - /* checked empty_dir above, can't have another parent, > - * ext4_dec_count() won't work for many-linked dirs */ > - clear_nlink(new.inode); > - } else { > + if (!(flags & RENAME_EXCHANGE) || !S_ISDIR(new.inode->i_mode)) > + ext4_dec_count(handle, old.dir); > + > + if (!new.inode || !S_ISDIR(new.inode->i_mode)) { > ext4_inc_count(handle, new.dir); > ext4_update_dx_flag(new.dir); > ext4_mark_inode_dirty(handle, new.dir); > } > } > + /* (flags & RENAME_EXCHANGE) && S_ISDIR(new.inode->i_mode */ > + if (new.dir_bh) { > + retval = ext4_rename_dir_finish(handle, &new, old.dir->i_ino); > + if (retval) > + goto end_rename; > + > + if (!S_ISDIR(old.inode->i_mode)) { > + ext4_dec_count(handle, new.dir); > + ext4_inc_count(handle, old.dir); > + ext4_mark_inode_dirty(handle, new.dir); > + } > + } > ext4_mark_inode_dirty(handle, old.dir); > - if (new.inode) { > + if (!(flags & RENAME_EXCHANGE) && new.inode) { > + ext4_dec_count(handle, new.inode); > + new.inode->i_ctime = ext4_current_time(new.inode); > + if (S_ISDIR(old.inode->i_mode)) { > + /* checked empty_dir above, can't have another parent, > + * ext4_dec_count() won't work for many-linked dirs */ > + clear_nlink(new.inode); > + } This hunk looks strange. Why do you check S_ISDIR(old.inode->i_mode)? I'd presume we need to clear nlink if new.inode is a directory... > ext4_mark_inode_dirty(handle, new.inode); > if (!new.inode->i_nlink) > ext4_orphan_add(handle, new.inode); Generally, I'm a bit unhappy about the number of various RENAME_EXCHANGE checks and the asymmetry between new & old which now shouldn't needed (that much). Especially the link count handling looks more complex than it should be. I'd hope that it should be possible to "delete new.inode iff !RENAME_EXCHANGE" and then the rest shouldn't need to care about RENAME_EXCHANGE at all and treat old & new completely symmetrically... Now I realize this isn't that easy because we want to do all error checks first before doing any changes on disk but still I hope some improvement can be made (maybe just zero out new.inode in our 'new' ext4_renament to allow for code to be symmetric and delete it on disk only when ext4_rename() is finishing). If the above won't be workable, we might at least make the link count handling more obvious by computing "old_link_cnt_update, new_link_cnt_update" - how link counts of parent dirs should be updated (-1, 0, +1) and then do the checks and updates based on this in one place. Honza -- Jan Kara <jack@suse.cz> SUSE Labs, CR ^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH 11/11] ext4: add cross rename support 2013-11-26 9:51 ` Jan Kara @ 2013-11-26 13:47 ` Miklos Szeredi 0 siblings, 0 replies; 22+ messages in thread From: Miklos Szeredi @ 2013-11-26 13:47 UTC (permalink / raw) To: Jan Kara Cc: viro, torvalds, linux-fsdevel, linux-kernel, hch, akpm, dhowells, zab, luto, mszeredi On Tue, Nov 26, 2013 at 10:51:25AM +0100, Jan Kara wrote: > On Wed 20-11-13 14:01:52, Miklos Szeredi wrote: > > From: Miklos Szeredi <mszeredi@suse.cz> > > > > Implement RENAME_EXCHANGE flag in renameat2 syscall. > > > > Signed-off-by: Miklos Szeredi <mszeredi@suse.cz> > > --- > > fs/ext4/namei.c | 97 ++++++++++++++++++++++++++++++++++++++++----------------- > > 1 file changed, 69 insertions(+), 28 deletions(-) > > > > diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c > > index d258b354b937..5307e482f403 100644 > > --- a/fs/ext4/namei.c > > +++ b/fs/ext4/namei.c > ... > > - old.dir->i_ctime = old.dir->i_mtime = ext4_current_time(old.dir); > > - ext4_update_dx_flag(old.dir); > > + /* S_ISDIR(old.inode->i_mode */ > > if (old.dir_bh) { > > retval = ext4_rename_dir_finish(handle, &old, new.dir->i_ino); > > if (retval) > > goto end_rename; > > > > - ext4_dec_count(handle, old.dir); > > - if (new.inode) { > > - /* checked empty_dir above, can't have another parent, > > - * ext4_dec_count() won't work for many-linked dirs */ > > - clear_nlink(new.inode); > > - } else { > > + if (!(flags & RENAME_EXCHANGE) || !S_ISDIR(new.inode->i_mode)) > > + ext4_dec_count(handle, old.dir); > > + > > + if (!new.inode || !S_ISDIR(new.inode->i_mode)) { > > ext4_inc_count(handle, new.dir); > > ext4_update_dx_flag(new.dir); > > ext4_mark_inode_dirty(handle, new.dir); > > } > > } > > + /* (flags & RENAME_EXCHANGE) && S_ISDIR(new.inode->i_mode */ > > + if (new.dir_bh) { > > + retval = ext4_rename_dir_finish(handle, &new, old.dir->i_ino); > > + if (retval) > > + goto end_rename; > > + > > + if (!S_ISDIR(old.inode->i_mode)) { > > + ext4_dec_count(handle, new.dir); > > + ext4_inc_count(handle, old.dir); > > + ext4_mark_inode_dirty(handle, new.dir); > > + } > > + } > > ext4_mark_inode_dirty(handle, old.dir); > > - if (new.inode) { > > + if (!(flags & RENAME_EXCHANGE) && new.inode) { > > + ext4_dec_count(handle, new.inode); > > + new.inode->i_ctime = ext4_current_time(new.inode); > > + if (S_ISDIR(old.inode->i_mode)) { > > + /* checked empty_dir above, can't have another parent, > > + * ext4_dec_count() won't work for many-linked dirs */ > > + clear_nlink(new.inode); > > + } > This hunk looks strange. Why do you check S_ISDIR(old.inode->i_mode)? I'd > presume we need to clear nlink if new.inode is a directory... It's confusing, that's for sure. I think it's correct, since S_ISDIR(old) is equivalent to S_ISDIR(new) if not cross-renaming, but that's not a lot of consolation to someone trying to understand the code. > > > ext4_mark_inode_dirty(handle, new.inode); > > if (!new.inode->i_nlink) > > ext4_orphan_add(handle, new.inode); > Generally, I'm a bit unhappy about the number of various RENAME_EXCHANGE > checks and the asymmetry between new & old which now shouldn't needed (that > much). Especially the link count handling looks more complex than it should > be. > > I'd hope that it should be possible to "delete new.inode iff > !RENAME_EXCHANGE" and then the rest shouldn't need to care about > RENAME_EXCHANGE at all and treat old & new completely symmetrically... Now I > realize this isn't that easy because we want to do all error checks first > before doing any changes on disk but still I hope some improvement can be > made (maybe just zero out new.inode in our 'new' ext4_renament to allow for > code to be symmetric and delete it on disk only when ext4_rename() is > finishing). > > If the above won't be workable, we might at least make the link count > handling more obvious by computing "old_link_cnt_update, > new_link_cnt_update" - how link counts of parent dirs should be updated > (-1, 0, +1) and then do the checks and updates based on this in one place. Okay, will try to clean this up. I agree that it became a bit too complicated. Thanks, Miklos ^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH 00/11] cross rename v3 @ 2014-01-08 22:10 Miklos Szeredi 2014-01-08 22:10 ` [PATCH 08/11] ext4: rename: create ext4_renament structure for local vars Miklos Szeredi 0 siblings, 1 reply; 22+ messages in thread From: Miklos Szeredi @ 2014-01-08 22:10 UTC (permalink / raw) To: viro Cc: torvalds, linux-fsdevel, linux-kernel, hch, akpm, dhowells, zab, jack, luto, mszeredi [Only cosmetic changes from v2 based on Jan Kara's review] This series adds a new syscall, renameat2(), which is the same as renameat() but with a flags argument. The purpose of extending rename is to add cross-rename, a symmetric variant of rename, which exchanges the two files. This allows interesting things, which were not possible before, for example atomically replacing a directory tree with a symlink, etc... This also allows overlayfs and friends to operate on whiteouts atomically. Andy Lutomirski also suggested a "noreplace" flag, which disables the overwriting behavior of rename. These two flags, RENAME_EXCHANGE and RENAME_NOREPLACE are only implemented for ext4 as an example and for testing. Implementing RENAME_NOREPLACE for other local (disk or ram based) filesystems is trivial: just don't fail with -EOPNOTSUPP, the rest is done by the VFS. Network filesystems need special treatment to avoid creation races. Please consider for -next (3.14). Git tree is here: git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/vfs.git cross-rename Thanks, Miklos --- Miklos Szeredi (11): vfs: add d_is_dir() vfs: rename: move d_move() up vfs: rename: use common code for dir and non-dir vfs: add renameat2 syscall vfs: add RENAME_NOREPLACE flag security: add flags to rename hooks vfs: add cross-rename ext4: rename: create ext4_renament structure for local vars ext4: rename: move EMLINK check up ext4: rename: split out helper functions ext4: add cross rename support --- Documentation/filesystems/Locking | 2 +- Documentation/filesystems/vfs.txt | 4 +- arch/x86/syscalls/syscall_64.tbl | 1 + .../lustre/lustre/include/linux/lustre_compat25.h | 4 +- drivers/staging/lustre/lustre/llite/namei.c | 7 +- drivers/staging/lustre/lustre/lvfs/lvfs_linux.c | 2 +- fs/9p/v9fs.h | 3 +- fs/9p/vfs_inode.c | 7 +- fs/affs/affs.h | 3 +- fs/affs/namei.c | 6 +- fs/afs/dir.c | 9 +- fs/bad_inode.c | 3 +- fs/bfs/dir.c | 6 +- fs/btrfs/inode.c | 6 +- fs/cachefiles/namei.c | 4 +- fs/ceph/dir.c | 6 +- fs/cifs/cifsfs.h | 2 +- fs/cifs/inode.c | 6 +- fs/coda/dir.c | 11 +- fs/dcache.c | 46 ++- fs/debugfs/inode.c | 2 +- fs/ecryptfs/inode.c | 8 +- fs/exofs/namei.c | 6 +- fs/ext2/namei.c | 8 +- fs/ext3/namei.c | 8 +- fs/ext4/namei.c | 395 ++++++++++++++------- fs/f2fs/namei.c | 6 +- fs/fat/namei_msdos.c | 6 +- fs/fat/namei_vfat.c | 6 +- fs/fuse/dir.c | 9 +- fs/gfs2/inode.c | 6 +- fs/hfs/dir.c | 6 +- fs/hfsplus/dir.c | 6 +- fs/hostfs/hostfs_kern.c | 8 +- fs/hpfs/namei.c | 6 +- fs/jffs2/dir.c | 8 +- fs/jfs/namei.c | 5 +- fs/libfs.c | 6 +- fs/logfs/dir.c | 6 +- fs/minix/namei.c | 8 +- fs/namei.c | 316 +++++++++-------- fs/ncpfs/dir.c | 8 +- fs/nfs/dir.c | 6 +- fs/nfs/internal.h | 3 +- fs/nfsd/vfs.c | 2 +- fs/nilfs2/namei.c | 6 +- fs/ocfs2/namei.c | 6 +- fs/omfs/dir.c | 6 +- fs/reiserfs/namei.c | 6 +- fs/sysv/namei.c | 8 +- fs/ubifs/dir.c | 6 +- fs/udf/namei.c | 6 +- fs/ufs/namei.c | 6 +- fs/xfs/xfs_iops.c | 6 +- include/linux/dcache.h | 8 +- include/linux/fs.h | 6 +- include/linux/security.h | 12 +- include/uapi/linux/fs.h | 3 + kernel/cgroup.c | 8 +- mm/shmem.c | 5 +- security/security.c | 22 +- 61 files changed, 746 insertions(+), 366 deletions(-) ^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH 08/11] ext4: rename: create ext4_renament structure for local vars 2014-01-08 22:10 [PATCH 00/11] cross rename v3 Miklos Szeredi @ 2014-01-08 22:10 ` Miklos Szeredi 0 siblings, 0 replies; 22+ messages in thread From: Miklos Szeredi @ 2014-01-08 22:10 UTC (permalink / raw) To: viro Cc: torvalds, linux-fsdevel, linux-kernel, hch, akpm, dhowells, zab, jack, luto, mszeredi From: Miklos Szeredi <mszeredi@suse.cz> Need to split up ext4_rename() into helpers but there are too many local variables involved, so create a new structure. This also, apparently, makes the generated code size slightly smaller. Signed-off-by: Miklos Szeredi <mszeredi@suse.cz> Reviewed-by: Jan Kara <jack@suse.cz> --- fs/ext4/namei.c | 211 ++++++++++++++++++++++++++++++-------------------------- 1 file changed, 114 insertions(+), 97 deletions(-) diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c index e0129b6e74cf..bb3e2b4614dc 100644 --- a/fs/ext4/namei.c +++ b/fs/ext4/namei.c @@ -3001,6 +3001,22 @@ static struct buffer_head *ext4_get_first_dir_block(handle_t *handle, return ext4_get_first_inline_block(inode, parent_de, retval); } +struct ext4_renament { + struct inode *dir; + struct dentry *dentry; + struct inode *inode; + + /* entry for "dentry" */ + struct buffer_head *bh; + struct ext4_dir_entry_2 *de; + int inlined; + + /* entry for ".." in inode if it's a directory */ + struct buffer_head *dir_bh; + struct ext4_dir_entry_2 *parent_de; + int dir_inlined; +}; + /* * Anybody can rename anything with this: the permission checks are left to the * higher-level routines. @@ -3014,196 +3030,197 @@ static int ext4_rename(struct inode *old_dir, struct dentry *old_dentry, unsigned int flags) { handle_t *handle = NULL; - struct inode *old_inode, *new_inode; - struct buffer_head *old_bh, *new_bh, *dir_bh; - struct ext4_dir_entry_2 *old_de, *new_de; + struct ext4_renament old = { + .dir = old_dir, + .dentry = old_dentry, + .inode = old_dentry->d_inode, + }; + struct ext4_renament new = { + .dir = new_dir, + .dentry = new_dentry, + .inode = new_dentry->d_inode, + }; int retval; - int inlined = 0, new_inlined = 0; - struct ext4_dir_entry_2 *parent_de; if (flags & ~RENAME_NOREPLACE) return -EOPNOTSUPP; - dquot_initialize(old_dir); - dquot_initialize(new_dir); - - old_bh = new_bh = dir_bh = NULL; + dquot_initialize(old.dir); + dquot_initialize(new.dir); /* Initialize quotas before so that eventual writes go * in separate transaction */ - if (new_dentry->d_inode) - dquot_initialize(new_dentry->d_inode); + if (new.inode) + dquot_initialize(new.inode); - old_bh = ext4_find_entry(old_dir, &old_dentry->d_name, &old_de, NULL); + old.bh = ext4_find_entry(old.dir, &old.dentry->d_name, &old.de, NULL); /* * Check for inode number is _not_ due to possible IO errors. * We might rmdir the source, keep it as pwd of some process * and merrily kill the link to whatever was created under the * same name. Goodbye sticky bit ;-< */ - old_inode = old_dentry->d_inode; retval = -ENOENT; - if (!old_bh || le32_to_cpu(old_de->inode) != old_inode->i_ino) + if (!old.bh || le32_to_cpu(old.de->inode) != old.inode->i_ino) goto end_rename; - new_inode = new_dentry->d_inode; - new_bh = ext4_find_entry(new_dir, &new_dentry->d_name, - &new_de, &new_inlined); - if (new_bh) { - if (!new_inode) { - brelse(new_bh); - new_bh = NULL; + new.bh = ext4_find_entry(new.dir, &new.dentry->d_name, + &new.de, &new.inlined); + if (new.bh) { + if (!new.inode) { + brelse(new.bh); + new.bh = NULL; } } - if (new_inode && !test_opt(new_dir->i_sb, NO_AUTO_DA_ALLOC)) - ext4_alloc_da_blocks(old_inode); + if (new.inode && !test_opt(new.dir->i_sb, NO_AUTO_DA_ALLOC)) + ext4_alloc_da_blocks(old.inode); - handle = ext4_journal_start(old_dir, EXT4_HT_DIR, - (2 * EXT4_DATA_TRANS_BLOCKS(old_dir->i_sb) + + handle = ext4_journal_start(old.dir, EXT4_HT_DIR, + (2 * EXT4_DATA_TRANS_BLOCKS(old.dir->i_sb) + EXT4_INDEX_EXTRA_TRANS_BLOCKS + 2)); if (IS_ERR(handle)) return PTR_ERR(handle); - if (IS_DIRSYNC(old_dir) || IS_DIRSYNC(new_dir)) + if (IS_DIRSYNC(old.dir) || IS_DIRSYNC(new.dir)) ext4_handle_sync(handle); - if (S_ISDIR(old_inode->i_mode)) { - if (new_inode) { + if (S_ISDIR(old.inode->i_mode)) { + if (new.inode) { retval = -ENOTEMPTY; - if (!empty_dir(new_inode)) + if (!empty_dir(new.inode)) goto end_rename; } retval = -EIO; - dir_bh = ext4_get_first_dir_block(handle, old_inode, - &retval, &parent_de, - &inlined); - if (!dir_bh) + old.dir_bh = ext4_get_first_dir_block(handle, old.inode, + &retval, &old.parent_de, + &old.dir_inlined); + if (!old.dir_bh) goto end_rename; - if (le32_to_cpu(parent_de->inode) != old_dir->i_ino) + if (le32_to_cpu(old.parent_de->inode) != old.dir->i_ino) goto end_rename; retval = -EMLINK; - if (!new_inode && new_dir != old_dir && - EXT4_DIR_LINK_MAX(new_dir)) + if (!new.inode && new.dir != old.dir && + EXT4_DIR_LINK_MAX(new.dir)) goto end_rename; - BUFFER_TRACE(dir_bh, "get_write_access"); - retval = ext4_journal_get_write_access(handle, dir_bh); + BUFFER_TRACE(old.dir_bh, "get_write_access"); + retval = ext4_journal_get_write_access(handle, old.dir_bh); if (retval) goto end_rename; } - if (!new_bh) { - retval = ext4_add_entry(handle, new_dentry, old_inode); + if (!new.bh) { + retval = ext4_add_entry(handle, new.dentry, old.inode); if (retval) goto end_rename; } else { - BUFFER_TRACE(new_bh, "get write access"); - retval = ext4_journal_get_write_access(handle, new_bh); + BUFFER_TRACE(new.bh, "get write access"); + retval = ext4_journal_get_write_access(handle, new.bh); if (retval) goto end_rename; - new_de->inode = cpu_to_le32(old_inode->i_ino); - if (EXT4_HAS_INCOMPAT_FEATURE(new_dir->i_sb, + new.de->inode = cpu_to_le32(old.inode->i_ino); + if (EXT4_HAS_INCOMPAT_FEATURE(new.dir->i_sb, EXT4_FEATURE_INCOMPAT_FILETYPE)) - new_de->file_type = old_de->file_type; - new_dir->i_version++; - new_dir->i_ctime = new_dir->i_mtime = - ext4_current_time(new_dir); - ext4_mark_inode_dirty(handle, new_dir); - BUFFER_TRACE(new_bh, "call ext4_handle_dirty_metadata"); - if (!new_inlined) { + new.de->file_type = old.de->file_type; + new.dir->i_version++; + new.dir->i_ctime = new.dir->i_mtime = + ext4_current_time(new.dir); + ext4_mark_inode_dirty(handle, new.dir); + BUFFER_TRACE(new.bh, "call ext4_handle_dirty_metadata"); + if (!new.inlined) { retval = ext4_handle_dirty_dirent_node(handle, - new_dir, new_bh); + new.dir, new.bh); if (unlikely(retval)) { - ext4_std_error(new_dir->i_sb, retval); + ext4_std_error(new.dir->i_sb, retval); goto end_rename; } } - brelse(new_bh); - new_bh = NULL; + brelse(new.bh); + new.bh = NULL; } /* * Like most other Unix systems, set the ctime for inodes on a * rename. */ - old_inode->i_ctime = ext4_current_time(old_inode); - ext4_mark_inode_dirty(handle, old_inode); + old.inode->i_ctime = ext4_current_time(old.inode); + ext4_mark_inode_dirty(handle, old.inode); /* * ok, that's it */ - if (le32_to_cpu(old_de->inode) != old_inode->i_ino || - old_de->name_len != old_dentry->d_name.len || - strncmp(old_de->name, old_dentry->d_name.name, old_de->name_len) || - (retval = ext4_delete_entry(handle, old_dir, - old_de, old_bh)) == -ENOENT) { - /* old_de could have moved from under us during htree split, so + if (le32_to_cpu(old.de->inode) != old.inode->i_ino || + old.de->name_len != old.dentry->d_name.len || + strncmp(old.de->name, old.dentry->d_name.name, old.de->name_len) || + (retval = ext4_delete_entry(handle, old.dir, + old.de, old.bh)) == -ENOENT) { + /* old.de could have moved from under us during htree split, so * make sure that we are deleting the right entry. We might * also be pointing to a stale entry in the unused part of - * old_bh so just checking inum and the name isn't enough. */ + * old.bh so just checking inum and the name isn't enough. */ struct buffer_head *old_bh2; struct ext4_dir_entry_2 *old_de2; - old_bh2 = ext4_find_entry(old_dir, &old_dentry->d_name, + old_bh2 = ext4_find_entry(old.dir, &old.dentry->d_name, &old_de2, NULL); if (old_bh2) { - retval = ext4_delete_entry(handle, old_dir, + retval = ext4_delete_entry(handle, old.dir, old_de2, old_bh2); brelse(old_bh2); } } if (retval) { - ext4_warning(old_dir->i_sb, + ext4_warning(old.dir->i_sb, "Deleting old file (%lu), %d, error=%d", - old_dir->i_ino, old_dir->i_nlink, retval); + old.dir->i_ino, old.dir->i_nlink, retval); } - if (new_inode) { - ext4_dec_count(handle, new_inode); - new_inode->i_ctime = ext4_current_time(new_inode); + if (new.inode) { + ext4_dec_count(handle, new.inode); + new.inode->i_ctime = ext4_current_time(new.inode); } - old_dir->i_ctime = old_dir->i_mtime = ext4_current_time(old_dir); - ext4_update_dx_flag(old_dir); - if (dir_bh) { - parent_de->inode = cpu_to_le32(new_dir->i_ino); - BUFFER_TRACE(dir_bh, "call ext4_handle_dirty_metadata"); - if (!inlined) { - if (is_dx(old_inode)) { + old.dir->i_ctime = old.dir->i_mtime = ext4_current_time(old.dir); + ext4_update_dx_flag(old.dir); + if (old.dir_bh) { + old.parent_de->inode = cpu_to_le32(new.dir->i_ino); + BUFFER_TRACE(old.dir_bh, "call ext4_handle_dirty_metadata"); + if (!old.dir_inlined) { + if (is_dx(old.inode)) { retval = ext4_handle_dirty_dx_node(handle, - old_inode, - dir_bh); + old.inode, + old.dir_bh); } else { retval = ext4_handle_dirty_dirent_node(handle, - old_inode, dir_bh); + old.inode, old.dir_bh); } } else { - retval = ext4_mark_inode_dirty(handle, old_inode); + retval = ext4_mark_inode_dirty(handle, old.inode); } if (retval) { - ext4_std_error(old_dir->i_sb, retval); + ext4_std_error(old.dir->i_sb, retval); goto end_rename; } - ext4_dec_count(handle, old_dir); - if (new_inode) { + ext4_dec_count(handle, old.dir); + if (new.inode) { /* checked empty_dir above, can't have another parent, * ext4_dec_count() won't work for many-linked dirs */ - clear_nlink(new_inode); + clear_nlink(new.inode); } else { - ext4_inc_count(handle, new_dir); - ext4_update_dx_flag(new_dir); - ext4_mark_inode_dirty(handle, new_dir); + ext4_inc_count(handle, new.dir); + ext4_update_dx_flag(new.dir); + ext4_mark_inode_dirty(handle, new.dir); } } - ext4_mark_inode_dirty(handle, old_dir); - if (new_inode) { - ext4_mark_inode_dirty(handle, new_inode); - if (!new_inode->i_nlink) - ext4_orphan_add(handle, new_inode); + ext4_mark_inode_dirty(handle, old.dir); + if (new.inode) { + ext4_mark_inode_dirty(handle, new.inode); + if (!new.inode->i_nlink) + ext4_orphan_add(handle, new.inode); } retval = 0; end_rename: - brelse(dir_bh); - brelse(old_bh); - brelse(new_bh); + brelse(old.dir_bh); + brelse(old.bh); + brelse(new.bh); if (handle) ext4_journal_stop(handle); return retval; -- 1.8.1.4 ^ permalink raw reply related [flat|nested] 22+ messages in thread
end of thread, other threads:[~2014-01-08 22:10 UTC | newest] Thread overview: 22+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2013-11-20 13:01 [PATCH 00/11] cross rename v2 Miklos Szeredi 2013-11-20 13:01 ` [PATCH 01/11] vfs: add d_is_dir() Miklos Szeredi 2013-11-20 13:01 ` [PATCH 02/11] vfs: rename: move d_move() up Miklos Szeredi 2013-11-20 13:01 ` [PATCH 03/11] vfs: rename: use common code for dir and non-dir Miklos Szeredi 2013-11-20 13:01 ` [PATCH 04/11] vfs: add renameat2 syscall Miklos Szeredi 2013-11-20 16:34 ` Andy Lutomirski 2013-11-21 4:10 ` Stephen Rothwell 2013-11-21 12:37 ` Miklos Szeredi 2013-11-20 13:01 ` [PATCH 05/11] vfs: add RENAME_NOREPLACE flag Miklos Szeredi 2013-11-20 13:01 ` [PATCH 06/11] security: add flags to rename hooks Miklos Szeredi 2013-11-20 13:01 ` [PATCH 07/11] vfs: add cross-rename Miklos Szeredi 2013-11-20 16:39 ` Andy Lutomirski 2013-11-20 16:44 ` Miklos Szeredi 2013-11-20 16:51 ` Andy Lutomirski 2013-11-20 13:01 ` [PATCH 08/11] ext4: rename: create ext4_renament structure for local vars Miklos Szeredi 2013-11-26 8:04 ` Jan Kara 2013-11-20 13:01 ` [PATCH 09/11] ext4: rename: move EMLINK check up Miklos Szeredi 2013-11-20 13:01 ` [PATCH 10/11] ext4: rename: split out helper functions Miklos Szeredi 2013-11-20 13:01 ` [PATCH 11/11] ext4: add cross rename support Miklos Szeredi 2013-11-26 9:51 ` Jan Kara 2013-11-26 13:47 ` Miklos Szeredi -- strict thread matches above, loose matches on Subject: below -- 2014-01-08 22:10 [PATCH 00/11] cross rename v3 Miklos Szeredi 2014-01-08 22:10 ` [PATCH 08/11] ext4: rename: create ext4_renament structure for local vars Miklos Szeredi
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).