forked from Minki/linux
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 (*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 *);
|
||||
@ -78,6 +80,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
|
||||
@ -96,7 +99,8 @@ tmpfile: no
|
||||
|
||||
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.
|
||||
|
@ -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 *);
|
||||
@ -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
|
||||
|
||||
|
@ -322,6 +322,7 @@
|
||||
313 common finit_module sys_finit_module
|
||||
314 common sched_setattr sys_sched_setattr
|
||||
315 common sched_getattr sys_sched_getattr
|
||||
316 common renameat2 sys_renameat2
|
||||
|
||||
#
|
||||
# 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_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))
|
||||
|
@ -223,7 +223,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:
|
||||
|
@ -391,12 +391,12 @@ 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 {
|
||||
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);
|
||||
|
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;
|
||||
} 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);
|
||||
@ -2545,13 +2547,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");
|
||||
@ -2573,8 +2577,15 @@ static void __d_move(struct dentry * dentry, struct dentry * target)
|
||||
__d_drop(dentry);
|
||||
__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);
|
||||
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);
|
||||
@ -2601,6 +2612,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);
|
||||
@ -2618,11 +2631,30 @@ 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
|
||||
@ -2670,7 +2702,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:
|
||||
|
@ -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,
|
||||
lower_new_dir_dentry->d_inode, lower_new_dentry,
|
||||
NULL);
|
||||
NULL, 0);
|
||||
if (rc)
|
||||
goto out_lock;
|
||||
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);
|
||||
}
|
||||
|
||||
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
|
||||
* 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)
|
||||
{
|
||||
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;
|
||||
|
||||
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;
|
||||
} else {
|
||||
retval = -EMLINK;
|
||||
if (new.dir != old.dir && EXT4_DIR_LINK_MAX(new.dir))
|
||||
goto end_rename;
|
||||
}
|
||||
retval = -EIO;
|
||||
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);
|
||||
retval = ext4_rename_dir_prepare(handle, &old);
|
||||
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);
|
||||
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;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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
|
||||
* 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;
|
||||
ext4_rename_delete(handle, &old);
|
||||
|
||||
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 (new.inode) {
|
||||
ext4_dec_count(handle, new.inode);
|
||||
new.inode->i_ctime = ext4_current_time(new.inode);
|
||||
}
|
||||
if (retval) {
|
||||
ext4_warning(old_dir->i_sb,
|
||||
"Deleting old file (%lu), %d, error=%d",
|
||||
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);
|
||||
}
|
||||
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);
|
||||
old.dir->i_ctime = old.dir->i_mtime = ext4_current_time(old.dir);
|
||||
ext4_update_dx_flag(old.dir);
|
||||
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) {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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...
|
||||
*/
|
||||
@ -3218,6 +3435,7 @@ const struct inode_operations ext4_dir_inode_operations = {
|
||||
.mknod = ext4_mknod,
|
||||
.tmpfile = ext4_tmpfile,
|
||||
.rename = ext4_rename,
|
||||
.rename2 = ext4_rename2,
|
||||
.setattr = ext4_setattr,
|
||||
.setxattr = generic_setxattr,
|
||||
.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 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
|
||||
* @inode2: second inode to lock
|
||||
*/
|
||||
void lock_two_nondirectories(struct inode *inode1, struct inode *inode2)
|
||||
{
|
||||
WARN_ON_ONCE(S_ISDIR(inode1->i_mode));
|
||||
if (inode1 == inode2 || !inode2) {
|
||||
mutex_lock(&inode1->i_mutex);
|
||||
return;
|
||||
}
|
||||
WARN_ON_ONCE(S_ISDIR(inode2->i_mode));
|
||||
if (inode1 < inode2) {
|
||||
if (inode1 > inode2)
|
||||
swap(inode1, inode2);
|
||||
|
||||
if (inode1 && !S_ISDIR(inode1->i_mode))
|
||||
mutex_lock(&inode1->i_mutex);
|
||||
if (inode2 && !S_ISDIR(inode2->i_mode) && inode2 != inode1)
|
||||
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);
|
||||
|
||||
@ -972,8 +970,9 @@ EXPORT_SYMBOL(lock_two_nondirectories);
|
||||
*/
|
||||
void unlock_two_nondirectories(struct inode *inode1, struct inode *inode2)
|
||||
{
|
||||
mutex_unlock(&inode1->i_mutex);
|
||||
if (inode2 && inode2 != inode1)
|
||||
if (inode1 && !S_ISDIR(inode1->i_mode))
|
||||
mutex_unlock(&inode1->i_mutex);
|
||||
if (inode2 && !S_ISDIR(inode2->i_mode) && inode2 != inode1)
|
||||
mutex_unlock(&inode2->i_mutex);
|
||||
}
|
||||
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)
|
||||
return err;
|
||||
}
|
||||
if (!d_is_directory(nd->path.dentry)) {
|
||||
if (!d_can_lookup(nd->path.dentry)) {
|
||||
err = -ENOTDIR;
|
||||
break;
|
||||
}
|
||||
@ -1817,7 +1817,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)
|
||||
@ -1873,7 +1873,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;
|
||||
}
|
||||
@ -1955,7 +1955,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;
|
||||
}
|
||||
@ -2414,11 +2414,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;
|
||||
@ -3016,11 +3016,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;
|
||||
@ -3744,7 +3743,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;
|
||||
@ -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);
|
||||
}
|
||||
|
||||
/*
|
||||
/**
|
||||
* 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"
|
||||
* doesn't even start to describe it. Somebody in UCB had a heck of a trip...
|
||||
* 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
|
||||
* 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,
|
||||
struct inode *new_dir, struct dentry *new_dentry,
|
||||
struct inode **delegated_inode)
|
||||
struct inode **delegated_inode, unsigned int flags)
|
||||
{
|
||||
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;
|
||||
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);
|
||||
if (error)
|
||||
return error;
|
||||
|
||||
if (!new_dentry->d_inode)
|
||||
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;
|
||||
|
||||
if (!old_dir->i_op->rename)
|
||||
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);
|
||||
else
|
||||
error = vfs_rename_other(old_dir,old_dentry,new_dir,new_dentry,delegated_inode);
|
||||
if (!error)
|
||||
/*
|
||||
* If we are going to change the parent - check write permissions,
|
||||
* we'll need to flip '..'.
|
||||
*/
|
||||
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,
|
||||
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);
|
||||
|
||||
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;
|
||||
@ -4170,6 +4166,13 @@ SYSCALL_DEFINE4(renameat, int, olddfd, const char __user *, oldname,
|
||||
unsigned int lookup_flags = 0;
|
||||
bool should_retry = false;
|
||||
int error;
|
||||
|
||||
if (flags & ~(RENAME_NOREPLACE | RENAME_EXCHANGE))
|
||||
return -EINVAL;
|
||||
|
||||
if ((flags & RENAME_NOREPLACE) && (flags & RENAME_EXCHANGE))
|
||||
return -EINVAL;
|
||||
|
||||
retry:
|
||||
from = user_path_parent(olddfd, oldname, &oldnd, lookup_flags);
|
||||
if (IS_ERR(from)) {
|
||||
@ -4193,6 +4196,8 @@ retry:
|
||||
goto exit2;
|
||||
|
||||
new_dir = newnd.path.dentry;
|
||||
if (flags & RENAME_NOREPLACE)
|
||||
error = -EEXIST;
|
||||
if (newnd.last_type != LAST_NORM)
|
||||
goto exit2;
|
||||
|
||||
@ -4202,7 +4207,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);
|
||||
@ -4215,34 +4221,49 @@ retry_deleg:
|
||||
error = -ENOENT;
|
||||
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)) {
|
||||
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);
|
||||
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;
|
||||
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 */
|
||||
error = -ENOTEMPTY;
|
||||
if (!(flags & RENAME_EXCHANGE))
|
||||
error = -ENOTEMPTY;
|
||||
if (new_dentry == trap)
|
||||
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,
|
||||
new_dir->d_inode, new_dentry,
|
||||
&delegated_inode);
|
||||
new_dir->d_inode, new_dentry,
|
||||
&delegated_inode, flags);
|
||||
exit5:
|
||||
dput(new_dentry);
|
||||
exit4:
|
||||
@ -4272,9 +4293,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)
|
||||
|
@ -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)
|
||||
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)
|
||||
|
@ -308,6 +308,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 */
|
||||
@ -429,7 +430,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;
|
||||
}
|
||||
@ -439,6 +440,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;
|
||||
|
@ -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_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.
|
||||
@ -1572,6 +1572,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 (*getattr) (struct vfsmount *mnt, struct dentry *, struct kstat *);
|
||||
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_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);
|
||||
@ -2161,7 +2162,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;
|
||||
}
|
||||
@ -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,
|
||||
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);
|
||||
@ -3003,7 +3006,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;
|
||||
}
|
||||
|
@ -35,6 +35,9 @@
|
||||
#define SEEK_HOLE 4 /* seek to the next 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 {
|
||||
__u64 start;
|
||||
__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,
|
||||
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))))
|
||||
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);
|
||||
}
|
||||
@ -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,
|
||||
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))))
|
||||
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);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user