diff options
-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; |
4080 | 4095 | ||
4081 | if (is_dir) { | 4096 | if (max_links && new_dir != old_dir) { |
4082 | unsigned max_links = new_dir->i_sb->s_max_links; | ||
4083 | |||
4084 | error = -EMLINK; | 4097 | error = -EMLINK; |
4085 | if (max_links && !target && new_dir != old_dir && | 4098 | if (is_dir && !new_is_dir && new_dir->i_nlink >= max_links) |
4086 | new_dir->i_nlink >= max_links) | ||
4087 | goto out; | 4099 | goto out; |
4088 | 4100 | if ((flags & RENAME_EXCHANGE) && !is_dir && new_is_dir && | |
4089 | if (target) | 4101 | old_dir->i_nlink >= max_links) |
4090 | shrink_dcache_parent(new_dentry); | 4102 | goto out; |
4091 | } else { | 4103 | } |
4104 | if (is_dir && !(flags & RENAME_EXCHANGE) && target) | ||
4105 | shrink_dcache_parent(new_dentry); | ||
4106 | if (!is_dir) { | ||
4092 | error = try_break_deleg(source, delegated_inode); | 4107 | error = try_break_deleg(source, delegated_inode); |
4093 | if (error) | 4108 | if (error) |
4094 | goto out; | 4109 | goto out; |
4095 | if (target) { | 4110 | } |
4096 | error = try_break_deleg(target, delegated_inode); | 4111 | if (target && !new_is_dir) { |
4097 | if (error) | 4112 | error = try_break_deleg(target, delegated_inode); |
4098 | goto out; | 4113 | if (error) |
4099 | } | 4114 | goto out; |
4100 | } | 4115 | } |
4101 | if (!flags) { | 4116 | if (!flags) { |
4102 | error = old_dir->i_op->rename(old_dir, old_dentry, | 4117 | error = old_dir->i_op->rename(old_dir, old_dentry, |
@@ -4108,22 +4123,31 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry, | |||
4108 | if (error) | 4123 | if (error) |
4109 | goto out; | 4124 | goto out; |
4110 | 4125 | ||
4111 | if (target) { | 4126 | if (!(flags & RENAME_EXCHANGE) && target) { |
4112 | if (is_dir) | 4127 | if (is_dir) |
4113 | target->i_flags |= S_DEAD; | 4128 | target->i_flags |= S_DEAD; |
4114 | dont_mount(new_dentry); | 4129 | dont_mount(new_dentry); |
4115 | } | 4130 | } |
4116 | if (!(old_dir->i_sb->s_type->fs_flags & FS_RENAME_DOES_D_MOVE)) | 4131 | if (!(old_dir->i_sb->s_type->fs_flags & FS_RENAME_DOES_D_MOVE)) { |
4117 | d_move(old_dentry, new_dentry); | 4132 | if (!(flags & RENAME_EXCHANGE)) |
4133 | d_move(old_dentry, new_dentry); | ||
4134 | else | ||
4135 | d_exchange(old_dentry, new_dentry); | ||
4136 | } | ||
4118 | out: | 4137 | out: |
4119 | if (!is_dir) | 4138 | if (!is_dir || (flags & RENAME_EXCHANGE)) |
4120 | unlock_two_nondirectories(source, target); | 4139 | unlock_two_nondirectories(source, target); |
4121 | else if (target) | 4140 | else if (target) |
4122 | mutex_unlock(&target->i_mutex); | 4141 | mutex_unlock(&target->i_mutex); |
4123 | dput(new_dentry); | 4142 | dput(new_dentry); |
4124 | if (!error) | 4143 | if (!error) { |
4125 | fsnotify_move(old_dir, new_dir, old_name, is_dir, | 4144 | fsnotify_move(old_dir, new_dir, old_name, is_dir, |
4126 | target, old_dentry); | 4145 | !(flags & RENAME_EXCHANGE) ? target : NULL, old_dentry); |
4146 | if (flags & RENAME_EXCHANGE) { | ||
4147 | fsnotify_move(new_dir, old_dir, old_dentry->d_name.name, | ||
4148 | new_is_dir, NULL, new_dentry); | ||
4149 | } | ||
4150 | } | ||
4127 | fsnotify_oldname_free(old_name); | 4151 | fsnotify_oldname_free(old_name); |
4128 | 4152 | ||
4129 | return error; | 4153 | return error; |
@@ -4143,7 +4167,10 @@ SYSCALL_DEFINE5(renameat2, int, olddfd, const char __user *, oldname, | |||
4143 | bool should_retry = false; | 4167 | bool should_retry = false; |
4144 | int error; | 4168 | int error; |
4145 | 4169 | ||
4146 | if (flags & ~RENAME_NOREPLACE) | 4170 | if (flags & ~(RENAME_NOREPLACE | RENAME_EXCHANGE)) |
4171 | return -EINVAL; | ||
4172 | |||
4173 | if ((flags & RENAME_NOREPLACE) && (flags & RENAME_EXCHANGE)) | ||
4147 | return -EINVAL; | 4174 | return -EINVAL; |
4148 | 4175 | ||
4149 | retry: | 4176 | retry: |
@@ -4180,7 +4207,8 @@ retry: | |||
4180 | 4207 | ||
4181 | oldnd.flags &= ~LOOKUP_PARENT; | 4208 | oldnd.flags &= ~LOOKUP_PARENT; |
4182 | newnd.flags &= ~LOOKUP_PARENT; | 4209 | newnd.flags &= ~LOOKUP_PARENT; |
4183 | newnd.flags |= LOOKUP_RENAME_TARGET; | 4210 | if (!(flags & RENAME_EXCHANGE)) |
4211 | newnd.flags |= LOOKUP_RENAME_TARGET; | ||
4184 | 4212 | ||
4185 | retry_deleg: | 4213 | retry_deleg: |
4186 | trap = lock_rename(new_dir, old_dir); | 4214 | trap = lock_rename(new_dir, old_dir); |
@@ -4200,12 +4228,23 @@ retry_deleg: | |||
4200 | error = -EEXIST; | 4228 | error = -EEXIST; |
4201 | if ((flags & RENAME_NOREPLACE) && d_is_positive(new_dentry)) | 4229 | if ((flags & RENAME_NOREPLACE) && d_is_positive(new_dentry)) |
4202 | goto exit5; | 4230 | goto exit5; |
4231 | if (flags & RENAME_EXCHANGE) { | ||
4232 | error = -ENOENT; | ||
4233 | if (d_is_negative(new_dentry)) | ||
4234 | goto exit5; | ||
4235 | |||
4236 | if (!d_is_dir(new_dentry)) { | ||
4237 | error = -ENOTDIR; | ||
4238 | if (newnd.last.name[newnd.last.len]) | ||
4239 | goto exit5; | ||
4240 | } | ||
4241 | } | ||
4203 | /* unless the source is a directory trailing slashes give -ENOTDIR */ | 4242 | /* unless the source is a directory trailing slashes give -ENOTDIR */ |
4204 | if (!d_is_dir(old_dentry)) { | 4243 | if (!d_is_dir(old_dentry)) { |
4205 | error = -ENOTDIR; | 4244 | error = -ENOTDIR; |
4206 | if (oldnd.last.name[oldnd.last.len]) | 4245 | if (oldnd.last.name[oldnd.last.len]) |
4207 | goto exit5; | 4246 | goto exit5; |
4208 | if (newnd.last.name[newnd.last.len]) | 4247 | if (!(flags & RENAME_EXCHANGE) && newnd.last.name[newnd.last.len]) |
4209 | goto exit5; | 4248 | goto exit5; |
4210 | } | 4249 | } |
4211 | /* source should not be ancestor of target */ | 4250 | /* source should not be ancestor of target */ |
@@ -4213,7 +4252,8 @@ retry_deleg: | |||
4213 | if (old_dentry == trap) | 4252 | if (old_dentry == trap) |
4214 | goto exit5; | 4253 | goto exit5; |
4215 | /* target should not be an ancestor of source */ | 4254 | /* target should not be an ancestor of source */ |
4216 | error = -ENOTEMPTY; | 4255 | if (!(flags & RENAME_EXCHANGE)) |
4256 | error = -ENOTEMPTY; | ||
4217 | if (new_dentry == trap) | 4257 | if (new_dentry == trap) |
4218 | goto exit5; | 4258 | goto exit5; |
4219 | 4259 | ||
diff --git a/include/linux/dcache.h b/include/linux/dcache.h index 3b50cac7ccb3..3b9bfdb83ba6 100644 --- a/include/linux/dcache.h +++ b/include/linux/dcache.h | |||
@@ -308,6 +308,7 @@ extern void dentry_update_name_case(struct dentry *, struct qstr *); | |||
308 | 308 | ||
309 | /* used for rename() and baskets */ | 309 | /* used for rename() and baskets */ |
310 | extern void d_move(struct dentry *, struct dentry *); | 310 | extern void d_move(struct dentry *, struct dentry *); |
311 | extern void d_exchange(struct dentry *, struct dentry *); | ||
311 | extern struct dentry *d_ancestor(struct dentry *, struct dentry *); | 312 | extern struct dentry *d_ancestor(struct dentry *, struct dentry *); |
312 | 313 | ||
313 | /* appendix may either be NULL or be used for transname suffixes */ | 314 | /* appendix may either be NULL or be used for transname suffixes */ |
diff --git a/include/uapi/linux/fs.h b/include/uapi/linux/fs.h index 9250f4dd7d96..ca1a11bb4443 100644 --- a/include/uapi/linux/fs.h +++ b/include/uapi/linux/fs.h | |||
@@ -36,6 +36,7 @@ | |||
36 | #define SEEK_MAX SEEK_HOLE | 36 | #define SEEK_MAX SEEK_HOLE |
37 | 37 | ||
38 | #define RENAME_NOREPLACE (1 << 0) /* Don't overwrite target */ | 38 | #define RENAME_NOREPLACE (1 << 0) /* Don't overwrite target */ |
39 | #define RENAME_EXCHANGE (1 << 1) /* Exchange source and dest */ | ||
39 | 40 | ||
40 | struct fstrim_range { | 41 | struct fstrim_range { |
41 | __u64 start; | 42 | __u64 start; |
diff --git a/security/security.c b/security/security.c index 284fbc99aa9d..8b774f362a3d 100644 --- a/security/security.c +++ b/security/security.c | |||
@@ -439,6 +439,14 @@ int security_path_rename(struct path *old_dir, struct dentry *old_dentry, | |||
439 | if (unlikely(IS_PRIVATE(old_dentry->d_inode) || | 439 | if (unlikely(IS_PRIVATE(old_dentry->d_inode) || |
440 | (new_dentry->d_inode && IS_PRIVATE(new_dentry->d_inode)))) | 440 | (new_dentry->d_inode && IS_PRIVATE(new_dentry->d_inode)))) |
441 | return 0; | 441 | return 0; |
442 | |||
443 | if (flags & RENAME_EXCHANGE) { | ||
444 | int err = security_ops->path_rename(new_dir, new_dentry, | ||
445 | old_dir, old_dentry); | ||
446 | if (err) | ||
447 | return err; | ||
448 | } | ||
449 | |||
442 | return security_ops->path_rename(old_dir, old_dentry, new_dir, | 450 | return security_ops->path_rename(old_dir, old_dentry, new_dir, |
443 | new_dentry); | 451 | new_dentry); |
444 | } | 452 | } |
@@ -531,6 +539,14 @@ int security_inode_rename(struct inode *old_dir, struct dentry *old_dentry, | |||
531 | if (unlikely(IS_PRIVATE(old_dentry->d_inode) || | 539 | if (unlikely(IS_PRIVATE(old_dentry->d_inode) || |
532 | (new_dentry->d_inode && IS_PRIVATE(new_dentry->d_inode)))) | 540 | (new_dentry->d_inode && IS_PRIVATE(new_dentry->d_inode)))) |
533 | return 0; | 541 | return 0; |
542 | |||
543 | if (flags & RENAME_EXCHANGE) { | ||
544 | int err = security_ops->inode_rename(new_dir, new_dentry, | ||
545 | old_dir, old_dentry); | ||
546 | if (err) | ||
547 | return err; | ||
548 | } | ||
549 | |||
534 | return security_ops->inode_rename(old_dir, old_dentry, | 550 | return security_ops->inode_rename(old_dir, old_dentry, |
535 | new_dir, new_dentry); | 551 | new_dir, new_dentry); |
536 | } | 552 | } |