Skip to content

Commit bd42998

Browse files
author
Miklos Szeredi
committed
ext4: add cross rename support
Implement RENAME_EXCHANGE flag in renameat2 syscall. Signed-off-by: Miklos Szeredi <[email protected]> Reviewed-by: Jan Kara <[email protected]>
1 parent bd1af14 commit bd42998

File tree

1 file changed

+138
-1
lines changed

1 file changed

+138
-1
lines changed

fs/ext4/namei.c

Lines changed: 138 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3004,6 +3004,8 @@ struct ext4_renament {
30043004
struct inode *dir;
30053005
struct dentry *dentry;
30063006
struct inode *inode;
3007+
bool is_dir;
3008+
int dir_nlink_delta;
30073009

30083010
/* entry for "dentry" */
30093011
struct buffer_head *bh;
@@ -3135,6 +3137,17 @@ static void ext4_rename_delete(handle_t *handle, struct ext4_renament *ent)
31353137
}
31363138
}
31373139

3140+
static void ext4_update_dir_count(handle_t *handle, struct ext4_renament *ent)
3141+
{
3142+
if (ent->dir_nlink_delta) {
3143+
if (ent->dir_nlink_delta == -1)
3144+
ext4_dec_count(handle, ent->dir);
3145+
else
3146+
ext4_inc_count(handle, ent->dir);
3147+
ext4_mark_inode_dirty(handle, ent->dir);
3148+
}
3149+
}
3150+
31383151
/*
31393152
* Anybody can rename anything with this: the permission checks are left to the
31403153
* higher-level routines.
@@ -3274,13 +3287,137 @@ static int ext4_rename(struct inode *old_dir, struct dentry *old_dentry,
32743287
return retval;
32753288
}
32763289

3290+
static int ext4_cross_rename(struct inode *old_dir, struct dentry *old_dentry,
3291+
struct inode *new_dir, struct dentry *new_dentry)
3292+
{
3293+
handle_t *handle = NULL;
3294+
struct ext4_renament old = {
3295+
.dir = old_dir,
3296+
.dentry = old_dentry,
3297+
.inode = old_dentry->d_inode,
3298+
};
3299+
struct ext4_renament new = {
3300+
.dir = new_dir,
3301+
.dentry = new_dentry,
3302+
.inode = new_dentry->d_inode,
3303+
};
3304+
u8 new_file_type;
3305+
int retval;
3306+
3307+
dquot_initialize(old.dir);
3308+
dquot_initialize(new.dir);
3309+
3310+
old.bh = ext4_find_entry(old.dir, &old.dentry->d_name,
3311+
&old.de, &old.inlined);
3312+
/*
3313+
* Check for inode number is _not_ due to possible IO errors.
3314+
* We might rmdir the source, keep it as pwd of some process
3315+
* and merrily kill the link to whatever was created under the
3316+
* same name. Goodbye sticky bit ;-<
3317+
*/
3318+
retval = -ENOENT;
3319+
if (!old.bh || le32_to_cpu(old.de->inode) != old.inode->i_ino)
3320+
goto end_rename;
3321+
3322+
new.bh = ext4_find_entry(new.dir, &new.dentry->d_name,
3323+
&new.de, &new.inlined);
3324+
3325+
/* RENAME_EXCHANGE case: old *and* new must both exist */
3326+
if (!new.bh || le32_to_cpu(new.de->inode) != new.inode->i_ino)
3327+
goto end_rename;
3328+
3329+
handle = ext4_journal_start(old.dir, EXT4_HT_DIR,
3330+
(2 * EXT4_DATA_TRANS_BLOCKS(old.dir->i_sb) +
3331+
2 * EXT4_INDEX_EXTRA_TRANS_BLOCKS + 2));
3332+
if (IS_ERR(handle))
3333+
return PTR_ERR(handle);
3334+
3335+
if (IS_DIRSYNC(old.dir) || IS_DIRSYNC(new.dir))
3336+
ext4_handle_sync(handle);
3337+
3338+
if (S_ISDIR(old.inode->i_mode)) {
3339+
old.is_dir = true;
3340+
retval = ext4_rename_dir_prepare(handle, &old);
3341+
if (retval)
3342+
goto end_rename;
3343+
}
3344+
if (S_ISDIR(new.inode->i_mode)) {
3345+
new.is_dir = true;
3346+
retval = ext4_rename_dir_prepare(handle, &new);
3347+
if (retval)
3348+
goto end_rename;
3349+
}
3350+
3351+
/*
3352+
* Other than the special case of overwriting a directory, parents'
3353+
* nlink only needs to be modified if this is a cross directory rename.
3354+
*/
3355+
if (old.dir != new.dir && old.is_dir != new.is_dir) {
3356+
old.dir_nlink_delta = old.is_dir ? -1 : 1;
3357+
new.dir_nlink_delta = -old.dir_nlink_delta;
3358+
retval = -EMLINK;
3359+
if ((old.dir_nlink_delta > 0 && EXT4_DIR_LINK_MAX(old.dir)) ||
3360+
(new.dir_nlink_delta > 0 && EXT4_DIR_LINK_MAX(new.dir)))
3361+
goto end_rename;
3362+
}
3363+
3364+
new_file_type = new.de->file_type;
3365+
retval = ext4_setent(handle, &new, old.inode->i_ino, old.de->file_type);
3366+
if (retval)
3367+
goto end_rename;
3368+
3369+
retval = ext4_setent(handle, &old, new.inode->i_ino, new_file_type);
3370+
if (retval)
3371+
goto end_rename;
3372+
3373+
/*
3374+
* Like most other Unix systems, set the ctime for inodes on a
3375+
* rename.
3376+
*/
3377+
old.inode->i_ctime = ext4_current_time(old.inode);
3378+
new.inode->i_ctime = ext4_current_time(new.inode);
3379+
ext4_mark_inode_dirty(handle, old.inode);
3380+
ext4_mark_inode_dirty(handle, new.inode);
3381+
3382+
if (old.dir_bh) {
3383+
retval = ext4_rename_dir_finish(handle, &old, new.dir->i_ino);
3384+
if (retval)
3385+
goto end_rename;
3386+
}
3387+
if (new.dir_bh) {
3388+
retval = ext4_rename_dir_finish(handle, &new, old.dir->i_ino);
3389+
if (retval)
3390+
goto end_rename;
3391+
}
3392+
ext4_update_dir_count(handle, &old);
3393+
ext4_update_dir_count(handle, &new);
3394+
retval = 0;
3395+
3396+
end_rename:
3397+
brelse(old.dir_bh);
3398+
brelse(new.dir_bh);
3399+
brelse(old.bh);
3400+
brelse(new.bh);
3401+
if (handle)
3402+
ext4_journal_stop(handle);
3403+
return retval;
3404+
}
3405+
32773406
static int ext4_rename2(struct inode *old_dir, struct dentry *old_dentry,
32783407
struct inode *new_dir, struct dentry *new_dentry,
32793408
unsigned int flags)
32803409
{
3281-
if (flags & ~RENAME_NOREPLACE)
3410+
if (flags & ~(RENAME_NOREPLACE | RENAME_EXCHANGE))
32823411
return -EINVAL;
32833412

3413+
if (flags & RENAME_EXCHANGE) {
3414+
return ext4_cross_rename(old_dir, old_dentry,
3415+
new_dir, new_dentry);
3416+
}
3417+
/*
3418+
* Existence checking was done by the VFS, otherwise "RENAME_NOREPLACE"
3419+
* is equivalent to regular rename.
3420+
*/
32843421
return ext4_rename(old_dir, old_dentry, new_dir, new_dentry);
32853422
}
32863423

0 commit comments

Comments
 (0)