diff options
| author | Miklos Szeredi <mszeredi@suse.cz> | 2014-04-01 11:08:43 -0400 |
|---|---|---|
| committer | Miklos Szeredi <mszeredi@suse.cz> | 2014-04-01 11:08:43 -0400 |
| commit | da1ce0670c14d8380e423a3239e562a1dc15fa9e (patch) | |
| tree | 146ea4ac0fbf5550db8e65a59ddc7c668b68db76 | |
| parent | 4fd699ae3fbca2ac760137e1d26f98a105f59f05 (diff) | |
vfs: add cross-rename
If flags contain RENAME_EXCHANGE then exchange source and destination files.
There's no restriction on the type of the files; e.g. a directory can be
exchanged with a symlink.
Signed-off-by: Miklos Szeredi <mszeredi@suse.cz>
Reviewed-by: Jan Kara <jack@suse.cz>
Reviewed-by: J. Bruce Fields <bfields@redhat.com>
| -rw-r--r-- | fs/dcache.c | 50 | ||||
| -rw-r--r-- | fs/namei.c | 104 | ||||
| -rw-r--r-- | include/linux/dcache.h | 1 | ||||
| -rw-r--r-- | include/uapi/linux/fs.h | 1 | ||||
| -rw-r--r-- | security/security.c | 16 |
5 files changed, 131 insertions, 41 deletions
diff --git a/fs/dcache.c b/fs/dcache.c index ca02c13a84aa..66cba5a8a346 100644 --- a/fs/dcache.c +++ b/fs/dcache.c | |||
| @@ -2483,12 +2483,14 @@ static void switch_names(struct dentry *dentry, struct dentry *target) | |||
| 2483 | dentry->d_name.name = dentry->d_iname; | 2483 | dentry->d_name.name = dentry->d_iname; |
| 2484 | } else { | 2484 | } else { |
| 2485 | /* | 2485 | /* |
| 2486 | * Both are internal. Just copy target to dentry | 2486 | * Both are internal. |
| 2487 | */ | 2487 | */ |
| 2488 | memcpy(dentry->d_iname, target->d_name.name, | 2488 | unsigned int i; |
| 2489 | target->d_name.len + 1); | 2489 | BUILD_BUG_ON(!IS_ALIGNED(DNAME_INLINE_LEN, sizeof(long))); |
| 2490 | dentry->d_name.len = target->d_name.len; | 2490 | for (i = 0; i < DNAME_INLINE_LEN / sizeof(long); i++) { |
| 2491 | return; | 2491 | swap(((long *) &dentry->d_iname)[i], |
| 2492 | ((long *) &target->d_iname)[i]); | ||
| 2493 | } | ||
| 2492 | } | 2494 | } |
| 2493 | } | 2495 | } |
| 2494 | swap(dentry->d_name.len, target->d_name.len); | 2496 | swap(dentry->d_name.len, target->d_name.len); |
| @@ -2545,13 +2547,15 @@ static void dentry_unlock_parents_for_move(struct dentry *dentry, | |||
| 2545 | * __d_move - move a dentry | 2547 | * __d_move - move a dentry |
| 2546 | * @dentry: entry to move | 2548 | * @dentry: entry to move |
| 2547 | * @target: new dentry | 2549 | * @target: new dentry |
| 2550 | * @exchange: exchange the two dentries | ||
| 2548 | * | 2551 | * |
| 2549 | * Update the dcache to reflect the move of a file name. Negative | 2552 | * Update the dcache to reflect the move of a file name. Negative |
| 2550 | * dcache entries should not be moved in this way. Caller must hold | 2553 | * dcache entries should not be moved in this way. Caller must hold |
| 2551 | * rename_lock, the i_mutex of the source and target directories, | 2554 | * rename_lock, the i_mutex of the source and target directories, |
| 2552 | * and the sb->s_vfs_rename_mutex if they differ. See lock_rename(). | 2555 | * and the sb->s_vfs_rename_mutex if they differ. See lock_rename(). |
| 2553 | */ | 2556 | */ |
| 2554 | static void __d_move(struct dentry * dentry, struct dentry * target) | 2557 | static void __d_move(struct dentry *dentry, struct dentry *target, |
| 2558 | bool exchange) | ||
| 2555 | { | 2559 | { |
| 2556 | if (!dentry->d_inode) | 2560 | if (!dentry->d_inode) |
| 2557 | printk(KERN_WARNING "VFS: moving negative dcache entry\n"); | 2561 | printk(KERN_WARNING "VFS: moving negative dcache entry\n"); |
| @@ -2573,8 +2577,15 @@ static void __d_move(struct dentry * dentry, struct dentry * target) | |||
| 2573 | __d_drop(dentry); | 2577 | __d_drop(dentry); |
| 2574 | __d_rehash(dentry, d_hash(target->d_parent, target->d_name.hash)); | 2578 | __d_rehash(dentry, d_hash(target->d_parent, target->d_name.hash)); |
| 2575 | 2579 | ||
| 2576 | /* Unhash the target: dput() will then get rid of it */ | 2580 | /* |
| 2581 | * Unhash the target (d_delete() is not usable here). If exchanging | ||
| 2582 | * the two dentries, then rehash onto the other's hash queue. | ||
| 2583 | */ | ||
| 2577 | __d_drop(target); | 2584 | __d_drop(target); |
| 2585 | if (exchange) { | ||
| 2586 | __d_rehash(target, | ||
| 2587 | d_hash(dentry->d_parent, dentry->d_name.hash)); | ||
| 2588 | } | ||
| 2578 | 2589 | ||
| 2579 | list_del(&dentry->d_u.d_child); | 2590 | list_del(&dentry->d_u.d_child); |
| 2580 | list_del(&target->d_u.d_child); | 2591 | list_del(&target->d_u.d_child); |
| @@ -2601,6 +2612,8 @@ static void __d_move(struct dentry * dentry, struct dentry * target) | |||
| 2601 | write_seqcount_end(&dentry->d_seq); | 2612 | write_seqcount_end(&dentry->d_seq); |
| 2602 | 2613 | ||
| 2603 | dentry_unlock_parents_for_move(dentry, target); | 2614 | dentry_unlock_parents_for_move(dentry, target); |
| 2615 | if (exchange) | ||
| 2616 | fsnotify_d_move(target); | ||
| 2604 | spin_unlock(&target->d_lock); | 2617 | spin_unlock(&target->d_lock); |
| 2605 | fsnotify_d_move(dentry); | 2618 | fsnotify_d_move(dentry); |
| 2606 | spin_unlock(&dentry->d_lock); | 2619 | spin_unlock(&dentry->d_lock); |
| @@ -2618,11 +2631,30 @@ static void __d_move(struct dentry * dentry, struct dentry * target) | |||
| 2618 | void d_move(struct dentry *dentry, struct dentry *target) | 2631 | void d_move(struct dentry *dentry, struct dentry *target) |
| 2619 | { | 2632 | { |
| 2620 | write_seqlock(&rename_lock); | 2633 | write_seqlock(&rename_lock); |
| 2621 | __d_move(dentry, target); | 2634 | __d_move(dentry, target, false); |
| 2622 | write_sequnlock(&rename_lock); | 2635 | write_sequnlock(&rename_lock); |
| 2623 | } | 2636 | } |
| 2624 | EXPORT_SYMBOL(d_move); | 2637 | EXPORT_SYMBOL(d_move); |
| 2625 | 2638 | ||
| 2639 | /* | ||
| 2640 | * d_exchange - exchange two dentries | ||
| 2641 | * @dentry1: first dentry | ||
| 2642 | * @dentry2: second dentry | ||
| 2643 | */ | ||
| 2644 | void d_exchange(struct dentry *dentry1, struct dentry *dentry2) | ||
| 2645 | { | ||
| 2646 | write_seqlock(&rename_lock); | ||
| 2647 | |||
| 2648 | WARN_ON(!dentry1->d_inode); | ||
| 2649 | WARN_ON(!dentry2->d_inode); | ||
| 2650 | WARN_ON(IS_ROOT(dentry1)); | ||
| 2651 | WARN_ON(IS_ROOT(dentry2)); | ||
| 2652 | |||
| 2653 | __d_move(dentry1, dentry2, true); | ||
| 2654 | |||
| 2655 | write_sequnlock(&rename_lock); | ||
| 2656 | } | ||
| 2657 | |||
| 2626 | /** | 2658 | /** |
| 2627 | * d_ancestor - search for an ancestor | 2659 | * d_ancestor - search for an ancestor |
| 2628 | * @p1: ancestor dentry | 2660 | * @p1: ancestor dentry |
| @@ -2670,7 +2702,7 @@ static struct dentry *__d_unalias(struct inode *inode, | |||
| 2670 | m2 = &alias->d_parent->d_inode->i_mutex; | 2702 | m2 = &alias->d_parent->d_inode->i_mutex; |
| 2671 | out_unalias: | 2703 | out_unalias: |
| 2672 | if (likely(!d_mountpoint(alias))) { | 2704 | if (likely(!d_mountpoint(alias))) { |
| 2673 | __d_move(alias, dentry); | 2705 | __d_move(alias, dentry, false); |
| 2674 | ret = alias; | 2706 | ret = alias; |
| 2675 | } | 2707 | } |
| 2676 | out_err: | 2708 | out_err: |
diff --git a/fs/namei.c b/fs/namei.c index 4096d589bb3f..c1178880f23c 100644 --- a/fs/namei.c +++ b/fs/namei.c | |||
| @@ -4031,6 +4031,8 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry, | |||
| 4031 | const unsigned char *old_name; | 4031 | const unsigned char *old_name; |
| 4032 | struct inode *source = old_dentry->d_inode; | 4032 | struct inode *source = old_dentry->d_inode; |
| 4033 | struct inode *target = new_dentry->d_inode; | 4033 | struct inode *target = new_dentry->d_inode; |
| 4034 | bool new_is_dir = false; | ||
| 4035 | unsigned max_links = new_dir->i_sb->s_max_links; | ||
| 4034 | 4036 | ||
| 4035 | if (source == target) | 4037 | if (source == target) |
| 4036 | return 0; | 4038 | return 0; |
| @@ -4039,10 +4041,16 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry, | |||
| 4039 | if (error) | 4041 | if (error) |
| 4040 | return error; | 4042 | return error; |
| 4041 | 4043 | ||
| 4042 | if (!target) | 4044 | if (!target) { |
| 4043 | error = may_create(new_dir, new_dentry); | 4045 | error = may_create(new_dir, new_dentry); |
| 4044 | else | 4046 | } else { |
| 4045 | error = may_delete(new_dir, new_dentry, is_dir); | 4047 | new_is_dir = d_is_dir(new_dentry); |
| 4048 | |||
| 4049 | if (!(flags & RENAME_EXCHANGE)) | ||
| 4050 | error = may_delete(new_dir, new_dentry, is_dir); | ||
| 4051 | else | ||
| 4052 | error = may_delete(new_dir, new_dentry, new_is_dir); | ||
| 4053 | } | ||
| 4046 | if (error) | 4054 | if (error) |
| 4047 | return error; | 4055 | return error; |
| 4048 | 4056 | ||
| @@ -4056,10 +4064,17 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry, | |||
| 4056 | * If we are going to change the parent - check write permissions, | 4064 | * If we are going to change the parent - check write permissions, |
| 4057 | * we'll need to flip '..'. | 4065 | * we'll need to flip '..'. |
| 4058 | */ | 4066 | */ |
| 4059 | if (is_dir && new_dir != old_dir) { | 4067 | if (new_dir != old_dir) { |
| 4060 | error = inode_permission(source, MAY_WRITE); | 4068 | if (is_dir) { |
| 4061 | if (error) | 4069 | error = inode_permission(source, MAY_WRITE); |
| 4062 | return error; | 4070 | if (error) |
| 4071 | return error; | ||
| 4072 | } | ||
| 4073 | if ((flags & RENAME_EXCHANGE) && new_is_dir) { | ||
| 4074 | error = inode_permission(target, MAY_WRITE); | ||
| 4075 | if (error) | ||
| 4076 | return error; | ||
| 4077 | } | ||
| 4063 | } | 4078 | } |
| 4064 | 4079 | ||
| 4065 | error = security_inode_rename(old_dir, old_dentry, new_dir, new_dentry, | 4080 | error = security_inode_rename(old_dir, old_dentry, new_dir, new_dentry, |
| @@ -4069,7 +4084,7 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry, | |||
| 4069 | 4084 | ||
| 4070 | old_name = fsnotify_oldname_init(old_dentry->d_name.name); | 4085 | old_name = fsnotify_oldname_init(old_dentry->d_name.name); |
| 4071 | dget(new_dentry); | 4086 | dget(new_dentry); |
| 4072 | if (!is_dir) | 4087 | if (!is_dir || (flags & RENAME_EXCHANGE)) |
| 4073 | lock_two_nondirectories(source, target); | 4088 | lock_two_nondirectories(source, target); |
| 4074 | else if (target) | 4089 | else if (target) |
| 4075 | mutex_lock(&target->i_mutex); | 4090 | mutex_lock(&target->i_mutex); |
| @@ -4078,25 +4093,25 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry, | |||
| 4078 | if (d_mountpoint(old_dentry) || d_mountpoint(new_dentry)) | 4093 | if (d_mountpoint(old_dentry) || d_mountpoint(new_dentry)) |
| 4079 | goto out; | 4094 | goto out; |
