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:
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,
|
||||
|
Reference in New Issue
Block a user