aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMiklos Szeredi <mszeredi@suse.cz>2014-04-01 11:08:43 -0400
committerMiklos Szeredi <mszeredi@suse.cz>2014-04-01 11:08:43 -0400
commit0a7c3937a1f23f8cb5fc77ae01661e9968a51d0c (patch)
tree8924086bfd279409f9eb83aab9a6177e0748b257
parent520c8b16505236fc82daa352e6c5e73cd9870cff (diff)
vfs: add RENAME_NOREPLACE flag
If this flag is specified and the target of the rename exists then the rename syscall fails with EEXIST. The VFS does the existence checking, so it is trivial to enable for most local filesystems. This patch only enables it in ext4. For network filesystems the VFS check is not enough as there may be a race between a remote create and the rename, so these filesystems need to handle this flag in their ->rename() implementations to ensure atomicity. Andy writes about why this is useful: "The trivial answer: to eliminate the race condition from 'mv -i'. Another answer: there's a common pattern to atomically create a file with contents: open a temporary file, write to it, optionally fsync it, close it, then link(2) it to the final name, then unlink the temporary file. The reason to use link(2) is because it won't silently clobber the destination. This is annoying: - It requires an extra system call that shouldn't be necessary. - It doesn't work on (IMO sensible) filesystems that don't support hard links (e.g. vfat). - It's not atomic -- there's an intermediate state where both files exist. - It's ugly. The new rename flag will make this totally sensible. To be fair, on new enough kernels, you can also use O_TMPFILE and linkat to achieve the same thing even more cleanly." Suggested-by: Andy Lutomirski <luto@amacapital.net> Signed-off-by: Miklos Szeredi <mszeredi@suse.cz> Reviewed-by: J. Bruce Fields <bfields@redhat.com>
-rw-r--r--fs/ext4/namei.c11
-rw-r--r--fs/namei.c21
-rw-r--r--include/uapi/linux/fs.h2
3 files changed, 26 insertions, 8 deletions
diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c
index d050e043e884..5f19171b3e1f 100644
--- a/fs/ext4/namei.c
+++ b/fs/ext4/namei.c
@@ -3204,6 +3204,16 @@ end_rename:
3204 return retval; 3204 return retval;
3205} 3205}
3206 3206
3207static int ext4_rename2(struct inode *old_dir, struct dentry *old_dentry,
3208 struct inode *new_dir, struct dentry *new_dentry,
3209 unsigned int flags)
3210{
3211 if (flags & ~RENAME_NOREPLACE)
3212 return -EINVAL;
3213
3214 return ext4_rename(old_dir, old_dentry, new_dir, new_dentry);
3215}
3216
3207/* 3217/*
3208 * directories can handle most operations... 3218 * directories can handle most operations...
3209 */ 3219 */
@@ -3218,6 +3228,7 @@ const struct inode_operations ext4_dir_inode_operations = {
3218 .mknod = ext4_mknod, 3228 .mknod = ext4_mknod,
3219 .tmpfile = ext4_tmpfile, 3229 .tmpfile = ext4_tmpfile,
3220 .rename = ext4_rename, 3230 .rename = ext4_rename,
3231 .rename2 = ext4_rename2,
3221 .setattr = ext4_setattr, 3232 .setattr = ext4_setattr,
3222 .setxattr = generic_setxattr, 3233 .setxattr = generic_setxattr,
3223 .getxattr = generic_getxattr, 3234 .getxattr = generic_getxattr,
diff --git a/fs/namei.c b/fs/namei.c
index ab4e48c4a80a..0e9d186b7f77 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -4142,7 +4142,7 @@ SYSCALL_DEFINE5(renameat2, int, olddfd, const char __user *, oldname,
4142 bool should_retry = false; 4142 bool should_retry = false;
4143 int error; 4143 int error;
4144 4144
4145 if (flags) 4145 if (flags & ~RENAME_NOREPLACE)
4146 return -EINVAL; 4146 return -EINVAL;
4147 4147
4148retry: 4148retry:
@@ -4168,6 +4168,8 @@ retry:
4168 goto exit2; 4168 goto exit2;
4169 4169
4170 new_dir = newnd.path.dentry; 4170 new_dir = newnd.path.dentry;
4171 if (flags & RENAME_NOREPLACE)
4172 error = -EEXIST;
4171 if (newnd.last_type != LAST_NORM) 4173 if (newnd.last_type != LAST_NORM)
4172 goto exit2; 4174 goto exit2;
4173 4175
@@ -4190,22 +4192,25 @@ retry_deleg:
4190 error = -ENOENT; 4192 error = -ENOENT;
4191 if (d_is_negative(old_dentry)) 4193 if (d_is_negative(old_dentry))
4192 goto exit4; 4194 goto exit4;
4195 new_dentry = lookup_hash(&newnd);
4196 error = PTR_ERR(new_dentry);
4197 if (IS_ERR(new_dentry))
4198 goto exit4;
4199 error = -EEXIST;
4200 if ((flags & RENAME_NOREPLACE) && d_is_positive(new_dentry))
4201 goto exit5;
4193 /* unless the source is a directory trailing slashes give -ENOTDIR */ 4202 /* unless the source is a directory trailing slashes give -ENOTDIR */
4194 if (!d_is_dir(old_dentry)) { 4203 if (!d_is_dir(old_dentry)) {
4195 error = -ENOTDIR; 4204 error = -ENOTDIR;
4196 if (oldnd.last.name[oldnd.last.len]) 4205 if (oldnd.last.name[oldnd.last.len])
4197 goto exit4; 4206 goto exit5;
4198 if (newnd.last.name[newnd.last.len]) 4207 if (newnd.last.name[newnd.last.len])
4199 goto exit4; 4208 goto exit5;
4200 } 4209 }
4201 /* source should not be ancestor of target */ 4210 /* source should not be ancestor of target */
4202 error = -EINVAL; 4211 error = -EINVAL;
4203 if (old_dentry == trap) 4212 if (old_dentry == trap)
4204 goto exit4; 4213 goto exit5;
4205 new_dentry = lookup_hash(&newnd);
4206 error = PTR_ERR(new_dentry);
4207 if (IS_ERR(new_dentry))
4208 goto exit4;
4209 /* target should not be an ancestor of source */ 4214 /* target should not be an ancestor of source */
4210 error = -ENOTEMPTY; 4215 error = -ENOTEMPTY;
4211 if (new_dentry == trap) 4216 if (new_dentry == trap)
diff --git a/include/uapi/linux/fs.h b/include/uapi/linux/fs.h
index 6c28b61bb690..9250f4dd7d96 100644
--- a/include/uapi/linux/fs.h
+++ b/include/uapi/linux/fs.h
@@ -35,6 +35,8 @@
35#define SEEK_HOLE 4 /* seek to the next hole */ 35#define SEEK_HOLE 4 /* seek to the next hole */
36#define SEEK_MAX SEEK_HOLE 36#define SEEK_MAX SEEK_HOLE
37 37
38#define RENAME_NOREPLACE (1 << 0) /* Don't overwrite target */
39
38struct fstrim_range { 40struct fstrim_range {
39 __u64 start; 41 __u64 start;
40 __u64 len; 42 __u64 len;