udf: Convert udf_rename() to new directory iteration code
authorJan Kara <jack@suse.cz>
Thu, 6 Oct 2022 14:41:23 +0000 (16:41 +0200)
committerJan Kara <jack@suse.cz>
Mon, 9 Jan 2023 09:39:52 +0000 (10:39 +0100)
Convert udf_rename() to use new directory iteration code.

Reported-by: syzbot+0eaad3590d65102b9391@syzkaller.appspotmail.com
Reported-by: syzbot+b7fc73213bc2361ab650@syzkaller.appspotmail.com
Signed-off-by: Jan Kara <jack@suse.cz>
fs/udf/namei.c

index 1b3ee80..b9eb764 100644 (file)
@@ -1257,78 +1257,68 @@ static int udf_rename(struct user_namespace *mnt_userns, struct inode *old_dir,
 {
        struct inode *old_inode = d_inode(old_dentry);
        struct inode *new_inode = d_inode(new_dentry);
-       struct udf_fileident_bh ofibh, nfibh;
-       struct fileIdentDesc *ofi = NULL, *nfi = NULL, *dir_fi = NULL;
-       struct fileIdentDesc ocfi, ncfi;
-       struct buffer_head *dir_bh = NULL;
-       int retval = -ENOENT;
+       struct udf_fileident_iter oiter, niter, diriter;
+       bool has_diriter = false;
+       int retval;
        struct kernel_lb_addr tloc;
-       struct udf_inode_info *old_iinfo = UDF_I(old_inode);
 
        if (flags & ~RENAME_NOREPLACE)
                return -EINVAL;
 
-       ofi = udf_find_entry(old_dir, &old_dentry->d_name, &ofibh, &ocfi);
-       if (!ofi || IS_ERR(ofi)) {
-               if (IS_ERR(ofi))
-                       retval = PTR_ERR(ofi);
-               goto end_rename;
-       }
-
-       if (ofibh.sbh != ofibh.ebh)
-               brelse(ofibh.ebh);
-
-       brelse(ofibh.sbh);
-       tloc = lelb_to_cpu(ocfi.icb.extLocation);
-       if (udf_get_lb_pblock(old_dir->i_sb, &tloc, 0) != old_inode->i_ino)
-               goto end_rename;
+       retval = udf_fiiter_find_entry(old_dir, &old_dentry->d_name, &oiter);
+       if (retval)
+               return retval;
 
-       nfi = udf_find_entry(new_dir, &new_dentry->d_name, &nfibh, &ncfi);
-       if (IS_ERR(nfi)) {
-               retval = PTR_ERR(nfi);
-               goto end_rename;
+       tloc = lelb_to_cpu(oiter.fi.icb.extLocation);
+       if (udf_get_lb_pblock(old_dir->i_sb, &tloc, 0) != old_inode->i_ino) {
+               retval = -ENOENT;
+               goto out_oiter;
        }
-       if (nfi && !new_inode) {
-               if (nfibh.sbh != nfibh.ebh)
-                       brelse(nfibh.ebh);
-               brelse(nfibh.sbh);
-               nfi = NULL;
-       }
-       if (S_ISDIR(old_inode->i_mode)) {
-               int offset = udf_ext0_offset(old_inode);
 
+       if (S_ISDIR(old_inode->i_mode)) {
                if (new_inode) {
                        retval = -ENOTEMPTY;
                        if (!empty_dir(new_inode))
-                               goto end_rename;
+                               goto out_oiter;
                }
-               retval = -EIO;
-               if (old_iinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB) {
-                       dir_fi = udf_get_fileident(
-                                       old_iinfo->i_data -
-                                         (old_iinfo->i_efe ?
-                                          sizeof(struct extendedFileEntry) :
-                                          sizeof(struct fileEntry)),
-                                       old_inode->i_sb->s_blocksize, &offset);
-               } else {
-                       dir_bh = udf_bread(old_inode, 0, 0, &retval);
-                       if (!dir_bh)
-                               goto end_rename;
-                       dir_fi = udf_get_fileident(dir_bh->b_data,
-                                       old_inode->i_sb->s_blocksize, &offset);
+               retval = udf_fiiter_find_entry(old_inode, &dotdot_name,
+                                              &diriter);
+               if (retval == -ENOENT) {
+                       udf_err(old_inode->i_sb,
+                               "directory (ino %lu) has no '..' entry\n",
+                               old_inode->i_ino);
+                       retval = -EFSCORRUPTED;
                }
-               if (!dir_fi)
-                       goto end_rename;
-               tloc = lelb_to_cpu(dir_fi->icb.extLocation);
+               if (retval)
+                       goto out_oiter;
+               has_diriter = true;
+               tloc = lelb_to_cpu(diriter.fi.icb.extLocation);
                if (udf_get_lb_pblock(old_inode->i_sb, &tloc, 0) !=
-                               old_dir->i_ino)
-                       goto end_rename;
+                               old_dir->i_ino) {
+                       retval = -EFSCORRUPTED;
+                       udf_err(old_inode->i_sb,
+                               "directory (ino %lu) has parent entry pointing to another inode (%lu != %u)\n",
+                               old_inode->i_ino, old_dir->i_ino,
+                               udf_get_lb_pblock(old_inode->i_sb, &tloc, 0));
+                       goto out_oiter;
+               }
        }
-       if (!nfi) {
-               nfi = udf_add_entry(new_dir, new_dentry, &nfibh, &ncfi,
-                                   &retval);
-               if (!nfi)
-                       goto end_rename;
+
+       retval = udf_fiiter_find_entry(new_dir, &new_dentry->d_name, &niter);
+       if (retval && retval != -ENOENT)
+               goto out_oiter;
+       /* Entry found but not passed by VFS? */
+       if (!retval && !new_inode) {
+               retval = -EFSCORRUPTED;
+               udf_fiiter_release(&niter);
+               goto out_oiter;
+       }
+       /* Entry not found? Need to add one... */
+       if (retval) {
+               udf_fiiter_release(&niter);
+               retval = udf_fiiter_add_entry(new_dir, new_dentry, &niter);
+               if (retval)
+                       goto out_oiter;
        }
 
        /*
@@ -1341,14 +1331,26 @@ static int udf_rename(struct user_namespace *mnt_userns, struct inode *old_dir,
        /*
         * ok, that's it
         */
-       ncfi.fileVersionNum = ocfi.fileVersionNum;
-       ncfi.fileCharacteristics = ocfi.fileCharacteristics;
-       memcpy(&(ncfi.icb), &(ocfi.icb), sizeof(ocfi.icb));
-       udf_write_fi(new_dir, &ncfi, nfi, &nfibh, NULL, NULL);
+       niter.fi.fileVersionNum = oiter.fi.fileVersionNum;
+       niter.fi.fileCharacteristics = oiter.fi.fileCharacteristics;
+       memcpy(&(niter.fi.icb), &(oiter.fi.icb), sizeof(oiter.fi.icb));
+       udf_fiiter_write_fi(&niter, NULL);
+       udf_fiiter_release(&niter);
 
-       /* The old fid may have moved - find it again */
-       ofi = udf_find_entry(old_dir, &old_dentry->d_name, &ofibh, &ocfi);
-       udf_delete_entry(old_dir, ofi, &ofibh, &ocfi);
+       /*
+        * The old entry may have moved due to new entry allocation. Find it
+        * again.
+        */
+       udf_fiiter_release(&oiter);
+       retval = udf_fiiter_find_entry(old_dir, &old_dentry->d_name, &oiter);
+       if (retval) {
+               udf_err(old_dir->i_sb,
+                       "failed to find renamed entry again in directory (ino %lu)\n",
+                       old_dir->i_ino);
+       } else {
+               udf_fiiter_delete_entry(&oiter);
+               udf_fiiter_release(&oiter);
+       }
 
        if (new_inode) {
                new_inode->i_ctime = current_time(new_inode);
@@ -1359,13 +1361,13 @@ static int udf_rename(struct user_namespace *mnt_userns, struct inode *old_dir,
        mark_inode_dirty(old_dir);
        mark_inode_dirty(new_dir);
 
-       if (dir_fi) {
-               dir_fi->icb.extLocation = cpu_to_lelb(UDF_I(new_dir)->i_location);
-               udf_update_tag((char *)dir_fi, udf_dir_entry_len(dir_fi));
-               if (old_iinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB)
-                       mark_inode_dirty(old_inode);
-               else
-                       mark_buffer_dirty_inode(dir_bh, old_inode);
+       if (has_diriter) {
+               diriter.fi.icb.extLocation =
+                                       cpu_to_lelb(UDF_I(new_dir)->i_location);
+               udf_update_tag((char *)&diriter.fi,
+                              udf_dir_entry_len(&diriter.fi));
+               udf_fiiter_write_fi(&diriter, NULL);
+               udf_fiiter_release(&diriter);
 
                inode_dec_link_count(old_dir);
                if (new_inode)
@@ -1375,22 +1377,11 @@ static int udf_rename(struct user_namespace *mnt_userns, struct inode *old_dir,
                        mark_inode_dirty(new_dir);
                }
        }
-
-       if (ofi) {
-               if (ofibh.sbh != ofibh.ebh)
-                       brelse(ofibh.ebh);
-               brelse(ofibh.sbh);
-       }
-
-       retval = 0;
-
-end_rename:
-       brelse(dir_bh);
-       if (nfi) {
-               if (nfibh.sbh != nfibh.ebh)
-                       brelse(nfibh.ebh);
-               brelse(nfibh.sbh);
-       }
+       return 0;
+out_oiter:
+       if (has_diriter)
+               udf_fiiter_release(&diriter);
+       udf_fiiter_release(&oiter);
 
        return retval;
 }