erofs-utils: fix up unchanged directory pNIDs for incremental builds
authorGao Xiang <hsiangkao@linux.alibaba.com>
Tue, 18 Jun 2024 08:24:10 +0000 (16:24 +0800)
committerGao Xiang <hsiangkao@linux.alibaba.com>
Wed, 19 Jun 2024 03:11:32 +0000 (11:11 +0800)
For incremental builds, it's unnecessary to dump all unchanged
directories, yet the pNIDs of those directories need to be fixed to
the new parent on-disk inodes.

Signed-off-by: Gao Xiang <hsiangkao@linux.alibaba.com>
Link: https://lore.kernel.org/r/20240618082414.47876-5-hsiangkao@linux.alibaba.com
lib/inode.c

index 321b2f5b27a6f3bc595a7305ad44b8ce8674d43f..6c4fa2a2d93dcf694c625e5cbb5608ba8b86909e 100644 (file)
@@ -338,6 +338,84 @@ static void erofs_d_invalidate(struct erofs_dentry *d)
        erofs_iput(inode);
 }
 
+static int erofs_rebuild_inode_fix_pnid(struct erofs_inode *parent,
+                                       erofs_nid_t nid)
+{
+       struct erofs_inode dir = {
+               .sbi = parent->sbi,
+               .nid = nid
+       };
+       unsigned int bsz = erofs_blksiz(dir.sbi);
+       unsigned int err, isz;
+       erofs_off_t boff, off;
+       erofs_nid_t pnid;
+       bool fixed = false;
+
+       err = erofs_read_inode_from_disk(&dir);
+       if (err)
+               return err;
+
+       if (!S_ISDIR(dir.i_mode))
+               return -ENOTDIR;
+
+       if (dir.datalayout != EROFS_INODE_FLAT_INLINE &&
+           dir.datalayout != EROFS_INODE_FLAT_PLAIN)
+               return -EOPNOTSUPP;
+
+       pnid = erofs_lookupnid(parent);
+       isz = dir.inode_isize + dir.xattr_isize;
+       boff = erofs_pos(dir.sbi, dir.u.i_blkaddr);
+       for (off = 0; off < dir.i_size; off += bsz) {
+               char buf[EROFS_MAX_BLOCK_SIZE];
+               struct erofs_dirent *de = (struct erofs_dirent *)buf;
+               unsigned int nameoff, count, de_nameoff;
+
+               count = min_t(erofs_off_t, bsz, dir.i_size - off);
+               err = erofs_pread(&dir, buf, count, off);
+               if (err)
+                       return err;
+
+               nameoff = le16_to_cpu(de->nameoff);
+               if (nameoff < sizeof(struct erofs_dirent) ||
+                   nameoff >= count) {
+                       erofs_err("invalid de[0].nameoff %u @ nid %llu, offset %llu",
+                                 nameoff, dir.nid | 0ULL, off | 0ULL);
+                       return -EFSCORRUPTED;
+               }
+
+               while ((char *)de < buf + nameoff) {
+                       de_nameoff = le16_to_cpu(de->nameoff);
+                       if (((char *)(de + 1) >= buf + nameoff ?
+                               strnlen(buf + de_nameoff, count - de_nameoff) == 2 :
+                               le16_to_cpu(de[1].nameoff) == de_nameoff + 2) &&
+                          !memcmp(buf + de_nameoff, "..", 2)) {
+                               if (de->nid == cpu_to_le64(pnid))
+                                       return 0;
+                               de->nid = cpu_to_le64(pnid);
+                               fixed = true;
+                               break;
+                       }
+                       ++de;
+               }
+
+               if (!fixed)
+                       continue;
+               err = erofs_dev_write(dir.sbi, buf,
+                       (off + bsz > dir.i_size &&
+                               dir.datalayout == EROFS_INODE_FLAT_INLINE ?
+                               erofs_iloc(&dir) + isz : boff + off), count);
+               erofs_dbg("directory %llu pNID is updated to %llu",
+                         nid | 0ULL, pnid | 0ULL);
+               break;
+       }
+       if (err || fixed)
+               return err;
+
+       erofs_err("directory data %llu is corrupted (\"..\" not found)",
+                 nid | 0ULL);
+       return -EFSCORRUPTED;
+}
+
 static int erofs_write_dir_file(struct erofs_inode *dir)
 {
        struct erofs_dentry *head = list_first_entry(&dir->i_subdirs,
@@ -359,6 +437,13 @@ static int erofs_write_dir_file(struct erofs_inode *dir)
                const unsigned int len = strlen(d->name) +
                        sizeof(struct erofs_dirent);
 
+               /* XXX: a bit hacky, but to avoid another traversal */
+               if (d->validnid && d->type == EROFS_FT_DIR) {
+                       ret = erofs_rebuild_inode_fix_pnid(dir, d->nid);
+                       if (ret)
+                               return ret;
+               }
+
                erofs_d_invalidate(d);
                if (used + len > erofs_blksiz(sbi)) {
                        ret = write_dirblock(sbi, q, head, d,
@@ -1193,7 +1278,9 @@ static int erofs_mkfs_jobfn(struct erofs_mkfs_jobitem *item)
        }
 
        if (item->type == EROFS_MKFS_JOB_DIR_BH) {
-               erofs_write_dir_file(inode);
+               ret = erofs_write_dir_file(inode);
+               if (ret)
+                       return ret;
                erofs_write_tail_end(inode);
                inode->bh->op = &erofs_write_inode_bhops;
                erofs_iput(inode);