ext4: add cross rename support
Implement RENAME_EXCHANGE flag in renameat2 syscall. Signed-off-by: Miklos Szeredi <mszeredi@suse.cz> Reviewed-by: Jan Kara <jack@suse.cz>
This commit is contained in:
parent
bd1af145b9
commit
bd42998a6b
139
fs/ext4/namei.c
139
fs/ext4/namei.c
|
@ -3004,6 +3004,8 @@ 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;
|
||||
|
@ -3135,6 +3137,17 @@ static void ext4_rename_delete(handle_t *handle, struct ext4_renament *ent)
|
|||
}
|
||||
}
|
||||
|
||||
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.
|
||||
|
@ -3274,13 +3287,137 @@ end_rename:
|
|||
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)
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue