mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git
synced 2025-01-08 15:04:45 +00:00
Merge branch 'cross-rename' of git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/vfs
Pull renameat2 system call from Miklos Szeredi: "This 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" * 'cross-rename' of git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/vfs: ext4: add cross rename support ext4: rename: split out helper functions ext4: rename: move EMLINK check up ext4: rename: create ext4_renament structure for local vars vfs: add cross-rename vfs: lock_two_nondirectories: allow directory args security: add flags to rename hooks vfs: add RENAME_NOREPLACE flag vfs: add renameat2 syscall vfs: rename: use common code for dir and non-dir vfs: rename: move d_move() up vfs: add d_is_dir()
This commit is contained in:
commit
7df934526c
@ -47,6 +47,8 @@ prototypes:
|
|||||||
int (*mknod) (struct inode *,struct dentry *,umode_t,dev_t);
|
int (*mknod) (struct inode *,struct dentry *,umode_t,dev_t);
|
||||||
int (*rename) (struct inode *, struct dentry *,
|
int (*rename) (struct inode *, struct dentry *,
|
||||||
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);
|
int (*readlink) (struct dentry *, char __user *,int);
|
||||||
void * (*follow_link) (struct dentry *, struct nameidata *);
|
void * (*follow_link) (struct dentry *, struct nameidata *);
|
||||||
void (*put_link) (struct dentry *, struct nameidata *, void *);
|
void (*put_link) (struct dentry *, struct nameidata *, void *);
|
||||||
@ -78,6 +80,7 @@ mkdir: yes
|
|||||||
unlink: yes (both)
|
unlink: yes (both)
|
||||||
rmdir: yes (both) (see below)
|
rmdir: yes (both) (see below)
|
||||||
rename: yes (all) (see below)
|
rename: yes (all) (see below)
|
||||||
|
rename2: yes (all) (see below)
|
||||||
readlink: no
|
readlink: no
|
||||||
follow_link: no
|
follow_link: no
|
||||||
put_link: no
|
put_link: no
|
||||||
@ -96,7 +99,8 @@ tmpfile: no
|
|||||||
|
|
||||||
Additionally, ->rmdir(), ->unlink() and ->rename() have ->i_mutex on
|
Additionally, ->rmdir(), ->unlink() and ->rename() have ->i_mutex on
|
||||||
victim.
|
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
|
See Documentation/filesystems/directory-locking for more detailed discussion
|
||||||
of the locking scheme for directory operations.
|
of the locking scheme for directory operations.
|
||||||
|
@ -347,6 +347,8 @@ struct inode_operations {
|
|||||||
int (*mknod) (struct inode *,struct dentry *,umode_t,dev_t);
|
int (*mknod) (struct inode *,struct dentry *,umode_t,dev_t);
|
||||||
int (*rename) (struct inode *, struct dentry *,
|
int (*rename) (struct inode *, struct dentry *,
|
||||||
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);
|
int (*readlink) (struct dentry *, char __user *,int);
|
||||||
void * (*follow_link) (struct dentry *, struct nameidata *);
|
void * (*follow_link) (struct dentry *, struct nameidata *);
|
||||||
void (*put_link) (struct dentry *, struct nameidata *, void *);
|
void (*put_link) (struct dentry *, struct nameidata *, void *);
|
||||||
@ -414,6 +416,20 @@ otherwise noted.
|
|||||||
rename: called by the rename(2) system call to rename the object to
|
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.
|
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
|
readlink: called by the readlink(2) system call. Only required if
|
||||||
you want to support reading symbolic links
|
you want to support reading symbolic links
|
||||||
|
|
||||||
|
@ -322,6 +322,7 @@
|
|||||||
313 common finit_module sys_finit_module
|
313 common finit_module sys_finit_module
|
||||||
314 common sched_setattr sys_sched_setattr
|
314 common sched_setattr sys_sched_setattr
|
||||||
315 common sched_getattr sys_sched_getattr
|
315 common sched_getattr sys_sched_getattr
|
||||||
|
316 common renameat2 sys_renameat2
|
||||||
|
|
||||||
#
|
#
|
||||||
# x32-specific system call numbers start at 512 to avoid cache impact
|
# x32-specific system call numbers start at 512 to avoid cache impact
|
||||||
|
@ -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_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_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_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) \
|
#define ll_vfs_rename(old, old_dir, mnt, new, new_dir, mnt1) \
|
||||||
vfs_rename(old,old_dir,new,new_dir,delegated_inode)
|
vfs_rename(old, old_dir, new, new_dir, NULL, 0)
|
||||||
|
|
||||||
#define cfs_bio_io_error(a,b) bio_io_error((a))
|
#define cfs_bio_io_error(a,b) bio_io_error((a))
|
||||||
#define cfs_bio_endio(a,b,c) bio_endio((a),(c))
|
#define cfs_bio_endio(a,b,c) bio_endio((a),(c))
|
||||||
|
@ -223,7 +223,7 @@ int lustre_rename(struct dentry *dir, struct vfsmount *mnt,
|
|||||||
GOTO(put_old, err = PTR_ERR(dchild_new));
|
GOTO(put_old, err = PTR_ERR(dchild_new));
|
||||||
|
|
||||||
err = ll_vfs_rename(dir->d_inode, dchild_old, mnt,
|
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);
|
dput(dchild_new);
|
||||||
put_old:
|
put_old:
|
||||||
|
@ -391,12 +391,12 @@ static int cachefiles_bury_object(struct cachefiles_cache *cache,
|
|||||||
path.dentry = dir;
|
path.dentry = dir;
|
||||||
path_to_graveyard.mnt = cache->mnt;
|
path_to_graveyard.mnt = cache->mnt;
|
||||||
path_to_graveyard.dentry = cache->graveyard;
|
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) {
|
if (ret < 0) {
|
||||||
cachefiles_io_error(cache, "Rename security error %d", ret);
|
cachefiles_io_error(cache, "Rename security error %d", ret);
|
||||||
} else {
|
} else {
|
||||||
ret = vfs_rename(dir->d_inode, rep,
|
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)
|
if (ret != 0 && ret != -ENOMEM)
|
||||||
cachefiles_io_error(cache,
|
cachefiles_io_error(cache,
|
||||||
"Rename failed with error %d", ret);
|
"Rename failed with error %d", ret);
|
||||||
|
50
fs/dcache.c
50
fs/dcache.c
@ -2483,12 +2483,14 @@ static void switch_names(struct dentry *dentry, struct dentry *target)
|
|||||||
dentry->d_name.name = dentry->d_iname;
|
dentry->d_name.name = dentry->d_iname;
|
||||||
} else {
|
} else {
|
||||||
/*
|
/*
|
||||||
* Both are internal. Just copy target to dentry
|
* Both are internal.
|
||||||
*/
|
*/
|
||||||
memcpy(dentry->d_iname, target->d_name.name,
|
unsigned int i;
|
||||||
target->d_name.len + 1);
|
BUILD_BUG_ON(!IS_ALIGNED(DNAME_INLINE_LEN, sizeof(long)));
|
||||||
dentry->d_name.len = target->d_name.len;
|
for (i = 0; i < DNAME_INLINE_LEN / sizeof(long); i++) {
|
||||||
return;
|
swap(((long *) &dentry->d_iname)[i],
|
||||||
|
((long *) &target->d_iname)[i]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
swap(dentry->d_name.len, target->d_name.len);
|
swap(dentry->d_name.len, target->d_name.len);
|
||||||
@ -2545,13 +2547,15 @@ static void dentry_unlock_parents_for_move(struct dentry *dentry,
|
|||||||
* __d_move - move a dentry
|
* __d_move - move a dentry
|
||||||
* @dentry: entry to move
|
* @dentry: entry to move
|
||||||
* @target: new dentry
|
* @target: new dentry
|
||||||
|
* @exchange: exchange the two dentries
|
||||||
*
|
*
|
||||||
* Update the dcache to reflect the move of a file name. Negative
|
* Update the dcache to reflect the move of a file name. Negative
|
||||||
* dcache entries should not be moved in this way. Caller must hold
|
* dcache entries should not be moved in this way. Caller must hold
|
||||||
* rename_lock, the i_mutex of the source and target directories,
|
* rename_lock, the i_mutex of the source and target directories,
|
||||||
* and the sb->s_vfs_rename_mutex if they differ. See lock_rename().
|
* 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)
|
if (!dentry->d_inode)
|
||||||
printk(KERN_WARNING "VFS: moving negative dcache entry\n");
|
printk(KERN_WARNING "VFS: moving negative dcache entry\n");
|
||||||
@ -2573,8 +2577,15 @@ static void __d_move(struct dentry * dentry, struct dentry * target)
|
|||||||
__d_drop(dentry);
|
__d_drop(dentry);
|
||||||
__d_rehash(dentry, d_hash(target->d_parent, target->d_name.hash));
|
__d_rehash(dentry, d_hash(target->d_parent, target->d_name.hash));
|
||||||
|
|
||||||
/* Unhash the target: dput() will then get rid of it */
|
/*
|
||||||
|
* Unhash the target (d_delete() is not usable here). If exchanging
|
||||||
|
* the two dentries, then rehash onto the other's hash queue.
|
||||||
|
*/
|
||||||
__d_drop(target);
|
__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(&dentry->d_u.d_child);
|
||||||
list_del(&target->d_u.d_child);
|
list_del(&target->d_u.d_child);
|
||||||
@ -2601,6 +2612,8 @@ static void __d_move(struct dentry * dentry, struct dentry * target)
|
|||||||
write_seqcount_end(&dentry->d_seq);
|
write_seqcount_end(&dentry->d_seq);
|
||||||
|
|
||||||
dentry_unlock_parents_for_move(dentry, target);
|
dentry_unlock_parents_for_move(dentry, target);
|
||||||
|
if (exchange)
|
||||||
|
fsnotify_d_move(target);
|
||||||
spin_unlock(&target->d_lock);
|
spin_unlock(&target->d_lock);
|
||||||
fsnotify_d_move(dentry);
|
fsnotify_d_move(dentry);
|
||||||
spin_unlock(&dentry->d_lock);
|
spin_unlock(&dentry->d_lock);
|
||||||
@ -2618,11 +2631,30 @@ static void __d_move(struct dentry * dentry, struct dentry * target)
|
|||||||
void d_move(struct dentry *dentry, struct dentry *target)
|
void d_move(struct dentry *dentry, struct dentry *target)
|
||||||
{
|
{
|
||||||
write_seqlock(&rename_lock);
|
write_seqlock(&rename_lock);
|
||||||
__d_move(dentry, target);
|
__d_move(dentry, target, false);
|
||||||
write_sequnlock(&rename_lock);
|
write_sequnlock(&rename_lock);
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(d_move);
|
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
|
* d_ancestor - search for an ancestor
|
||||||
* @p1: ancestor dentry
|
* @p1: ancestor dentry
|
||||||
@ -2670,7 +2702,7 @@ static struct dentry *__d_unalias(struct inode *inode,
|
|||||||
m2 = &alias->d_parent->d_inode->i_mutex;
|
m2 = &alias->d_parent->d_inode->i_mutex;
|
||||||
out_unalias:
|
out_unalias:
|
||||||
if (likely(!d_mountpoint(alias))) {
|
if (likely(!d_mountpoint(alias))) {
|
||||||
__d_move(alias, dentry);
|
__d_move(alias, dentry, false);
|
||||||
ret = alias;
|
ret = alias;
|
||||||
}
|
}
|
||||||
out_err:
|
out_err:
|
||||||
|
@ -641,7 +641,7 @@ ecryptfs_rename(struct inode *old_dir, struct dentry *old_dentry,
|
|||||||
}
|
}
|
||||||
rc = vfs_rename(lower_old_dir_dentry->d_inode, lower_old_dentry,
|
rc = vfs_rename(lower_old_dir_dentry->d_inode, lower_old_dentry,
|
||||||
lower_new_dir_dentry->d_inode, lower_new_dentry,
|
lower_new_dir_dentry->d_inode, lower_new_dentry,
|
||||||
NULL);
|
NULL, 0);
|
||||||
if (rc)
|
if (rc)
|
||||||
goto out_lock;
|
goto out_lock;
|
||||||
if (target_inode)
|
if (target_inode)
|
||||||
|
478
fs/ext4/namei.c
478
fs/ext4/namei.c
@ -3000,6 +3000,154 @@ static struct buffer_head *ext4_get_first_dir_block(handle_t *handle,
|
|||||||
return ext4_get_first_inline_block(inode, parent_de, retval);
|
return ext4_get_first_inline_block(inode, parent_de, retval);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct ext4_renament {
|
||||||
|
struct inode *dir;
|
||||||
|
struct dentry *dentry;
|
||||||
|
struct inode *inode;
|
||||||
|
bool is_dir;
|
||||||
|
int dir_nlink_delta;
|
||||||
|
|
||||||
|
/* 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;
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ext4_update_dir_count(handle_t *handle, struct ext4_renament *ent)
|
||||||
|
{
|
||||||
|
if (ent->dir_nlink_delta) {
|
||||||
|
if (ent->dir_nlink_delta == -1)
|
||||||
|
ext4_dec_count(handle, ent->dir);
|
||||||
|
else
|
||||||
|
ext4_inc_count(handle, ent->dir);
|
||||||
|
ext4_mark_inode_dirty(handle, ent->dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Anybody can rename anything with this: the permission checks are left to the
|
* Anybody can rename anything with this: the permission checks are left to the
|
||||||
* higher-level routines.
|
* higher-level routines.
|
||||||
@ -3012,198 +3160,267 @@ 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)
|
||||||
{
|
{
|
||||||
handle_t *handle = NULL;
|
handle_t *handle = NULL;
|
||||||
struct inode *old_inode, *new_inode;
|
struct ext4_renament old = {
|
||||||
struct buffer_head *old_bh, *new_bh, *dir_bh;
|
.dir = old_dir,
|
||||||
struct ext4_dir_entry_2 *old_de, *new_de;
|
.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 retval;
|
||||||
int inlined = 0, new_inlined = 0;
|
|
||||||
struct ext4_dir_entry_2 *parent_de;
|
|
||||||
|
|
||||||
dquot_initialize(old_dir);
|
dquot_initialize(old.dir);
|
||||||
dquot_initialize(new_dir);
|
dquot_initialize(new.dir);
|
||||||
|
|
||||||
old_bh = new_bh = dir_bh = NULL;
|
|
||||||
|
|
||||||
/* Initialize quotas before so that eventual writes go
|
/* Initialize quotas before so that eventual writes go
|
||||||
* in separate transaction */
|
* in separate transaction */
|
||||||
if (new_dentry->d_inode)
|
if (new.inode)
|
||||||
dquot_initialize(new_dentry->d_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.
|
* Check for inode number is _not_ due to possible IO errors.
|
||||||
* We might rmdir the source, keep it as pwd of some process
|
* We might rmdir the source, keep it as pwd of some process
|
||||||
* and merrily kill the link to whatever was created under the
|
* and merrily kill the link to whatever was created under the
|
||||||
* same name. Goodbye sticky bit ;-<
|
* same name. Goodbye sticky bit ;-<
|
||||||
*/
|
*/
|
||||||
old_inode = old_dentry->d_inode;
|
|
||||||
retval = -ENOENT;
|
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;
|
goto end_rename;
|
||||||
|
|
||||||
new_inode = new_dentry->d_inode;
|
new.bh = ext4_find_entry(new.dir, &new.dentry->d_name,
|
||||||
new_bh = ext4_find_entry(new_dir, &new_dentry->d_name,
|
&new.de, &new.inlined);
|
||||||
&new_de, &new_inlined);
|
if (new.bh) {
|
||||||
if (new_bh) {
|
if (!new.inode) {
|
||||||
if (!new_inode) {
|
brelse(new.bh);
|
||||||
brelse(new_bh);
|
new.bh = NULL;
|
||||||
new_bh = NULL;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (new_inode && !test_opt(new_dir->i_sb, NO_AUTO_DA_ALLOC))
|
if (new.inode && !test_opt(new.dir->i_sb, NO_AUTO_DA_ALLOC))
|
||||||
ext4_alloc_da_blocks(old_inode);
|
ext4_alloc_da_blocks(old.inode);
|
||||||
|
|
||||||
handle = ext4_journal_start(old_dir, EXT4_HT_DIR,
|
handle = ext4_journal_start(old.dir, EXT4_HT_DIR,
|
||||||
(2 * EXT4_DATA_TRANS_BLOCKS(old_dir->i_sb) +
|
(2 * EXT4_DATA_TRANS_BLOCKS(old.dir->i_sb) +
|
||||||
EXT4_INDEX_EXTRA_TRANS_BLOCKS + 2));
|
EXT4_INDEX_EXTRA_TRANS_BLOCKS + 2));
|
||||||
if (IS_ERR(handle))
|
if (IS_ERR(handle))
|
||||||
return PTR_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);
|
ext4_handle_sync(handle);
|
||||||
|
|
||||||
if (S_ISDIR(old_inode->i_mode)) {
|
if (S_ISDIR(old.inode->i_mode)) {
|
||||||
if (new_inode) {
|
if (new.inode) {
|
||||||
retval = -ENOTEMPTY;
|
retval = -ENOTEMPTY;
|
||||||
if (!empty_dir(new_inode))
|
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;
|
goto end_rename;
|
||||||
}
|
}
|
||||||
retval = -EIO;
|
retval = ext4_rename_dir_prepare(handle, &old);
|
||||||
dir_bh = ext4_get_first_dir_block(handle, old_inode,
|
|
||||||
&retval, &parent_de,
|
|
||||||
&inlined);
|
|
||||||
if (!dir_bh)
|
|
||||||
goto end_rename;
|
|
||||||
if (le32_to_cpu(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(dir_bh, "get_write_access");
|
|
||||||
retval = ext4_journal_get_write_access(handle, dir_bh);
|
|
||||||
if (retval)
|
if (retval)
|
||||||
goto end_rename;
|
goto end_rename;
|
||||||
}
|
}
|
||||||
if (!new_bh) {
|
if (!new.bh) {
|
||||||
retval = ext4_add_entry(handle, new_dentry, old_inode);
|
retval = ext4_add_entry(handle, new.dentry, old.inode);
|
||||||
if (retval)
|
if (retval)
|
||||||
goto end_rename;
|
goto end_rename;
|
||||||
} else {
|
} else {
|
||||||
BUFFER_TRACE(new_bh, "get write access");
|
retval = ext4_setent(handle, &new,
|
||||||
retval = ext4_journal_get_write_access(handle, new_bh);
|
old.inode->i_ino, old.de->file_type);
|
||||||
if (retval)
|
if (retval)
|
||||||
goto end_rename;
|
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Like most other Unix systems, set the ctime for inodes on a
|
* Like most other Unix systems, set the ctime for inodes on a
|
||||||
* rename.
|
* rename.
|
||||||
*/
|
*/
|
||||||
old_inode->i_ctime = ext4_current_time(old_inode);
|
old.inode->i_ctime = ext4_current_time(old.inode);
|
||||||
ext4_mark_inode_dirty(handle, old_inode);
|
ext4_mark_inode_dirty(handle, old.inode);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ok, that's it
|
* ok, that's it
|
||||||
*/
|
*/
|
||||||
if (le32_to_cpu(old_de->inode) != old_inode->i_ino ||
|
ext4_rename_delete(handle, &old);
|
||||||
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,
|
if (new.inode) {
|
||||||
&old_de2, NULL);
|
ext4_dec_count(handle, new.inode);
|
||||||
if (old_bh2) {
|
new.inode->i_ctime = ext4_current_time(new.inode);
|
||||||
retval = ext4_delete_entry(handle, old_dir,
|
|
||||||
old_de2, old_bh2);
|
|
||||||
brelse(old_bh2);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (retval) {
|
old.dir->i_ctime = old.dir->i_mtime = ext4_current_time(old.dir);
|
||||||
ext4_warning(old_dir->i_sb,
|
ext4_update_dx_flag(old.dir);
|
||||||
"Deleting old file (%lu), %d, error=%d",
|
if (old.dir_bh) {
|
||||||
old_dir->i_ino, old_dir->i_nlink, retval);
|
retval = ext4_rename_dir_finish(handle, &old, new.dir->i_ino);
|
||||||
}
|
if (retval)
|
||||||
|
|
||||||
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)) {
|
|
||||||
retval = ext4_handle_dirty_dx_node(handle,
|
|
||||||
old_inode,
|
|
||||||
dir_bh);
|
|
||||||
} else {
|
|
||||||
retval = ext4_handle_dirty_dirent_node(handle,
|
|
||||||
old_inode, dir_bh);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
retval = ext4_mark_inode_dirty(handle, old_inode);
|
|
||||||
}
|
|
||||||
if (retval) {
|
|
||||||
ext4_std_error(old_dir->i_sb, retval);
|
|
||||||
goto end_rename;
|
goto end_rename;
|
||||||
}
|
|
||||||
ext4_dec_count(handle, old_dir);
|
ext4_dec_count(handle, old.dir);
|
||||||
if (new_inode) {
|
if (new.inode) {
|
||||||
/* checked empty_dir above, can't have another parent,
|
/* checked empty_dir above, can't have another parent,
|
||||||
* ext4_dec_count() won't work for many-linked dirs */
|
* ext4_dec_count() won't work for many-linked dirs */
|
||||||
clear_nlink(new_inode);
|
clear_nlink(new.inode);
|
||||||
} else {
|
} else {
|
||||||
ext4_inc_count(handle, new_dir);
|
ext4_inc_count(handle, new.dir);
|
||||||
ext4_update_dx_flag(new_dir);
|
ext4_update_dx_flag(new.dir);
|
||||||
ext4_mark_inode_dirty(handle, new_dir);
|
ext4_mark_inode_dirty(handle, new.dir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ext4_mark_inode_dirty(handle, old_dir);
|
ext4_mark_inode_dirty(handle, old.dir);
|
||||||
if (new_inode) {
|
if (new.inode) {
|
||||||
ext4_mark_inode_dirty(handle, new_inode);
|
ext4_mark_inode_dirty(handle, new.inode);
|
||||||
if (!new_inode->i_nlink)
|
if (!new.inode->i_nlink)
|
||||||
ext4_orphan_add(handle, new_inode);
|
ext4_orphan_add(handle, new.inode);
|
||||||
}
|
}
|
||||||
retval = 0;
|
retval = 0;
|
||||||
|
|
||||||
end_rename:
|
end_rename:
|
||||||
brelse(dir_bh);
|
brelse(old.dir_bh);
|
||||||
brelse(old_bh);
|
brelse(old.bh);
|
||||||
brelse(new_bh);
|
brelse(new.bh);
|
||||||
if (handle)
|
if (handle)
|
||||||
ext4_journal_stop(handle);
|
ext4_journal_stop(handle);
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int ext4_cross_rename(struct inode *old_dir, struct dentry *old_dentry,
|
||||||
|
struct inode *new_dir, struct dentry *new_dentry)
|
||||||
|
{
|
||||||
|
handle_t *handle = NULL;
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
u8 new_file_type;
|
||||||
|
int retval;
|
||||||
|
|
||||||
|
dquot_initialize(old.dir);
|
||||||
|
dquot_initialize(new.dir);
|
||||||
|
|
||||||
|
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
|
||||||
|
* and merrily kill the link to whatever was created under the
|
||||||
|
* same name. Goodbye sticky bit ;-<
|
||||||
|
*/
|
||||||
|
retval = -ENOENT;
|
||||||
|
if (!old.bh || le32_to_cpu(old.de->inode) != old.inode->i_ino)
|
||||||
|
goto end_rename;
|
||||||
|
|
||||||
|
new.bh = ext4_find_entry(new.dir, &new.dentry->d_name,
|
||||||
|
&new.de, &new.inlined);
|
||||||
|
|
||||||
|
/* RENAME_EXCHANGE case: old *and* new must both exist */
|
||||||
|
if (!new.bh || le32_to_cpu(new.de->inode) != new.inode->i_ino)
|
||||||
|
goto end_rename;
|
||||||
|
|
||||||
|
handle = ext4_journal_start(old.dir, EXT4_HT_DIR,
|
||||||
|
(2 * EXT4_DATA_TRANS_BLOCKS(old.dir->i_sb) +
|
||||||
|
2 * EXT4_INDEX_EXTRA_TRANS_BLOCKS + 2));
|
||||||
|
if (IS_ERR(handle))
|
||||||
|
return PTR_ERR(handle);
|
||||||
|
|
||||||
|
if (IS_DIRSYNC(old.dir) || IS_DIRSYNC(new.dir))
|
||||||
|
ext4_handle_sync(handle);
|
||||||
|
|
||||||
|
if (S_ISDIR(old.inode->i_mode)) {
|
||||||
|
old.is_dir = true;
|
||||||
|
retval = ext4_rename_dir_prepare(handle, &old);
|
||||||
|
if (retval)
|
||||||
|
goto end_rename;
|
||||||
|
}
|
||||||
|
if (S_ISDIR(new.inode->i_mode)) {
|
||||||
|
new.is_dir = true;
|
||||||
|
retval = ext4_rename_dir_prepare(handle, &new);
|
||||||
|
if (retval)
|
||||||
|
goto end_rename;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Other than the special case of overwriting a directory, parents'
|
||||||
|
* nlink only needs to be modified if this is a cross directory rename.
|
||||||
|
*/
|
||||||
|
if (old.dir != new.dir && old.is_dir != new.is_dir) {
|
||||||
|
old.dir_nlink_delta = old.is_dir ? -1 : 1;
|
||||||
|
new.dir_nlink_delta = -old.dir_nlink_delta;
|
||||||
|
retval = -EMLINK;
|
||||||
|
if ((old.dir_nlink_delta > 0 && EXT4_DIR_LINK_MAX(old.dir)) ||
|
||||||
|
(new.dir_nlink_delta > 0 && EXT4_DIR_LINK_MAX(new.dir)))
|
||||||
|
goto end_rename;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
retval = ext4_setent(handle, &old, new.inode->i_ino, new_file_type);
|
||||||
|
if (retval)
|
||||||
|
goto end_rename;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Like most other Unix systems, set the ctime for inodes on a
|
||||||
|
* rename.
|
||||||
|
*/
|
||||||
|
old.inode->i_ctime = ext4_current_time(old.inode);
|
||||||
|
new.inode->i_ctime = ext4_current_time(new.inode);
|
||||||
|
ext4_mark_inode_dirty(handle, old.inode);
|
||||||
|
ext4_mark_inode_dirty(handle, new.inode);
|
||||||
|
|
||||||
|
if (old.dir_bh) {
|
||||||
|
retval = ext4_rename_dir_finish(handle, &old, new.dir->i_ino);
|
||||||
|
if (retval)
|
||||||
|
goto end_rename;
|
||||||
|
}
|
||||||
|
if (new.dir_bh) {
|
||||||
|
retval = ext4_rename_dir_finish(handle, &new, old.dir->i_ino);
|
||||||
|
if (retval)
|
||||||
|
goto end_rename;
|
||||||
|
}
|
||||||
|
ext4_update_dir_count(handle, &old);
|
||||||
|
ext4_update_dir_count(handle, &new);
|
||||||
|
retval = 0;
|
||||||
|
|
||||||
|
end_rename:
|
||||||
|
brelse(old.dir_bh);
|
||||||
|
brelse(new.dir_bh);
|
||||||
|
brelse(old.bh);
|
||||||
|
brelse(new.bh);
|
||||||
|
if (handle)
|
||||||
|
ext4_journal_stop(handle);
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ext4_rename2(struct inode *old_dir, struct dentry *old_dentry,
|
||||||
|
struct inode *new_dir, struct dentry *new_dentry,
|
||||||
|
unsigned int flags)
|
||||||
|
{
|
||||||
|
if (flags & ~(RENAME_NOREPLACE | RENAME_EXCHANGE))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
if (flags & RENAME_EXCHANGE) {
|
||||||
|
return ext4_cross_rename(old_dir, old_dentry,
|
||||||
|
new_dir, new_dentry);
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* Existence checking was done by the VFS, otherwise "RENAME_NOREPLACE"
|
||||||
|
* is equivalent to regular rename.
|
||||||
|
*/
|
||||||
|
return ext4_rename(old_dir, old_dentry, new_dir, new_dentry);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* directories can handle most operations...
|
* directories can handle most operations...
|
||||||
*/
|
*/
|
||||||
@ -3218,6 +3435,7 @@ const struct inode_operations ext4_dir_inode_operations = {
|
|||||||
.mknod = ext4_mknod,
|
.mknod = ext4_mknod,
|
||||||
.tmpfile = ext4_tmpfile,
|
.tmpfile = ext4_tmpfile,
|
||||||
.rename = ext4_rename,
|
.rename = ext4_rename,
|
||||||
|
.rename2 = ext4_rename2,
|
||||||
.setattr = ext4_setattr,
|
.setattr = ext4_setattr,
|
||||||
.setxattr = generic_setxattr,
|
.setxattr = generic_setxattr,
|
||||||
.getxattr = generic_getxattr,
|
.getxattr = generic_getxattr,
|
||||||
|
25
fs/inode.c
25
fs/inode.c
@ -944,24 +944,22 @@ EXPORT_SYMBOL(unlock_new_inode);
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* lock_two_nondirectories - take two i_mutexes on non-directory objects
|
* lock_two_nondirectories - take two i_mutexes on non-directory objects
|
||||||
|
*
|
||||||
|
* Lock any non-NULL argument that is not a directory.
|
||||||
|
* Zero, one or two objects may be locked by this function.
|
||||||
|
*
|
||||||
* @inode1: first inode to lock
|
* @inode1: first inode to lock
|
||||||
* @inode2: second inode to lock
|
* @inode2: second inode to lock
|
||||||
*/
|
*/
|
||||||
void lock_two_nondirectories(struct inode *inode1, struct inode *inode2)
|
void lock_two_nondirectories(struct inode *inode1, struct inode *inode2)
|
||||||
{
|
{
|
||||||
WARN_ON_ONCE(S_ISDIR(inode1->i_mode));
|
if (inode1 > inode2)
|
||||||
if (inode1 == inode2 || !inode2) {
|
swap(inode1, inode2);
|
||||||
mutex_lock(&inode1->i_mutex);
|
|
||||||
return;
|
if (inode1 && !S_ISDIR(inode1->i_mode))
|
||||||
}
|
|
||||||
WARN_ON_ONCE(S_ISDIR(inode2->i_mode));
|
|
||||||
if (inode1 < inode2) {
|
|
||||||
mutex_lock(&inode1->i_mutex);
|
mutex_lock(&inode1->i_mutex);
|
||||||
|
if (inode2 && !S_ISDIR(inode2->i_mode) && inode2 != inode1)
|
||||||
mutex_lock_nested(&inode2->i_mutex, I_MUTEX_NONDIR2);
|
mutex_lock_nested(&inode2->i_mutex, I_MUTEX_NONDIR2);
|
||||||
} else {
|
|
||||||
mutex_lock(&inode2->i_mutex);
|
|
||||||
mutex_lock_nested(&inode1->i_mutex, I_MUTEX_NONDIR2);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(lock_two_nondirectories);
|
EXPORT_SYMBOL(lock_two_nondirectories);
|
||||||
|
|
||||||
@ -972,8 +970,9 @@ EXPORT_SYMBOL(lock_two_nondirectories);
|
|||||||
*/
|
*/
|
||||||
void unlock_two_nondirectories(struct inode *inode1, struct inode *inode2)
|
void unlock_two_nondirectories(struct inode *inode1, struct inode *inode2)
|
||||||
{
|
{
|
||||||
mutex_unlock(&inode1->i_mutex);
|
if (inode1 && !S_ISDIR(inode1->i_mode))
|
||||||
if (inode2 && inode2 != inode1)
|
mutex_unlock(&inode1->i_mutex);
|
||||||
|
if (inode2 && !S_ISDIR(inode2->i_mode) && inode2 != inode1)
|
||||||
mutex_unlock(&inode2->i_mutex);
|
mutex_unlock(&inode2->i_mutex);
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(unlock_two_nondirectories);
|
EXPORT_SYMBOL(unlock_two_nondirectories);
|
||||||
|
351
fs/namei.c
351
fs/namei.c
@ -1796,7 +1796,7 @@ static int link_path_walk(const char *name, struct nameidata *nd)
|
|||||||
if (err)
|
if (err)
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
if (!d_is_directory(nd->path.dentry)) {
|
if (!d_can_lookup(nd->path.dentry)) {
|
||||||
err = -ENOTDIR;
|
err = -ENOTDIR;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -1817,7 +1817,7 @@ static int path_init(int dfd, const char *name, unsigned int flags,
|
|||||||
struct dentry *root = nd->root.dentry;
|
struct dentry *root = nd->root.dentry;
|
||||||
struct inode *inode = root->d_inode;
|
struct inode *inode = root->d_inode;
|
||||||
if (*name) {
|
if (*name) {
|
||||||
if (!d_is_directory(root))
|
if (!d_can_lookup(root))
|
||||||
return -ENOTDIR;
|
return -ENOTDIR;
|
||||||
retval = inode_permission(inode, MAY_EXEC);
|
retval = inode_permission(inode, MAY_EXEC);
|
||||||
if (retval)
|
if (retval)
|
||||||
@ -1873,7 +1873,7 @@ static int path_init(int dfd, const char *name, unsigned int flags,
|
|||||||
dentry = f.file->f_path.dentry;
|
dentry = f.file->f_path.dentry;
|
||||||
|
|
||||||
if (*name) {
|
if (*name) {
|
||||||
if (!d_is_directory(dentry)) {
|
if (!d_can_lookup(dentry)) {
|
||||||
fdput(f);
|
fdput(f);
|
||||||
return -ENOTDIR;
|
return -ENOTDIR;
|
||||||
}
|
}
|
||||||
@ -1955,7 +1955,7 @@ static int path_lookupat(int dfd, const char *name,
|
|||||||
err = complete_walk(nd);
|
err = complete_walk(nd);
|
||||||
|
|
||||||
if (!err && nd->flags & LOOKUP_DIRECTORY) {
|
if (!err && nd->flags & LOOKUP_DIRECTORY) {
|
||||||
if (!d_is_directory(nd->path.dentry)) {
|
if (!d_can_lookup(nd->path.dentry)) {
|
||||||
path_put(&nd->path);
|
path_put(&nd->path);
|
||||||
err = -ENOTDIR;
|
err = -ENOTDIR;
|
||||||
}
|
}
|
||||||
@ -2414,11 +2414,11 @@ static int may_delete(struct inode *dir, struct dentry *victim, bool isdir)
|
|||||||
IS_IMMUTABLE(inode) || IS_SWAPFILE(inode))
|
IS_IMMUTABLE(inode) || IS_SWAPFILE(inode))
|
||||||
return -EPERM;
|
return -EPERM;
|
||||||
if (isdir) {
|
if (isdir) {
|
||||||
if (!d_is_directory(victim) && !d_is_autodir(victim))
|
if (!d_is_dir(victim))
|
||||||
return -ENOTDIR;
|
return -ENOTDIR;
|
||||||
if (IS_ROOT(victim))
|
if (IS_ROOT(victim))
|
||||||
return -EBUSY;
|
return -EBUSY;
|
||||||
} else if (d_is_directory(victim) || d_is_autodir(victim))
|
} else if (d_is_dir(victim))
|
||||||
return -EISDIR;
|
return -EISDIR;
|
||||||
if (IS_DEADDIR(dir))
|
if (IS_DEADDIR(dir))
|
||||||
return -ENOENT;
|
return -ENOENT;
|
||||||
@ -3016,11 +3016,10 @@ static int do_last(struct nameidata *nd, struct path *path,
|
|||||||
}
|
}
|
||||||
audit_inode(name, nd->path.dentry, 0);
|
audit_inode(name, nd->path.dentry, 0);
|
||||||
error = -EISDIR;
|
error = -EISDIR;
|
||||||
if ((open_flag & O_CREAT) &&
|
if ((open_flag & O_CREAT) && d_is_dir(nd->path.dentry))
|
||||||
(d_is_directory(nd->path.dentry) || d_is_autodir(nd->path.dentry)))
|
|
||||||
goto out;
|
goto out;
|
||||||
error = -ENOTDIR;
|
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;
|
goto out;
|
||||||
if (!S_ISREG(nd->inode->i_mode))
|
if (!S_ISREG(nd->inode->i_mode))
|
||||||
will_truncate = false;
|
will_truncate = false;
|
||||||
@ -3744,7 +3743,7 @@ static long do_unlinkat(int dfd, const char __user *pathname)
|
|||||||
slashes:
|
slashes:
|
||||||
if (d_is_negative(dentry))
|
if (d_is_negative(dentry))
|
||||||
error = -ENOENT;
|
error = -ENOENT;
|
||||||
else if (d_is_directory(dentry) || d_is_autodir(dentry))
|
else if (d_is_dir(dentry))
|
||||||
error = -EISDIR;
|
error = -EISDIR;
|
||||||
else
|
else
|
||||||
error = -ENOTDIR;
|
error = -ENOTDIR;
|
||||||
@ -3974,7 +3973,28 @@ SYSCALL_DEFINE2(link, const char __user *, oldname, const char __user *, newname
|
|||||||
return sys_linkat(AT_FDCWD, oldname, AT_FDCWD, newname, 0);
|
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
|
||||||
|
* @flags: rename flags
|
||||||
|
*
|
||||||
|
* 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"
|
* 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...
|
* doesn't even start to describe it. Somebody in UCB had a heck of a trip...
|
||||||
* Problems:
|
* Problems:
|
||||||
@ -4002,163 +4022,139 @@ SYSCALL_DEFINE2(link, const char __user *, oldname, const char __user *, newname
|
|||||||
* ->i_mutex on parents, which works but leads to some truly excessive
|
* ->i_mutex on parents, which works but leads to some truly excessive
|
||||||
* locking].
|
* locking].
|
||||||
*/
|
*/
|
||||||
static int vfs_rename_dir(struct inode *old_dir, struct dentry *old_dentry,
|
|
||||||
struct inode *new_dir, struct dentry *new_dentry)
|
|
||||||
{
|
|
||||||
int error = 0;
|
|
||||||
struct inode *target = new_dentry->d_inode;
|
|
||||||
unsigned max_links = new_dir->i_sb->s_max_links;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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 (error)
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
|
|
||||||
error = security_inode_rename(old_dir, old_dentry, new_dir, new_dentry);
|
|
||||||
if (error)
|
|
||||||
return error;
|
|
||||||
|
|
||||||
dget(new_dentry);
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
error = -EBUSY;
|
|
||||||
if (d_mountpoint(old_dentry)||d_mountpoint(new_dentry))
|
|
||||||
goto out;
|
|
||||||
|
|
||||||
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)
|
|
||||||
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);
|
|
||||||
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,
|
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,
|
||||||
struct inode **delegated_inode)
|
struct inode **delegated_inode, unsigned int flags)
|
||||||
{
|
{
|
||||||
int error;
|
int error;
|
||||||
int is_dir = d_is_directory(old_dentry) || d_is_autodir(old_dentry);
|
bool is_dir = d_is_dir(old_dentry);
|
||||||
const unsigned char *old_name;
|
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;
|
||||||
|
|
||||||
if (old_dentry->d_inode == new_dentry->d_inode)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
error = may_delete(old_dir, old_dentry, is_dir);
|
error = may_delete(old_dir, old_dentry, is_dir);
|
||||||
if (error)
|
if (error)
|
||||||
return error;
|
return error;
|
||||||
|
|
||||||
if (!new_dentry->d_inode)
|
if (!target) {
|
||||||
error = may_create(new_dir, new_dentry);
|
error = may_create(new_dir, new_dentry);
|
||||||
else
|
} else {
|
||||||
error = may_delete(new_dir, new_dentry, is_dir);
|
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)
|
if (error)
|
||||||
return error;
|
return error;
|
||||||
|
|
||||||
if (!old_dir->i_op->rename)
|
if (!old_dir->i_op->rename)
|
||||||
return -EPERM;
|
return -EPERM;
|
||||||
|
|
||||||
old_name = fsnotify_oldname_init(old_dentry->d_name.name);
|
if (flags && !old_dir->i_op->rename2)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
if (is_dir)
|
/*
|
||||||
error = vfs_rename_dir(old_dir,old_dentry,new_dir,new_dentry);
|
* If we are going to change the parent - check write permissions,
|
||||||
else
|
* we'll need to flip '..'.
|
||||||
error = vfs_rename_other(old_dir,old_dentry,new_dir,new_dentry,delegated_inode);
|
*/
|
||||||
if (!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,
|
||||||
|
flags);
|
||||||
|
if (error)
|
||||||
|
return error;
|
||||||
|
|
||||||
|
old_name = fsnotify_oldname_init(old_dentry->d_name.name);
|
||||||
|
dget(new_dentry);
|
||||||
|
if (!is_dir || (flags & RENAME_EXCHANGE))
|
||||||
|
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 (max_links && new_dir != old_dir) {
|
||||||
|
error = -EMLINK;
|
||||||
|
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 (is_dir && !(flags & RENAME_EXCHANGE) && target)
|
||||||
|
shrink_dcache_parent(new_dentry);
|
||||||
|
if (!is_dir) {
|
||||||
|
error = try_break_deleg(source, delegated_inode);
|
||||||
|
if (error)
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
if (target && !new_is_dir) {
|
||||||
|
error = try_break_deleg(target, delegated_inode);
|
||||||
|
if (error)
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
|
||||||
|
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)) {
|
||||||
|
if (!(flags & RENAME_EXCHANGE))
|
||||||
|
d_move(old_dentry, new_dentry);
|
||||||
|
else
|
||||||
|
d_exchange(old_dentry, new_dentry);
|
||||||
|
}
|
||||||
|
out:
|
||||||
|
if (!is_dir || (flags & RENAME_EXCHANGE))
|
||||||
|
unlock_two_nondirectories(source, target);
|
||||||
|
else if (target)
|
||||||
|
mutex_unlock(&target->i_mutex);
|
||||||
|
dput(new_dentry);
|
||||||
|
if (!error) {
|
||||||
fsnotify_move(old_dir, new_dir, old_name, is_dir,
|
fsnotify_move(old_dir, new_dir, old_name, is_dir,
|
||||||
new_dentry->d_inode, 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);
|
fsnotify_oldname_free(old_name);
|
||||||
|
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
SYSCALL_DEFINE4(renameat, int, olddfd, const char __user *, oldname,
|
SYSCALL_DEFINE5(renameat2, int, olddfd, const char __user *, oldname,
|
||||||
int, newdfd, const char __user *, newname)
|
int, newdfd, const char __user *, newname, unsigned int, flags)
|
||||||
{
|
{
|
||||||
struct dentry *old_dir, *new_dir;
|
struct dentry *old_dir, *new_dir;
|
||||||
struct dentry *old_dentry, *new_dentry;
|
struct dentry *old_dentry, *new_dentry;
|
||||||
@ -4170,6 +4166,13 @@ SYSCALL_DEFINE4(renameat, int, olddfd, const char __user *, oldname,
|
|||||||
unsigned int lookup_flags = 0;
|
unsigned int lookup_flags = 0;
|
||||||
bool should_retry = false;
|
bool should_retry = false;
|
||||||
int error;
|
int error;
|
||||||
|
|
||||||
|
if (flags & ~(RENAME_NOREPLACE | RENAME_EXCHANGE))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
if ((flags & RENAME_NOREPLACE) && (flags & RENAME_EXCHANGE))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
retry:
|
retry:
|
||||||
from = user_path_parent(olddfd, oldname, &oldnd, lookup_flags);
|
from = user_path_parent(olddfd, oldname, &oldnd, lookup_flags);
|
||||||
if (IS_ERR(from)) {
|
if (IS_ERR(from)) {
|
||||||
@ -4193,6 +4196,8 @@ SYSCALL_DEFINE4(renameat, int, olddfd, const char __user *, oldname,
|
|||||||
goto exit2;
|
goto exit2;
|
||||||
|
|
||||||
new_dir = newnd.path.dentry;
|
new_dir = newnd.path.dentry;
|
||||||
|
if (flags & RENAME_NOREPLACE)
|
||||||
|
error = -EEXIST;
|
||||||
if (newnd.last_type != LAST_NORM)
|
if (newnd.last_type != LAST_NORM)
|
||||||
goto exit2;
|
goto exit2;
|
||||||
|
|
||||||
@ -4202,7 +4207,8 @@ SYSCALL_DEFINE4(renameat, int, olddfd, const char __user *, oldname,
|
|||||||
|
|
||||||
oldnd.flags &= ~LOOKUP_PARENT;
|
oldnd.flags &= ~LOOKUP_PARENT;
|
||||||
newnd.flags &= ~LOOKUP_PARENT;
|
newnd.flags &= ~LOOKUP_PARENT;
|
||||||
newnd.flags |= LOOKUP_RENAME_TARGET;
|
if (!(flags & RENAME_EXCHANGE))
|
||||||
|
newnd.flags |= LOOKUP_RENAME_TARGET;
|
||||||
|
|
||||||
retry_deleg:
|
retry_deleg:
|
||||||
trap = lock_rename(new_dir, old_dir);
|
trap = lock_rename(new_dir, old_dir);
|
||||||
@ -4215,34 +4221,49 @@ SYSCALL_DEFINE4(renameat, int, olddfd, const char __user *, oldname,
|
|||||||
error = -ENOENT;
|
error = -ENOENT;
|
||||||
if (d_is_negative(old_dentry))
|
if (d_is_negative(old_dentry))
|
||||||
goto exit4;
|
goto exit4;
|
||||||
/* unless the source is a directory trailing slashes give -ENOTDIR */
|
|
||||||
if (!d_is_directory(old_dentry) && !d_is_autodir(old_dentry)) {
|
|
||||||
error = -ENOTDIR;
|
|
||||||
if (oldnd.last.name[oldnd.last.len])
|
|
||||||
goto exit4;
|
|
||||||
if (newnd.last.name[newnd.last.len])
|
|
||||||
goto exit4;
|
|
||||||
}
|
|
||||||
/* source should not be ancestor of target */
|
|
||||||
error = -EINVAL;
|
|
||||||
if (old_dentry == trap)
|
|
||||||
goto exit4;
|
|
||||||
new_dentry = lookup_hash(&newnd);
|
new_dentry = lookup_hash(&newnd);
|
||||||
error = PTR_ERR(new_dentry);
|
error = PTR_ERR(new_dentry);
|
||||||
if (IS_ERR(new_dentry))
|
if (IS_ERR(new_dentry))
|
||||||
goto exit4;
|
goto exit4;
|
||||||
|
error = -EEXIST;
|
||||||
|
if ((flags & RENAME_NOREPLACE) && d_is_positive(new_dentry))
|
||||||
|
goto exit5;
|
||||||
|
if (flags & RENAME_EXCHANGE) {
|
||||||
|
error = -ENOENT;
|
||||||
|
if (d_is_negative(new_dentry))
|
||||||
|
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 (!(flags & RENAME_EXCHANGE) && newnd.last.name[newnd.last.len])
|
||||||
|
goto exit5;
|
||||||
|
}
|
||||||
|
/* source should not be ancestor of target */
|
||||||
|
error = -EINVAL;
|
||||||
|
if (old_dentry == trap)
|
||||||
|
goto exit5;
|
||||||
/* target should not be an ancestor of source */
|
/* target should not be an ancestor of source */
|
||||||
error = -ENOTEMPTY;
|
if (!(flags & RENAME_EXCHANGE))
|
||||||
|
error = -ENOTEMPTY;
|
||||||
if (new_dentry == trap)
|
if (new_dentry == trap)
|
||||||
goto exit5;
|
goto exit5;
|
||||||
|
|
||||||
error = security_path_rename(&oldnd.path, old_dentry,
|
error = security_path_rename(&oldnd.path, old_dentry,
|
||||||
&newnd.path, new_dentry);
|
&newnd.path, new_dentry, flags);
|
||||||
if (error)
|
if (error)
|
||||||
goto exit5;
|
goto exit5;
|
||||||
error = vfs_rename(old_dir->d_inode, old_dentry,
|
error = vfs_rename(old_dir->d_inode, old_dentry,
|
||||||
new_dir->d_inode, new_dentry,
|
new_dir->d_inode, new_dentry,
|
||||||
&delegated_inode);
|
&delegated_inode, flags);
|
||||||
exit5:
|
exit5:
|
||||||
dput(new_dentry);
|
dput(new_dentry);
|
||||||
exit4:
|
exit4:
|
||||||
@ -4272,9 +4293,15 @@ SYSCALL_DEFINE4(renameat, int, olddfd, const char __user *, oldname,
|
|||||||
return error;
|
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)
|
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)
|
int vfs_readlink(struct dentry *dentry, char __user *buffer, int buflen, const char *link)
|
||||||
|
@ -1694,7 +1694,7 @@ nfsd_rename(struct svc_rqst *rqstp, struct svc_fh *ffhp, char *fname, int flen,
|
|||||||
if (ffhp->fh_export->ex_path.dentry != tfhp->fh_export->ex_path.dentry)
|
if (ffhp->fh_export->ex_path.dentry != tfhp->fh_export->ex_path.dentry)
|
||||||
goto out_dput_new;
|
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) {
|
if (!host_err) {
|
||||||
host_err = commit_metadata(tfhp);
|
host_err = commit_metadata(tfhp);
|
||||||
if (!host_err)
|
if (!host_err)
|
||||||
|
@ -308,6 +308,7 @@ extern void dentry_update_name_case(struct dentry *, struct qstr *);
|
|||||||
|
|
||||||
/* used for rename() and baskets */
|
/* used for rename() and baskets */
|
||||||
extern void d_move(struct dentry *, struct dentry *);
|
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 *);
|
extern struct dentry *d_ancestor(struct dentry *, struct dentry *);
|
||||||
|
|
||||||
/* appendix may either be NULL or be used for transname suffixes */
|
/* appendix may either be NULL or be used for transname suffixes */
|
||||||
@ -429,7 +430,7 @@ static inline unsigned __d_entry_type(const struct dentry *dentry)
|
|||||||
return dentry->d_flags & DCACHE_ENTRY_TYPE;
|
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;
|
return __d_entry_type(dentry) == DCACHE_DIRECTORY_TYPE;
|
||||||
}
|
}
|
||||||
@ -439,6 +440,11 @@ static inline bool d_is_autodir(const struct dentry *dentry)
|
|||||||
return __d_entry_type(dentry) == DCACHE_AUTODIR_TYPE;
|
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)
|
static inline bool d_is_symlink(const struct dentry *dentry)
|
||||||
{
|
{
|
||||||
return __d_entry_type(dentry) == DCACHE_SYMLINK_TYPE;
|
return __d_entry_type(dentry) == DCACHE_SYMLINK_TYPE;
|
||||||
|
@ -1461,7 +1461,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_link(struct dentry *, struct inode *, struct dentry *, struct inode **);
|
||||||
extern int vfs_rmdir(struct inode *, struct dentry *);
|
extern int vfs_rmdir(struct inode *, struct dentry *);
|
||||||
extern int vfs_unlink(struct inode *, struct dentry *, struct inode **);
|
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.
|
* VFS dentry helper functions.
|
||||||
@ -1572,6 +1572,8 @@ struct inode_operations {
|
|||||||
int (*mknod) (struct inode *,struct dentry *,umode_t,dev_t);
|
int (*mknod) (struct inode *,struct dentry *,umode_t,dev_t);
|
||||||
int (*rename) (struct inode *, struct dentry *,
|
int (*rename) (struct inode *, struct dentry *,
|
||||||
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 (*setattr) (struct dentry *, struct iattr *);
|
||||||
int (*getattr) (struct vfsmount *mnt, struct dentry *, struct kstat *);
|
int (*getattr) (struct vfsmount *mnt, struct dentry *, struct kstat *);
|
||||||
int (*setxattr) (struct dentry *, const char *,const void *,size_t,int);
|
int (*setxattr) (struct dentry *, const char *,const void *,size_t,int);
|
||||||
|
@ -1793,7 +1793,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_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_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,
|
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_readlink(struct dentry *dentry);
|
||||||
int security_inode_follow_link(struct dentry *dentry, struct nameidata *nd);
|
int security_inode_follow_link(struct dentry *dentry, struct nameidata *nd);
|
||||||
int security_inode_permission(struct inode *inode, int mask);
|
int security_inode_permission(struct inode *inode, int mask);
|
||||||
@ -2161,7 +2162,8 @@ static inline int security_inode_mknod(struct inode *dir,
|
|||||||
static inline int security_inode_rename(struct inode *old_dir,
|
static inline int security_inode_rename(struct inode *old_dir,
|
||||||
struct dentry *old_dentry,
|
struct dentry *old_dentry,
|
||||||
struct inode *new_dir,
|
struct inode *new_dir,
|
||||||
struct dentry *new_dentry)
|
struct dentry *new_dentry,
|
||||||
|
unsigned int flags)
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -2955,7 +2957,8 @@ int security_path_symlink(struct path *dir, struct dentry *dentry,
|
|||||||
int security_path_link(struct dentry *old_dentry, struct path *new_dir,
|
int security_path_link(struct dentry *old_dentry, struct path *new_dir,
|
||||||
struct dentry *new_dentry);
|
struct dentry *new_dentry);
|
||||||
int security_path_rename(struct path *old_dir, struct dentry *old_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_chmod(struct path *path, umode_t mode);
|
||||||
int security_path_chown(struct path *path, kuid_t uid, kgid_t gid);
|
int security_path_chown(struct path *path, kuid_t uid, kgid_t gid);
|
||||||
int security_path_chroot(struct path *path);
|
int security_path_chroot(struct path *path);
|
||||||
@ -3003,7 +3006,8 @@ static inline int security_path_link(struct dentry *old_dentry,
|
|||||||
static inline int security_path_rename(struct path *old_dir,
|
static inline int security_path_rename(struct path *old_dir,
|
||||||
struct dentry *old_dentry,
|
struct dentry *old_dentry,
|
||||||
struct path *new_dir,
|
struct path *new_dir,
|
||||||
struct dentry *new_dentry)
|
struct dentry *new_dentry,
|
||||||
|
unsigned int flags)
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,9 @@
|
|||||||
#define SEEK_HOLE 4 /* seek to the next hole */
|
#define SEEK_HOLE 4 /* seek to the next hole */
|
||||||
#define SEEK_MAX SEEK_HOLE
|
#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 {
|
struct fstrim_range {
|
||||||
__u64 start;
|
__u64 start;
|
||||||
__u64 len;
|
__u64 len;
|
||||||
|
@ -433,11 +433,20 @@ int security_path_link(struct dentry *old_dentry, struct path *new_dir,
|
|||||||
}
|
}
|
||||||
|
|
||||||
int security_path_rename(struct path *old_dir, struct dentry *old_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)
|
||||||
{
|
{
|
||||||
if (unlikely(IS_PRIVATE(old_dentry->d_inode) ||
|
if (unlikely(IS_PRIVATE(old_dentry->d_inode) ||
|
||||||
(new_dentry->d_inode && IS_PRIVATE(new_dentry->d_inode))))
|
(new_dentry->d_inode && IS_PRIVATE(new_dentry->d_inode))))
|
||||||
return 0;
|
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,
|
return security_ops->path_rename(old_dir, old_dentry, new_dir,
|
||||||
new_dentry);
|
new_dentry);
|
||||||
}
|
}
|
||||||
@ -524,11 +533,20 @@ 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,
|
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) ||
|
if (unlikely(IS_PRIVATE(old_dentry->d_inode) ||
|
||||||
(new_dentry->d_inode && IS_PRIVATE(new_dentry->d_inode))))
|
(new_dentry->d_inode && IS_PRIVATE(new_dentry->d_inode))))
|
||||||
return 0;
|
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,
|
return security_ops->inode_rename(old_dir, old_dentry,
|
||||||
new_dir, new_dentry);
|
new_dir, new_dentry);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user