Skip to content

Commit da1ce06

Browse files
author
Miklos Szeredi
committed
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>
1 parent 4fd699a commit da1ce06

File tree

5 files changed

+131
-41
lines changed

5 files changed

+131
-41
lines changed

fs/dcache.c

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2483,12 +2483,14 @@ static void switch_names(struct dentry *dentry, struct dentry *target)
24832483
dentry->d_name.name = dentry->d_iname;
24842484
} else {
24852485
/*
2486-
* Both are internal. Just copy target to dentry
2486+
* Both are internal.
24872487
*/
2488-
memcpy(dentry->d_iname, target->d_name.name,
2489-
target->d_name.len + 1);
2490-
dentry->d_name.len = target->d_name.len;
2491-
return;
2488+
unsigned int i;
2489+
BUILD_BUG_ON(!IS_ALIGNED(DNAME_INLINE_LEN, sizeof(long)));
2490+
for (i = 0; i < DNAME_INLINE_LEN / sizeof(long); i++) {
2491+
swap(((long *) &dentry->d_iname)[i],
2492+
((long *) &target->d_iname)[i]);
2493+
}
24922494
}
24932495
}
24942496
swap(dentry->d_name.len, target->d_name.len);
@@ -2545,13 +2547,15 @@ static void dentry_unlock_parents_for_move(struct dentry *dentry,
25452547
* __d_move - move a dentry
25462548
* @dentry: entry to move
25472549
* @target: new dentry
2550+
* @exchange: exchange the two dentries
25482551
*
25492552
* Update the dcache to reflect the move of a file name. Negative
25502553
* dcache entries should not be moved in this way. Caller must hold
25512554
* rename_lock, the i_mutex of the source and target directories,
25522555
* and the sb->s_vfs_rename_mutex if they differ. See lock_rename().
25532556
*/
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)
25552559
{
25562560
if (!dentry->d_inode)
25572561
printk(KERN_WARNING "VFS: moving negative dcache entry\n");
@@ -2573,8 +2577,15 @@ static void __d_move(struct dentry * dentry, struct dentry * target)
25732577
__d_drop(dentry);
25742578
__d_rehash(dentry, d_hash(target->d_parent, target->d_name.hash));
25752579

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+
*/
25772584
__d_drop(target);
2585+
if (exchange) {
2586+
__d_rehash(target,
2587+
d_hash(dentry->d_parent, dentry->d_name.hash));
2588+
}
25782589

25792590
list_del(&dentry->d_u.d_child);
25802591
list_del(&target->d_u.d_child);
@@ -2601,6 +2612,8 @@ static void __d_move(struct dentry * dentry, struct dentry * target)
26012612
write_seqcount_end(&dentry->d_seq);
26022613

26032614
dentry_unlock_parents_for_move(dentry, target);
2615+
if (exchange)
2616+
fsnotify_d_move(target);
26042617
spin_unlock(&target->d_lock);
26052618
fsnotify_d_move(dentry);
26062619
spin_unlock(&dentry->d_lock);
@@ -2618,11 +2631,30 @@ static void __d_move(struct dentry * dentry, struct dentry * target)
26182631
void d_move(struct dentry *dentry, struct dentry *target)
26192632
{
26202633
write_seqlock(&rename_lock);
2621-
__d_move(dentry, target);
2634+
__d_move(dentry, target, false);
26222635
write_sequnlock(&rename_lock);
26232636
}
26242637
EXPORT_SYMBOL(d_move);
26252638

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+
26262658
/**
26272659
* d_ancestor - search for an ancestor
26282660
* @p1: ancestor dentry
@@ -2670,7 +2702,7 @@ static struct dentry *__d_unalias(struct inode *inode,
26702702
m2 = &alias->d_parent->d_inode->i_mutex;
26712703
out_unalias:
26722704
if (likely(!d_mountpoint(alias))) {
2673-
__d_move(alias, dentry);
2705+
__d_move(alias, dentry, false);
26742706
ret = alias;
26752707
}
26762708
out_err:

fs/namei.c

Lines changed: 72 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4031,6 +4031,8 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry,
40314031
const unsigned char *old_name;
40324032
struct inode *source = old_dentry->d_inode;
40334033
struct inode *target = new_dentry->d_inode;
4034+
bool new_is_dir = false;
4035+
unsigned max_links = new_dir->i_sb->s_max_links;
40344036

40354037
if (source == target)
40364038
return 0;
@@ -4039,10 +4041,16 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry,
40394041
if (error)
40404042
return error;
40414043

4042-
if (!target)
4044+
if (!target) {
40434045
error = may_create(new_dir, new_dentry);
4044-
else
4045-
error = may_delete(new_dir, new_dentry, is_dir);
4046+
} else {
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+
}
40464054
if (error)
40474055
return error;
40484056

@@ -4056,10 +4064,17 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry,
40564064
* If we are going to change the parent - check write permissions,
40574065
* we'll need to flip '..'.
40584066
*/
4059-
if (is_dir && new_dir != old_dir) {
4060-
error = inode_permission(source, MAY_WRITE);
4061-
if (error)
4062-
return error;
4067+
if (new_dir != old_dir) {
4068+
if (is_dir) {
4069+
error = inode_permission(source, MAY_WRITE);
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+
}
40634078
}
40644079

40654080
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,
40694084

40704085
old_name = fsnotify_oldname_init(old_dentry->d_name.name);
40714086
dget(new_dentry);
4072-
if (!is_dir)
4087+
if (!is_dir || (flags & RENAME_EXCHANGE))
40734088
lock_two_nondirectories(source, target);
40744089
else if (target)
40754090
mutex_lock(&target->i_mutex);
@@ -4078,25 +4093,25 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry,
40784093
if (d_mountpoint(old_dentry) || d_mountpoint(new_dentry))
40794094
goto out;
40804095

4081-
if (is_dir) {
4082-
unsigned max_links = new_dir->i_sb->s_max_links;
4083-
4096+
if (max_links && new_dir != old_dir) {
40844097
error = -EMLINK;
4085-
if (max_links && !target && new_dir != old_dir &&
4086-
new_dir->i_nlink >= max_links)
4098+
if (is_dir && !new_is_dir && new_dir->i_nlink >= max_links)
40874099
goto out;
4088-
4089-
if (target)
4090-
shrink_dcache_parent(new_dentry);
4091-
} else {
4100+
if ((flags & RENAME_EXCHANGE) && !is_dir && new_is_dir &&
4101+
old_dir->i_nlink >= max_links)
4102+
goto out;
4103+
}
4104+
if (is_dir && !(flags & RENAME_EXCHANGE) && target)
4105+
shrink_dcache_parent(new_dentry);
4106+
if (!is_dir) {
40924107
error = try_break_deleg(source, delegated_inode);
40934108
if (error)
40944109
goto out;
4095-
if (target) {
4096-
error = try_break_deleg(target, delegated_inode);
4097-
if (error)
4098-
goto out;
4099-
}
4110+
}
4111+
if (target && !new_is_dir) {
4112+
error = try_break_deleg(target, delegated_inode);
4113+
if (error)
4114+
goto out;
41004115
}
41014116
if (!flags) {
41024117
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,
41084123
if (error)
41094124
goto out;
41104125

4111-
if (target) {
4126+
if (!(flags & RENAME_EXCHANGE) && target) {
41124127
if (is_dir)
41134128
target->i_flags |= S_DEAD;
41144129
dont_mount(new_dentry);
41154130
}
4116-
if (!(old_dir->i_sb->s_type->fs_flags & FS_RENAME_DOES_D_MOVE))
4117-
d_move(old_dentry, new_dentry);
4131+
if (!(old_dir->i_sb->s_type->fs_flags & FS_RENAME_DOES_D_MOVE)) {
4132+
if (!(flags & RENAME_EXCHANGE))
4133+
d_move(old_dentry, new_dentry);
4134+
else
4135+
d_exchange(old_dentry, new_dentry);
4136+
}
41184137
out:
4119-
if (!is_dir)
4138+
if (!is_dir || (flags & RENAME_EXCHANGE))
41204139
unlock_two_nondirectories(source, target);
41214140
else if (target)
41224141
mutex_unlock(&target->i_mutex);
41234142
dput(new_dentry);
4124-
if (!error)
4143+
if (!error) {
41254144
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+
}
41274151
fsnotify_oldname_free(old_name);
41284152

41294153
return error;
@@ -4143,7 +4167,10 @@ SYSCALL_DEFINE5(renameat2, int, olddfd, const char __user *, oldname,
41434167
bool should_retry = false;
41444168
int error;
41454169

4146-
if (flags & ~RENAME_NOREPLACE)
4170+
if (flags & ~(RENAME_NOREPLACE | RENAME_EXCHANGE))
4171+
return -EINVAL;
4172+
4173+
if ((flags & RENAME_NOREPLACE) && (flags & RENAME_EXCHANGE))
41474174
return -EINVAL;
41484175

41494176
retry:
@@ -4180,7 +4207,8 @@ SYSCALL_DEFINE5(renameat2, int, olddfd, const char __user *, oldname,
41804207

41814208
oldnd.flags &= ~LOOKUP_PARENT;
41824209
newnd.flags &= ~LOOKUP_PARENT;
4183-
newnd.flags |= LOOKUP_RENAME_TARGET;
4210+
if (!(flags & RENAME_EXCHANGE))
4211+
newnd.flags |= LOOKUP_RENAME_TARGET;
41844212

41854213
retry_deleg:
41864214
trap = lock_rename(new_dir, old_dir);
@@ -4200,20 +4228,32 @@ SYSCALL_DEFINE5(renameat2, int, olddfd, const char __user *, oldname,
42004228
error = -EEXIST;
42014229
if ((flags & RENAME_NOREPLACE) && d_is_positive(new_dentry))
42024230
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+
}
42034242
/* unless the source is a directory trailing slashes give -ENOTDIR */
42044243
if (!d_is_dir(old_dentry)) {
42054244
error = -ENOTDIR;
42064245
if (oldnd.last.name[oldnd.last.len])
42074246
goto exit5;
4208-
if (newnd.last.name[newnd.last.len])
4247+
if (!(flags & RENAME_EXCHANGE) && newnd.last.name[newnd.last.len])
42094248
goto exit5;
42104249
}
42114250
/* source should not be ancestor of target */
42124251
error = -EINVAL;
42134252
if (old_dentry == trap)
42144253
goto exit5;
42154254
/* target should not be an ancestor of source */
4216-
error = -ENOTEMPTY;
4255+
if (!(flags & RENAME_EXCHANGE))
4256+
error = -ENOTEMPTY;
42174257
if (new_dentry == trap)
42184258
goto exit5;
42194259

include/linux/dcache.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,7 @@ extern void dentry_update_name_case(struct dentry *, struct qstr *);
308308

309309
/* used for rename() and baskets */
310310
extern void d_move(struct dentry *, struct dentry *);
311+
extern void d_exchange(struct dentry *, struct dentry *);
311312
extern struct dentry *d_ancestor(struct dentry *, struct dentry *);
312313

313314
/* appendix may either be NULL or be used for transname suffixes */

include/uapi/linux/fs.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
#define SEEK_MAX SEEK_HOLE
3737

3838
#define RENAME_NOREPLACE (1 << 0) /* Don't overwrite target */
39+
#define RENAME_EXCHANGE (1 << 1) /* Exchange source and dest */
3940

4041
struct fstrim_range {
4142
__u64 start;

security/security.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,14 @@ int security_path_rename(struct path *old_dir, struct dentry *old_dentry,
439439
if (unlikely(IS_PRIVATE(old_dentry->d_inode) ||
440440
(new_dentry->d_inode && IS_PRIVATE(new_dentry->d_inode))))
441441
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+
442450
return security_ops->path_rename(old_dir, old_dentry, new_dir,
443451
new_dentry);
444452
}
@@ -531,6 +539,14 @@ int security_inode_rename(struct inode *old_dir, struct dentry *old_dentry,
531539
if (unlikely(IS_PRIVATE(old_dentry->d_inode) ||
532540
(new_dentry->d_inode && IS_PRIVATE(new_dentry->d_inode))))
533541
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+
534550
return security_ops->inode_rename(old_dir, old_dentry,
535551
new_dir, new_dentry);
536552
}

0 commit comments

Comments
 (0)