From 0a7c3937a1f23f8cb5fc77ae01661e9968a51d0c Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Tue, 1 Apr 2014 17:08:43 +0200 Subject: 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 Signed-off-by: Miklos Szeredi Reviewed-by: J. Bruce Fields --- fs/ext4/namei.c | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'fs/ext4') 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: return retval; } +static int ext4_rename2(struct inode *old_dir, struct dentry *old_dentry, + struct inode *new_dir, struct dentry *new_dentry, + unsigned int flags) +{ + if (flags & ~RENAME_NOREPLACE) + return -EINVAL; + + return ext4_rename(old_dir, old_dentry, new_dir, new_dentry); +} + /* * directories can handle most operations... */ @@ -3218,6 +3228,7 @@ const struct inode_operations ext4_dir_inode_operations = { .mknod = ext4_mknod, .tmpfile = ext4_tmpfile, .rename = ext4_rename, + .rename2 = ext4_rename2, .setattr = ext4_setattr, .setxattr = generic_setxattr, .getxattr = generic_getxattr, -- cgit v1.2.3 From c0d268c3661efdfc76dde90ff2bc12806410cd0a Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Tue, 1 Apr 2014 17:08:43 +0200 Subject: ext4: rename: create ext4_renament structure for local vars Need to split up ext4_rename() into helpers but there are too many local variables involved, so create a new structure. This also, apparently, makes the generated code size slightly smaller. Signed-off-by: Miklos Szeredi Reviewed-by: Jan Kara --- fs/ext4/namei.c | 211 ++++++++++++++++++++++++++++++-------------------------- 1 file changed, 114 insertions(+), 97 deletions(-) (limited to 'fs/ext4') diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c index 5f19171b3e1f..7193cea805ff 100644 --- a/fs/ext4/namei.c +++ b/fs/ext4/namei.c @@ -3000,6 +3000,22 @@ static struct buffer_head *ext4_get_first_dir_block(handle_t *handle, return ext4_get_first_inline_block(inode, parent_de, retval); } +struct ext4_renament { + struct inode *dir; + struct dentry *dentry; + struct inode *inode; + + /* entry for "dentry" */ + struct buffer_head *bh; + struct ext4_dir_entry_2 *de; + int inlined; + + /* entry for ".." in inode if it's a directory */ + struct buffer_head *dir_bh; + struct ext4_dir_entry_2 *parent_de; + int dir_inlined; +}; + /* * Anybody can rename anything with this: the permission checks are left to the * higher-level routines. @@ -3012,193 +3028,194 @@ static int ext4_rename(struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry) { handle_t *handle = NULL; - struct inode *old_inode, *new_inode; - struct buffer_head *old_bh, *new_bh, *dir_bh; - struct ext4_dir_entry_2 *old_de, *new_de; + struct ext4_renament old = { + .dir = old_dir, + .dentry = old_dentry, + .inode = old_dentry->d_inode, + }; + struct ext4_renament new = { + .dir = new_dir, + .dentry = new_dentry, + .inode = new_dentry->d_inode, + }; int retval; - int inlined = 0, new_inlined = 0; - struct ext4_dir_entry_2 *parent_de; - - dquot_initialize(old_dir); - dquot_initialize(new_dir); - old_bh = new_bh = dir_bh = NULL; + dquot_initialize(old.dir); + dquot_initialize(new.dir); /* Initialize quotas before so that eventual writes go * in separate transaction */ - if (new_dentry->d_inode) - dquot_initialize(new_dentry->d_inode); + if (new.inode) + dquot_initialize(new.inode); - old_bh = ext4_find_entry(old_dir, &old_dentry->d_name, &old_de, NULL); + old.bh = ext4_find_entry(old.dir, &old.dentry->d_name, &old.de, NULL); /* * Check for inode number is _not_ due to possible IO errors. * We might rmdir the source, keep it as pwd of some process * and merrily kill the link to whatever was created under the * same name. Goodbye sticky bit ;-< */ - old_inode = old_dentry->d_inode; retval = -ENOENT; - if (!old_bh || le32_to_cpu(old_de->inode) != old_inode->i_ino) + if (!old.bh || le32_to_cpu(old.de->inode) != old.inode->i_ino) goto end_rename; - new_inode = new_dentry->d_inode; - new_bh = ext4_find_entry(new_dir, &new_dentry->d_name, - &new_de, &new_inlined); - if (new_bh) { - if (!new_inode) { - brelse(new_bh); - new_bh = NULL; + new.bh = ext4_find_entry(new.dir, &new.dentry->d_name, + &new.de, &new.inlined); + if (new.bh) { + if (!new.inode) { + brelse(new.bh); + new.bh = NULL; } } - if (new_inode && !test_opt(new_dir->i_sb, NO_AUTO_DA_ALLOC)) - ext4_alloc_da_blocks(old_inode); + if (new.inode && !test_opt(new.dir->i_sb, NO_AUTO_DA_ALLOC)) + ext4_alloc_da_blocks(old.inode); - handle = ext4_journal_start(old_dir, EXT4_HT_DIR, - (2 * EXT4_DATA_TRANS_BLOCKS(old_dir->i_sb) + + handle = ext4_journal_start(old.dir, EXT4_HT_DIR, + (2 * EXT4_DATA_TRANS_BLOCKS(old.dir->i_sb) + EXT4_INDEX_EXTRA_TRANS_BLOCKS + 2)); if (IS_ERR(handle)) return PTR_ERR(handle); - if (IS_DIRSYNC(old_dir) || IS_DIRSYNC(new_dir)) + if (IS_DIRSYNC(old.dir) || IS_DIRSYNC(new.dir)) ext4_handle_sync(handle); - if (S_ISDIR(old_inode->i_mode)) { - if (new_inode) { + if (S_ISDIR(old.inode->i_mode)) { + if (new.inode) { retval = -ENOTEMPTY; - if (!empty_dir(new_inode)) + if (!empty_dir(new.inode)) goto end_rename; } retval = -EIO; - dir_bh = ext4_get_first_dir_block(handle, old_inode, - &retval, &parent_de, - &inlined); - if (!dir_bh) + old.dir_bh = ext4_get_first_dir_block(handle, old.inode, + &retval, &old.parent_de, + &old.dir_inlined); + if (!old.dir_bh) goto end_rename; - if (le32_to_cpu(parent_de->inode) != old_dir->i_ino) + if (le32_to_cpu(old.parent_de->inode) != old.dir->i_ino) goto end_rename; retval = -EMLINK; - if (!new_inode && new_dir != old_dir && - EXT4_DIR_LINK_MAX(new_dir)) + if (!new.inode && new.dir != old.dir && + EXT4_DIR_LINK_MAX(new.dir)) goto end_rename; - BUFFER_TRACE(dir_bh, "get_write_access"); - retval = ext4_journal_get_write_access(handle, dir_bh); + BUFFER_TRACE(old.dir_bh, "get_write_access"); + retval = ext4_journal_get_write_access(handle, old.dir_bh); if (retval) goto end_rename; } - if (!new_bh) { - retval = ext4_add_entry(handle, new_dentry, old_inode); + if (!new.bh) { + retval = ext4_add_entry(handle, new.dentry, old.inode); if (retval) goto end_rename; } else { - BUFFER_TRACE(new_bh, "get write access"); - retval = ext4_journal_get_write_access(handle, new_bh); + BUFFER_TRACE(new.bh, "get write access"); + retval = ext4_journal_get_write_access(handle, new.bh); if (retval) goto end_rename; - new_de->inode = cpu_to_le32(old_inode->i_ino); - if (EXT4_HAS_INCOMPAT_FEATURE(new_dir->i_sb, + new.de->inode = cpu_to_le32(old.inode->i_ino); + if (EXT4_HAS_INCOMPAT_FEATURE(new.dir->i_sb, EXT4_FEATURE_INCOMPAT_FILETYPE)) - new_de->file_type = old_de->file_type; - new_dir->i_version++; - new_dir->i_ctime = new_dir->i_mtime = - ext4_current_time(new_dir); - ext4_mark_inode_dirty(handle, new_dir); - BUFFER_TRACE(new_bh, "call ext4_handle_dirty_metadata"); - if (!new_inlined) { + new.de->file_type = old.de->file_type; + new.dir->i_version++; + new.dir->i_ctime = new.dir->i_mtime = + ext4_current_time(new.dir); + ext4_mark_inode_dirty(handle, new.dir); + BUFFER_TRACE(new.bh, "call ext4_handle_dirty_metadata"); + if (!new.inlined) { retval = ext4_handle_dirty_dirent_node(handle, - new_dir, new_bh); + new.dir, new.bh); if (unlikely(retval)) { - ext4_std_error(new_dir->i_sb, retval); + ext4_std_error(new.dir->i_sb, retval); goto end_rename; } } - brelse(new_bh); - new_bh = NULL; + brelse(new.bh); + new.bh = NULL; } /* * Like most other Unix systems, set the ctime for inodes on a * rename. */ - old_inode->i_ctime = ext4_current_time(old_inode); - ext4_mark_inode_dirty(handle, old_inode); + old.inode->i_ctime = ext4_current_time(old.inode); + ext4_mark_inode_dirty(handle, old.inode); /* * ok, that's it */ - if (le32_to_cpu(old_de->inode) != old_inode->i_ino || - old_de->name_len != old_dentry->d_name.len || - strncmp(old_de->name, old_dentry->d_name.name, old_de->name_len) || - (retval = ext4_delete_entry(handle, old_dir, - old_de, old_bh)) == -ENOENT) { - /* old_de could have moved from under us during htree split, so + if (le32_to_cpu(old.de->inode) != old.inode->i_ino || + old.de->name_len != old.dentry->d_name.len || + strncmp(old.de->name, old.dentry->d_name.name, old.de->name_len) || + (retval = ext4_delete_entry(handle, old.dir, + old.de, old.bh)) == -ENOENT) { + /* old.de could have moved from under us during htree split, so * make sure that we are deleting the right entry. We might * also be pointing to a stale entry in the unused part of - * old_bh so just checking inum and the name isn't enough. */ + * old.bh so just checking inum and the name isn't enough. */ struct buffer_head *old_bh2; struct ext4_dir_entry_2 *old_de2; - old_bh2 = ext4_find_entry(old_dir, &old_dentry->d_name, + old_bh2 = ext4_find_entry(old.dir, &old.dentry->d_name, &old_de2, NULL); if (old_bh2) { - retval = ext4_delete_entry(handle, old_dir, + retval = ext4_delete_entry(handle, old.dir, old_de2, old_bh2); brelse(old_bh2); } } if (retval) { - ext4_warning(old_dir->i_sb, + ext4_warning(old.dir->i_sb, "Deleting old file (%lu), %d, error=%d", - old_dir->i_ino, old_dir->i_nlink, retval); + old.dir->i_ino, old.dir->i_nlink, retval); } - if (new_inode) { - ext4_dec_count(handle, new_inode); - new_inode->i_ctime = ext4_current_time(new_inode); + if (new.inode) { + ext4_dec_count(handle, new.inode); + new.inode->i_ctime = ext4_current_time(new.inode); } - old_dir->i_ctime = old_dir->i_mtime = ext4_current_time(old_dir); - ext4_update_dx_flag(old_dir); - if (dir_bh) { - parent_de->inode = cpu_to_le32(new_dir->i_ino); - BUFFER_TRACE(dir_bh, "call ext4_handle_dirty_metadata"); - if (!inlined) { - if (is_dx(old_inode)) { + old.dir->i_ctime = old.dir->i_mtime = ext4_current_time(old.dir); + ext4_update_dx_flag(old.dir); + if (old.dir_bh) { + old.parent_de->inode = cpu_to_le32(new.dir->i_ino); + BUFFER_TRACE(old.dir_bh, "call ext4_handle_dirty_metadata"); + if (!old.dir_inlined) { + if (is_dx(old.inode)) { retval = ext4_handle_dirty_dx_node(handle, - old_inode, - dir_bh); + old.inode, + old.dir_bh); } else { retval = ext4_handle_dirty_dirent_node(handle, - old_inode, dir_bh); + old.inode, old.dir_bh); } } else { - retval = ext4_mark_inode_dirty(handle, old_inode); + retval = ext4_mark_inode_dirty(handle, old.inode); } if (retval) { - ext4_std_error(old_dir->i_sb, retval); + ext4_std_error(old.dir->i_sb, retval); goto end_rename; } - ext4_dec_count(handle, old_dir); - if (new_inode) { + ext4_dec_count(handle, old.dir); + if (new.inode) { /* checked empty_dir above, can't have another parent, * ext4_dec_count() won't work for many-linked dirs */ - clear_nlink(new_inode); + clear_nlink(new.inode); } else { - ext4_inc_count(handle, new_dir); - ext4_update_dx_flag(new_dir); - ext4_mark_inode_dirty(handle, new_dir); + ext4_inc_count(handle, new.dir); + ext4_update_dx_flag(new.dir); + ext4_mark_inode_dirty(handle, new.dir); } } - ext4_mark_inode_dirty(handle, old_dir); - if (new_inode) { - ext4_mark_inode_dirty(handle, new_inode); - if (!new_inode->i_nlink) - ext4_orphan_add(handle, new_inode); + ext4_mark_inode_dirty(handle, old.dir); + if (new.inode) { + ext4_mark_inode_dirty(handle, new.inode); + if (!new.inode->i_nlink) + ext4_orphan_add(handle, new.inode); } retval = 0; end_rename: - brelse(dir_bh); - brelse(old_bh); - brelse(new_bh); + brelse(old.dir_bh); + brelse(old.bh); + brelse(new.bh); if (handle) ext4_journal_stop(handle); return retval; -- cgit v1.2.3 From 0d7d5d678bf9e07dffe22b018cf035d511d9e86e Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Tue, 1 Apr 2014 17:08:44 +0200 Subject: ext4: rename: move EMLINK check up Move checking i_nlink from after ext4_get_first_dir_block() to before. The check doesn't rely on the result of that function and the function only fails on fs corruption, so the order shouldn't matter. Signed-off-by: Miklos Szeredi Reviewed-by: Jan Kara --- fs/ext4/namei.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'fs/ext4') diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c index 7193cea805ff..87a8a6e613ba 100644 --- a/fs/ext4/namei.c +++ b/fs/ext4/namei.c @@ -3084,6 +3084,10 @@ static int ext4_rename(struct inode *old_dir, struct dentry *old_dentry, retval = -ENOTEMPTY; if (!empty_dir(new.inode)) goto end_rename; + } else { + retval = -EMLINK; + if (new.dir != old.dir && EXT4_DIR_LINK_MAX(new.dir)) + goto end_rename; } retval = -EIO; old.dir_bh = ext4_get_first_dir_block(handle, old.inode, @@ -3093,10 +3097,6 @@ static int ext4_rename(struct inode *old_dir, struct dentry *old_dentry, goto end_rename; if (le32_to_cpu(old.parent_de->inode) != old.dir->i_ino) goto end_rename; - retval = -EMLINK; - if (!new.inode && new.dir != old.dir && - EXT4_DIR_LINK_MAX(new.dir)) - goto end_rename; BUFFER_TRACE(old.dir_bh, "get_write_access"); retval = ext4_journal_get_write_access(handle, old.dir_bh); if (retval) -- cgit v1.2.3 From bd1af145b99311242673b32dff4599ce614352be Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Tue, 1 Apr 2014 17:08:44 +0200 Subject: ext4: rename: split out helper functions Cross rename (exchange source and dest) will need to call some of these helpers for both source and dest, while overwriting rename currently only calls them for one or the other. This also makes the code easier to follow. Signed-off-by: Miklos Szeredi Reviewed-by: Jan Kara --- fs/ext4/namei.c | 199 +++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 126 insertions(+), 73 deletions(-) (limited to 'fs/ext4') diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c index 87a8a6e613ba..75f1bde43dcc 100644 --- a/fs/ext4/namei.c +++ b/fs/ext4/namei.c @@ -3016,6 +3016,125 @@ struct ext4_renament { int dir_inlined; }; +static int ext4_rename_dir_prepare(handle_t *handle, struct ext4_renament *ent) +{ + int retval; + + ent->dir_bh = ext4_get_first_dir_block(handle, ent->inode, + &retval, &ent->parent_de, + &ent->dir_inlined); + if (!ent->dir_bh) + return retval; + if (le32_to_cpu(ent->parent_de->inode) != ent->dir->i_ino) + return -EIO; + BUFFER_TRACE(ent->dir_bh, "get_write_access"); + return ext4_journal_get_write_access(handle, ent->dir_bh); +} + +static int ext4_rename_dir_finish(handle_t *handle, struct ext4_renament *ent, + unsigned dir_ino) +{ + int retval; + + ent->parent_de->inode = cpu_to_le32(dir_ino); + BUFFER_TRACE(ent->dir_bh, "call ext4_handle_dirty_metadata"); + if (!ent->dir_inlined) { + if (is_dx(ent->inode)) { + retval = ext4_handle_dirty_dx_node(handle, + ent->inode, + ent->dir_bh); + } else { + retval = ext4_handle_dirty_dirent_node(handle, + ent->inode, + ent->dir_bh); + } + } else { + retval = ext4_mark_inode_dirty(handle, ent->inode); + } + if (retval) { + ext4_std_error(ent->dir->i_sb, retval); + return retval; + } + return 0; +} + +static int ext4_setent(handle_t *handle, struct ext4_renament *ent, + unsigned ino, unsigned file_type) +{ + int retval; + + BUFFER_TRACE(ent->bh, "get write access"); + retval = ext4_journal_get_write_access(handle, ent->bh); + if (retval) + return retval; + ent->de->inode = cpu_to_le32(ino); + if (EXT4_HAS_INCOMPAT_FEATURE(ent->dir->i_sb, + EXT4_FEATURE_INCOMPAT_FILETYPE)) + ent->de->file_type = file_type; + ent->dir->i_version++; + ent->dir->i_ctime = ent->dir->i_mtime = + ext4_current_time(ent->dir); + ext4_mark_inode_dirty(handle, ent->dir); + BUFFER_TRACE(ent->bh, "call ext4_handle_dirty_metadata"); + if (!ent->inlined) { + retval = ext4_handle_dirty_dirent_node(handle, + ent->dir, ent->bh); + if (unlikely(retval)) { + ext4_std_error(ent->dir->i_sb, retval); + return retval; + } + } + brelse(ent->bh); + ent->bh = NULL; + + return 0; +} + +static int ext4_find_delete_entry(handle_t *handle, struct inode *dir, + const struct qstr *d_name) +{ + int retval = -ENOENT; + struct buffer_head *bh; + struct ext4_dir_entry_2 *de; + + bh = ext4_find_entry(dir, d_name, &de, NULL); + if (bh) { + retval = ext4_delete_entry(handle, dir, de, bh); + brelse(bh); + } + return retval; +} + +static void ext4_rename_delete(handle_t *handle, struct ext4_renament *ent) +{ + int retval; + /* + * ent->de could have moved from under us during htree split, so make + * sure that we are deleting the right entry. We might also be pointing + * to a stale entry in the unused part of ent->bh so just checking inum + * and the name isn't enough. + */ + if (le32_to_cpu(ent->de->inode) != ent->inode->i_ino || + ent->de->name_len != ent->dentry->d_name.len || + strncmp(ent->de->name, ent->dentry->d_name.name, + ent->de->name_len)) { + retval = ext4_find_delete_entry(handle, ent->dir, + &ent->dentry->d_name); + } else { + retval = ext4_delete_entry(handle, ent->dir, ent->de, ent->bh); + if (retval == -ENOENT) { + retval = ext4_find_delete_entry(handle, ent->dir, + &ent->dentry->d_name); + } + } + + if (retval) { + ext4_warning(ent->dir->i_sb, + "Deleting old file (%lu), %d, error=%d", + ent->dir->i_ino, ent->dir->i_nlink, retval); + } +} + /* * Anybody can rename anything with this: the permission checks are left to the * higher-level routines. @@ -3089,16 +3208,7 @@ static int ext4_rename(struct inode *old_dir, struct dentry *old_dentry, if (new.dir != old.dir && EXT4_DIR_LINK_MAX(new.dir)) goto end_rename; } - retval = -EIO; - old.dir_bh = ext4_get_first_dir_block(handle, old.inode, - &retval, &old.parent_de, - &old.dir_inlined); - if (!old.dir_bh) - goto end_rename; - if (le32_to_cpu(old.parent_de->inode) != old.dir->i_ino) - goto end_rename; - BUFFER_TRACE(old.dir_bh, "get_write_access"); - retval = ext4_journal_get_write_access(handle, old.dir_bh); + retval = ext4_rename_dir_prepare(handle, &old); if (retval) goto end_rename; } @@ -3107,29 +3217,10 @@ static int ext4_rename(struct inode *old_dir, struct dentry *old_dentry, if (retval) goto end_rename; } else { - BUFFER_TRACE(new.bh, "get write access"); - retval = ext4_journal_get_write_access(handle, new.bh); + retval = ext4_setent(handle, &new, + old.inode->i_ino, old.de->file_type); if (retval) goto end_rename; - new.de->inode = cpu_to_le32(old.inode->i_ino); - if (EXT4_HAS_INCOMPAT_FEATURE(new.dir->i_sb, - EXT4_FEATURE_INCOMPAT_FILETYPE)) - new.de->file_type = old.de->file_type; - new.dir->i_version++; - new.dir->i_ctime = new.dir->i_mtime = - ext4_current_time(new.dir); - ext4_mark_inode_dirty(handle, new.dir); - BUFFER_TRACE(new.bh, "call ext4_handle_dirty_metadata"); - if (!new.inlined) { - retval = ext4_handle_dirty_dirent_node(handle, - new.dir, new.bh); - if (unlikely(retval)) { - ext4_std_error(new.dir->i_sb, retval); - goto end_rename; - } - } - brelse(new.bh); - new.bh = NULL; } /* @@ -3142,31 +3233,7 @@ static int ext4_rename(struct inode *old_dir, struct dentry *old_dentry, /* * ok, that's it */ - if (le32_to_cpu(old.de->inode) != old.inode->i_ino || - old.de->name_len != old.dentry->d_name.len || - strncmp(old.de->name, old.dentry->d_name.name, old.de->name_len) || - (retval = ext4_delete_entry(handle, old.dir, - old.de, old.bh)) == -ENOENT) { - /* old.de could have moved from under us during htree split, so - * make sure that we are deleting the right entry. We might - * also be pointing to a stale entry in the unused part of - * old.bh so just checking inum and the name isn't enough. */ - struct buffer_head *old_bh2; - struct ext4_dir_entry_2 *old_de2; - - old_bh2 = ext4_find_entry(old.dir, &old.dentry->d_name, - &old_de2, NULL); - if (old_bh2) { - retval = ext4_delete_entry(handle, old.dir, - old_de2, old_bh2); - brelse(old_bh2); - } - } - if (retval) { - ext4_warning(old.dir->i_sb, - "Deleting old file (%lu), %d, error=%d", - old.dir->i_ino, old.dir->i_nlink, retval); - } + ext4_rename_delete(handle, &old); if (new.inode) { ext4_dec_count(handle, new.inode); @@ -3175,24 +3242,10 @@ static int ext4_rename(struct inode *old_dir, struct dentry *old_dentry, old.dir->i_ctime = old.dir->i_mtime = ext4_current_time(old.dir); ext4_update_dx_flag(old.dir); if (old.dir_bh) { - old.parent_de->inode = cpu_to_le32(new.dir->i_ino); - BUFFER_TRACE(old.dir_bh, "call ext4_handle_dirty_metadata"); - if (!old.dir_inlined) { - if (is_dx(old.inode)) { - retval = ext4_handle_dirty_dx_node(handle, - old.inode, - old.dir_bh); - } else { - retval = ext4_handle_dirty_dirent_node(handle, - old.inode, old.dir_bh); - } - } else { - retval = ext4_mark_inode_dirty(handle, old.inode); - } - if (retval) { - ext4_std_error(old.dir->i_sb, retval); + retval = ext4_rename_dir_finish(handle, &old, new.dir->i_ino); + if (retval) goto end_rename; - } + ext4_dec_count(handle, old.dir); if (new.inode) { /* checked empty_dir above, can't have another parent, -- cgit v1.2.3 From bd42998a6bcb9b1708dac9ca9876e3d304c16f3d Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Tue, 1 Apr 2014 17:08:44 +0200 Subject: ext4: add cross rename support Implement RENAME_EXCHANGE flag in renameat2 syscall. Signed-off-by: Miklos Szeredi Reviewed-by: Jan Kara --- fs/ext4/namei.c | 139 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 138 insertions(+), 1 deletion(-) (limited to 'fs/ext4') 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 { struct inode *dir; struct dentry *dentry; struct inode *inode; + bool is_dir; + int dir_nlink_delta; /* entry for "dentry" */ struct buffer_head *bh; @@ -3135,6 +3137,17 @@ static void ext4_rename_delete(handle_t *handle, struct ext4_renament *ent) } } +static void ext4_update_dir_count(handle_t *handle, struct ext4_renament *ent) +{ + if (ent->dir_nlink_delta) { + if (ent->dir_nlink_delta == -1) + ext4_dec_count(handle, ent->dir); + else + ext4_inc_count(handle, ent->dir); + ext4_mark_inode_dirty(handle, ent->dir); + } +} + /* * Anybody can rename anything with this: the permission checks are left to the * higher-level routines. @@ -3274,13 +3287,137 @@ end_rename: return retval; } +static int ext4_cross_rename(struct inode *old_dir, struct dentry *old_dentry, + struct inode *new_dir, struct dentry *new_dentry) +{ + handle_t *handle = NULL; + struct ext4_renament old = { + .dir = old_dir, + .dentry = old_dentry, + .inode = old_dentry->d_inode, + }; + struct ext4_renament new = { + .dir = new_dir, + .dentry = new_dentry, + .inode = new_dentry->d_inode, + }; + u8 new_file_type; + int retval; + + dquot_initialize(old.dir); + dquot_initialize(new.dir); + + old.bh = ext4_find_entry(old.dir, &old.dentry->d_name, + &old.de, &old.inlined); + /* + * Check for inode number is _not_ due to possible IO errors. + * We might rmdir the source, keep it as pwd of some process + * and merrily kill the link to whatever was created under the + * same name. Goodbye sticky bit ;-< + */ + retval = -ENOENT; + if (!old.bh || le32_to_cpu(old.de->inode) != old.inode->i_ino) + goto end_rename; + + new.bh = ext4_find_entry(new.dir, &new.dentry->d_name, + &new.de, &new.inlined); + + /* RENAME_EXCHANGE case: old *and* new must both exist */ + if (!new.bh || le32_to_cpu(new.de->inode) != new.inode->i_ino) + goto end_rename; + + handle = ext4_journal_start(old.dir, EXT4_HT_DIR, + (2 * EXT4_DATA_TRANS_BLOCKS(old.dir->i_sb) + + 2 * EXT4_INDEX_EXTRA_TRANS_BLOCKS + 2)); + if (IS_ERR(handle)) + return PTR_ERR(handle); + + if (IS_DIRSYNC(old.dir) || IS_DIRSYNC(new.dir)) + ext4_handle_sync(handle); + + if (S_ISDIR(old.inode->i_mode)) { + old.is_dir = true; + retval = ext4_rename_dir_prepare(handle, &old); + if (retval) + goto end_rename; + } + if (S_ISDIR(new.inode->i_mode)) { + new.is_dir = true; + retval = ext4_rename_dir_prepare(handle, &new); + if (retval) + goto end_rename; + } + + /* + * Other than the special case of overwriting a directory, parents' + * nlink only needs to be modified if this is a cross directory rename. + */ + if (old.dir != new.dir && old.is_dir != new.is_dir) { + old.dir_nlink_delta = old.is_dir ? -1 : 1; + new.dir_nlink_delta = -old.dir_nlink_delta; + retval = -EMLINK; + if ((old.dir_nlink_delta > 0 && EXT4_DIR_LINK_MAX(old.dir)) || + (new.dir_nlink_delta > 0 && EXT4_DIR_LINK_MAX(new.dir))) + goto end_rename; + } + + new_file_type = new.de->file_type; + retval = ext4_setent(handle, &new, old.inode->i_ino, old.de->file_type); + if (retval) + goto end_rename; + + retval = ext4_setent(handle, &old, new.inode->i_ino, new_file_type); + if (retval) + goto end_rename; + + /* + * Like most other Unix systems, set the ctime for inodes on a + * rename. + */ + old.inode->i_ctime = ext4_current_time(old.inode); + new.inode->i_ctime = ext4_current_time(new.inode); + ext4_mark_inode_dirty(handle, old.inode); + ext4_mark_inode_dirty(handle, new.inode); + + if (old.dir_bh) { + retval = ext4_rename_dir_finish(handle, &old, new.dir->i_ino); + if (retval) + goto end_rename; + } + if (new.dir_bh) { + retval = ext4_rename_dir_finish(handle, &new, old.dir->i_ino); + if (retval) + goto end_rename; + } + ext4_update_dir_count(handle, &old); + ext4_update_dir_count(handle, &new); + retval = 0; + +end_rename: + brelse(old.dir_bh); + brelse(new.dir_bh); + brelse(old.bh); + brelse(new.bh); + if (handle) + ext4_journal_stop(handle); + return retval; +} + static int ext4_rename2(struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry, unsigned int flags) { - if (flags & ~RENAME_NOREPLACE) + if (flags & ~(RENAME_NOREPLACE | RENAME_EXCHANGE)) return -EINVAL; + if (flags & RENAME_EXCHANGE) { + return ext4_cross_rename(old_dir, old_dentry, + new_dir, new_dentry); + } + /* + * Existence checking was done by the VFS, otherwise "RENAME_NOREPLACE" + * is equivalent to regular rename. + */ return ext4_rename(old_dir, old_dentry, new_dir, new_dentry); } -- cgit v1.2.3