Skip to content

Commit

Permalink
BACKPORT: vfs: add renameat2 syscall
Browse files Browse the repository at this point in the history
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.

Signed-off-by: Miklos Szeredi <mszeredi@suse.cz>
Reviewed-by: J. Bruce Fields <bfields@redhat.com>

@acroreiser: backport to 3.10
  • Loading branch information
Miklos Szeredi authored and TARKZiM committed Dec 8, 2023
1 parent 718918e commit f043b3a
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 17 deletions.
6 changes: 5 additions & 1 deletion Documentation/filesystems/Locking
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ prototypes:
int (*mknod) (struct inode *,struct dentry *,umode_t,dev_t);
int (*rename) (struct inode *, struct dentry *,
struct inode *, struct dentry *);
int (*rename2) (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 *);
Expand Down Expand Up @@ -79,6 +81,7 @@ mkdir: yes
unlink: yes (both)
rmdir: yes (both) (see below)
rename: yes (all) (see below)
rename2: yes (all) (see below)
readlink: no
follow_link: no
put_link: no
Expand All @@ -96,7 +99,8 @@ atomic_open: yes

Additionally, ->rmdir(), ->unlink() and ->rename() have ->i_mutex on
victim.
cross-directory ->rename() has (per-superblock) ->s_vfs_rename_sem.
cross-directory ->rename() and rename2() has (per-superblock)
->s_vfs_rename_sem.

See Documentation/filesystems/directory-locking for more detailed discussion
of the locking scheme for directory operations.
Expand Down
16 changes: 16 additions & 0 deletions Documentation/filesystems/vfs.txt
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,8 @@ struct inode_operations {
int (*mknod) (struct inode *,struct dentry *,umode_t,dev_t);
int (*rename) (struct inode *, struct dentry *,
struct inode *, struct dentry *);
int (*rename2) (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 *);
Expand Down Expand Up @@ -414,6 +416,20 @@ 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.

rename2: this has an additional flags argument compared to rename.
If no flags are supported by the filesystem then this method
need not be implemented. If some flags are supported then the
filesystem must return -EINVAL for any unsupported or unknown
flags. Currently the following flags are implemented:
(1) RENAME_NOREPLACE: this flag indicates that if the target
of the rename exists the rename should fail with -EEXIST
instead of replacing the target. The VFS already checks for
existence, so for local filesystems the RENAME_NOREPLACE
implementation is equivalent to plain rename.
(2) RENAME_EXCHANGE: exchange source and target. Both must
exist; this is checked by the VFS. Unlike plain rename,
source and target may be of different type.

readlink: called by the readlink(2) system call. Only required if
you want to support reading symbolic links

Expand Down
57 changes: 43 additions & 14 deletions fs/namei.c
Original file line number Diff line number Diff line change
Expand Up @@ -3876,7 +3876,8 @@ SYSCALL_DEFINE2(link, const char __user *, oldname, const char __user *, newname
*/
static int vfs_rename_dir(struct vfsmount *mnt,
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 error = 0;
struct inode *target = new_dentry->d_inode;
Expand Down Expand Up @@ -3911,7 +3912,13 @@ static int vfs_rename_dir(struct vfsmount *mnt,

if (target)
shrink_dcache_parent(new_dentry);
error = old_dir->i_op->rename(old_dir, old_dentry, new_dir, new_dentry);
if (!flags) {
error = old_dir->i_op->rename(old_dir, old_dentry,
new_dir, new_dentry);
} else {
error = old_dir->i_op->rename2(old_dir, old_dentry,
new_dir, new_dentry, flags);
}
if (error)
goto out;

Expand All @@ -3930,12 +3937,13 @@ static int vfs_rename_dir(struct vfsmount *mnt,
}

static int vfs_rename_other(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 *target = new_dentry->d_inode;
int 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;

Expand All @@ -3947,7 +3955,13 @@ static int vfs_rename_other(struct inode *old_dir, struct dentry *old_dentry,
if (d_mountpoint(old_dentry)||d_mountpoint(new_dentry))
goto out;

error = old_dir->i_op->rename(old_dir, old_dentry, new_dir, new_dentry);
if (!flags) {
error = old_dir->i_op->rename(old_dir, old_dentry,
new_dir, new_dentry);
} else {
error = old_dir->i_op->rename2(old_dir, old_dentry,
new_dir, new_dentry, flags);
}
if (error)
goto out;

Expand All @@ -3964,7 +3978,8 @@ static int vfs_rename_other(struct inode *old_dir, struct dentry *old_dentry,

int vfs_rename2(struct vfsmount *mnt,
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 error;
int is_dir = S_ISDIR(old_dentry->d_inode->i_mode);
Expand All @@ -3987,12 +4002,15 @@ int vfs_rename2(struct vfsmount *mnt,
if (!old_dir->i_op->rename)
return -EPERM;

if (flags && !old_dir->i_op->rename2)
return -EINVAL;

take_dentry_name_snapshot(&old_name, old_dentry);

if (is_dir)
error = vfs_rename_dir(mnt, old_dir,old_dentry,new_dir,new_dentry);
error = vfs_rename_dir(mnt, old_dir, old_dentry, new_dir, new_dentry, flags);
else
error = vfs_rename_other(old_dir,old_dentry,new_dir,new_dentry);
error = vfs_rename_other(old_dir, old_dentry, new_dir, new_dentry, flags);
if (!error)
fsnotify_move(old_dir, new_dir, old_name.name, is_dir,
new_dentry->d_inode, old_dentry);
Expand All @@ -4003,14 +4021,15 @@ int vfs_rename2(struct vfsmount *mnt,
EXPORT_SYMBOL(vfs_rename2);

int 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)
{
return vfs_rename2(NULL, old_dir, old_dentry, new_dir, new_dentry);
return vfs_rename2(NULL, old_dir, old_dentry, new_dir, new_dentry, flags);
}
EXPORT_SYMBOL(vfs_rename);

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;
Expand All @@ -4021,6 +4040,10 @@ SYSCALL_DEFINE4(renameat, int, olddfd, const char __user *, oldname,
unsigned int lookup_flags = 0;
bool should_retry = false;
int error;

if (flags)
return -EINVAL;

retry:
from = user_path_parent(olddfd, oldname, &oldnd, lookup_flags);
if (IS_ERR(from)) {
Expand Down Expand Up @@ -4091,7 +4114,7 @@ SYSCALL_DEFINE4(renameat, int, olddfd, const char __user *, oldname,
if (error)
goto exit5;
error = vfs_rename2(oldnd.path.mnt, old_dir->d_inode, old_dentry,
new_dir->d_inode, new_dentry);
new_dir->d_inode, new_dentry, flags);
exit5:
dput(new_dentry);
exit4:
Expand All @@ -4116,9 +4139,15 @@ SYSCALL_DEFINE4(renameat, int, olddfd, const char __user *, oldname,
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)
Expand Down
6 changes: 4 additions & 2 deletions include/linux/fs.h
Original file line number Diff line number Diff line change
Expand Up @@ -1501,8 +1501,8 @@ extern int vfs_rmdir(struct inode *, struct dentry *);
extern int vfs_rmdir2(struct vfsmount *, struct inode *, struct dentry *);
extern int vfs_unlink(struct inode *, struct dentry *);
extern int vfs_unlink2(struct vfsmount *, struct inode *, struct dentry *);
extern int vfs_rename(struct inode *, struct dentry *, struct inode *, struct dentry *);
extern int vfs_rename2(struct vfsmount *, struct inode *, struct dentry *, struct inode *, struct dentry *);
extern int vfs_rename(struct inode *, struct dentry *, struct inode *, struct dentry *, unsigned int);
extern int vfs_rename2(struct vfsmount *, struct inode *, struct dentry *, struct inode *, struct dentry *, unsigned int);

/*
* VFS dentry helper functions.
Expand Down Expand Up @@ -1621,6 +1621,8 @@ struct inode_operations {
int (*mknod) (struct inode *,struct dentry *,umode_t,dev_t);
int (*rename) (struct inode *, struct dentry *,
struct inode *, struct dentry *);
int (*rename2) (struct inode *, struct dentry *,
struct inode *, struct dentry *, unsigned int);
int (*setattr) (struct dentry *, struct iattr *);
int (*setattr2) (struct vfsmount *, struct dentry *, struct iattr *);
int (*getattr) (struct vfsmount *mnt, struct dentry *, struct kstat *);
Expand Down

0 comments on commit f043b3a

Please sign in to comment.