f2fs: convert inline_dir early before starting rename
authorJaegeuk Kim <jaegeuk@kernel.org>
Tue, 10 Dec 2019 03:03:05 +0000 (19:03 -0800)
committerJaegeuk Kim <jaegeuk@kernel.org>
Sat, 18 Jan 2020 00:48:42 +0000 (16:48 -0800)
If we hit an error during rename, we'll get two dentries in different
directories.

Chao adds to check the room in inline_dir which can avoid needless
inversion. This should be done by inode_lock(&old_dir).

Signed-off-by: Chao Yu <yuchao0@huawei.com>
Signed-off-by: Jaegeuk Kim <jaegeuk@kernel.org>
fs/f2fs/dir.c
fs/f2fs/f2fs.h
fs/f2fs/inline.c
fs/f2fs/namei.c

index c967cac..b56f606 100644 (file)
@@ -578,6 +578,20 @@ next:
        goto next;
 }
 
+bool f2fs_has_enough_room(struct inode *dir, struct page *ipage,
+                                       struct fscrypt_name *fname)
+{
+       struct f2fs_dentry_ptr d;
+       unsigned int bit_pos;
+       int slots = GET_DENTRY_SLOTS(fname_len(fname));
+
+       make_dentry_ptr_inline(dir, &d, inline_data_addr(dir, ipage));
+
+       bit_pos = f2fs_room_for_filename(d.bitmap, slots, d.max);
+
+       return bit_pos < d.max;
+}
+
 void f2fs_update_dentry(nid_t ino, umode_t mode, struct f2fs_dentry_ptr *d,
                                const struct qstr *name, f2fs_hash_t name_hash,
                                unsigned int bit_pos)
index 1449405..e720844 100644 (file)
@@ -3107,6 +3107,8 @@ ino_t f2fs_inode_by_name(struct inode *dir, const struct qstr *qstr,
                        struct page **page);
 void f2fs_set_link(struct inode *dir, struct f2fs_dir_entry *de,
                        struct page *page, struct inode *inode);
+bool f2fs_has_enough_room(struct inode *dir, struct page *ipage,
+                       struct fscrypt_name *fname);
 void f2fs_update_dentry(nid_t ino, umode_t mode, struct f2fs_dentry_ptr *d,
                        const struct qstr *name, f2fs_hash_t name_hash,
                        unsigned int bit_pos);
@@ -3649,6 +3651,7 @@ void f2fs_truncate_inline_inode(struct inode *inode,
 int f2fs_read_inline_data(struct inode *inode, struct page *page);
 int f2fs_convert_inline_page(struct dnode_of_data *dn, struct page *page);
 int f2fs_convert_inline_inode(struct inode *inode);
+int f2fs_try_convert_inline_dir(struct inode *dir, struct dentry *dentry);
 int f2fs_write_inline_data(struct inode *inode, struct page *page);
 bool f2fs_recover_inline_data(struct inode *inode, struct page *npage);
 struct f2fs_dir_entry *f2fs_find_in_inline_dir(struct inode *dir,
index 52f85ed..4167e54 100644 (file)
@@ -530,7 +530,7 @@ recover:
        return err;
 }
 
-static int f2fs_convert_inline_dir(struct inode *dir, struct page *ipage,
+static int do_convert_inline_dir(struct inode *dir, struct page *ipage,
                                                        void *inline_dentry)
 {
        if (!F2FS_I(dir)->i_dir_level)
@@ -539,6 +539,44 @@ static int f2fs_convert_inline_dir(struct inode *dir, struct page *ipage,
                return f2fs_move_rehashed_dirents(dir, ipage, inline_dentry);
 }
 
+int f2fs_try_convert_inline_dir(struct inode *dir, struct dentry *dentry)
+{
+       struct f2fs_sb_info *sbi = F2FS_I_SB(dir);
+       struct page *ipage;
+       struct fscrypt_name fname;
+       void *inline_dentry = NULL;
+       int err = 0;
+
+       if (!f2fs_has_inline_dentry(dir))
+               return 0;
+
+       f2fs_lock_op(sbi);
+
+       err = fscrypt_setup_filename(dir, &dentry->d_name, 0, &fname);
+       if (err)
+               goto out;
+
+       ipage = f2fs_get_node_page(sbi, dir->i_ino);
+       if (IS_ERR(ipage)) {
+               err = PTR_ERR(ipage);
+               goto out;
+       }
+
+       if (f2fs_has_enough_room(dir, ipage, &fname)) {
+               f2fs_put_page(ipage, 1);
+               goto out;
+       }
+
+       inline_dentry = inline_data_addr(dir, ipage);
+
+       err = do_convert_inline_dir(dir, ipage, inline_dentry);
+       if (!err)
+               f2fs_put_page(ipage, 1);
+out:
+       f2fs_unlock_op(sbi);
+       return err;
+}
+
 int f2fs_add_inline_entry(struct inode *dir, const struct qstr *new_name,
                                const struct qstr *orig_name,
                                struct inode *inode, nid_t ino, umode_t mode)
@@ -562,7 +600,7 @@ int f2fs_add_inline_entry(struct inode *dir, const struct qstr *new_name,
 
        bit_pos = f2fs_room_for_filename(d.bitmap, slots, d.max);
        if (bit_pos >= d.max) {
-               err = f2fs_convert_inline_dir(dir, ipage, inline_dentry);
+               err = do_convert_inline_dir(dir, ipage, inline_dentry);
                if (err)
                        return err;
                err = -EAGAIN;
index d479b91..2aa0354 100644 (file)
@@ -906,7 +906,6 @@ static int f2fs_rename(struct inode *old_dir, struct dentry *old_dentry,
        struct f2fs_dir_entry *old_dir_entry = NULL;
        struct f2fs_dir_entry *old_entry;
        struct f2fs_dir_entry *new_entry;
-       bool is_old_inline = f2fs_has_inline_dentry(old_dir);
        int err;
 
        if (unlikely(f2fs_cp_error(sbi)))
@@ -919,6 +918,20 @@ static int f2fs_rename(struct inode *old_dir, struct dentry *old_dentry,
                        F2FS_I(old_dentry->d_inode)->i_projid)))
                return -EXDEV;
 
+       /*
+        * If new_inode is null, the below renaming flow will
+        * add a link in old_dir which can conver inline_dir.
+        * After then, if we failed to get the entry due to other
+        * reasons like ENOMEM, we had to remove the new entry.
+        * Instead of adding such the error handling routine, let's
+        * simply convert first here.
+        */
+       if (old_dir == new_dir && !new_inode) {
+               err = f2fs_try_convert_inline_dir(old_dir, new_dentry);
+               if (err)
+                       return err;
+       }
+
        if (flags & RENAME_WHITEOUT) {
                err = f2fs_create_whiteout(old_dir, &whiteout);
                if (err)
@@ -1006,28 +1019,6 @@ static int f2fs_rename(struct inode *old_dir, struct dentry *old_dentry,
 
                if (old_dir_entry)
                        f2fs_i_links_write(new_dir, true);
-
-               /*
-                * old entry and new entry can locate in the same inline
-                * dentry in inode, when attaching new entry in inline dentry,
-                * it could force inline dentry conversion, after that,
-                * old_entry and old_page will point to wrong address, in
-                * order to avoid this, let's do the check and update here.
-                */
-               if (is_old_inline && !f2fs_has_inline_dentry(old_dir)) {
-                       f2fs_put_page(old_page, 0);
-                       old_page = NULL;
-
-                       old_entry = f2fs_find_entry(old_dir,
-                                               &old_dentry->d_name, &old_page);
-                       if (!old_entry) {
-                               err = -ENOENT;
-                               if (IS_ERR(old_page))
-                                       err = PTR_ERR(old_page);
-                               f2fs_unlock_op(sbi);
-                               goto out_dir;
-                       }
-               }
        }
 
        down_write(&F2FS_I(old_inode)->i_sem);