diff options
author | Miklos Szeredi <mszeredi@suse.cz> | 2014-04-01 11:08:44 -0400 |
---|---|---|
committer | Miklos Szeredi <mszeredi@suse.cz> | 2014-04-01 11:08:44 -0400 |
commit | bd42998a6bcb9b1708dac9ca9876e3d304c16f3d (patch) | |
tree | 4e76a14bd33ad0499ed75e2aa13cc4516539707b /fs/ext4 | |
parent | bd1af145b99311242673b32dff4599ce614352be (diff) |
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>
Diffstat (limited to 'fs/ext4')
-rw-r--r-- | fs/ext4/namei.c | 139 |
1 files changed, 138 insertions, 1 deletions
diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c index 75f1bde43dcc..1cb84f78909e 100644 --- a/fs/ext4/namei.c +++ b/fs/ext4/namei.c | |||
@@ -3004,6 +3004,8 @@ struct ext4_renament { | |||
3004 | struct inode *dir; | 3004 | struct inode *dir; |
3005 | struct dentry *dentry; | 3005 | struct dentry *dentry; |
3006 | struct inode *inode; | 3006 | struct inode *inode; |
3007 | bool is_dir; | ||
3008 | int dir_nlink_delta; | ||
3007 | 3009 | ||
3008 | /* entry for "dentry" */ | 3010 | /* entry for "dentry" */ |
3009 | struct buffer_head *bh; | 3011 | struct buffer_head *bh; |
@@ -3135,6 +3137,17 @@ static void ext4_rename_delete(handle_t *handle, struct ext4_renament *ent) | |||
3135 | } | 3137 | } |
3136 | } | 3138 | } |
3137 | 3139 | ||
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 | |||
3138 | /* | 3151 | /* |
3139 | * Anybody can rename anything with this: the permission checks are left to the | 3152 | * Anybody can rename anything with this: the permission checks are left to the |
3140 | * higher-level routines. | 3153 | * higher-level routines. |
@@ -3274,13 +3287,137 @@ end_rename: | |||
3274 | return retval; | 3287 | return retval; |
3275 | } | 3288 | } |
3276 | 3289 | ||
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 | |||
3277 | static int ext4_rename2(struct inode *old_dir, struct dentry *old_dentry, | 3406 | static int ext4_rename2(struct inode *old_dir, struct dentry *old_dentry, |
3278 | struct inode *new_dir, struct dentry *new_dentry, | 3407 | struct inode *new_dir, struct dentry *new_dentry, |
3279 | unsigned int flags) | 3408 | unsigned int flags) |
3280 | { | 3409 | { |
3281 | if (flags & ~RENAME_NOREPLACE) | 3410 | if (flags & ~(RENAME_NOREPLACE | RENAME_EXCHANGE)) |
3282 | return -EINVAL; | 3411 | return -EINVAL; |
3283 | 3412 | ||
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 | */ | ||
3284 | return ext4_rename(old_dir, old_dentry, new_dir, new_dentry); | 3421 | return ext4_rename(old_dir, old_dentry, new_dir, new_dentry); |
3285 | } | 3422 | } |
3286 | 3423 | ||