btrfs-progs: check: repair inode nlink in lowmem
authorSu Yue <suy.fnst@cn.fujitsu.com>
Tue, 29 Aug 2017 03:23:46 +0000 (11:23 +0800)
committerDavid Sterba <dsterba@suse.com>
Mon, 16 Oct 2017 18:33:00 +0000 (20:33 +0200)
New function repair_inode_nlinks_lowmem() sets nlink of the inode to refs.

If refs equals 0, move the inode to lost+found and set refs to 1
initially.

Signed-off-by: Su Yue <suy.fnst@cn.fujitsu.com>
Signed-off-by: David Sterba <dsterba@suse.com>
cmds-check.c

index 437a084..ffb3e0c 100644 (file)
@@ -3018,15 +3018,79 @@ static int get_highest_inode(struct btrfs_trans_handle *trans,
        return ret;
 }
 
+/*
+ * Link inode to dir 'lost+found'. Increase @ref_count.
+ *
+ * Returns 0 means success.
+ * Returns <0 means failure.
+ */
+static int link_inode_to_lostfound(struct btrfs_trans_handle *trans,
+                                  struct btrfs_root *root,
+                                  struct btrfs_path *path,
+                                  u64 ino, char *namebuf, u32 name_len,
+                                  u8 filetype, u64 *ref_count)
+{
+       char *dir_name = "lost+found";
+       u64 lost_found_ino;
+       int ret;
+       u32 mode = 0700;
+
+       btrfs_release_path(path);
+       ret = get_highest_inode(trans, root, path, &lost_found_ino);
+       if (ret < 0)
+               goto out;
+       lost_found_ino++;
+
+       ret = btrfs_mkdir(trans, root, dir_name, strlen(dir_name),
+                         BTRFS_FIRST_FREE_OBJECTID, &lost_found_ino,
+                         mode);
+       if (ret < 0) {
+               error("failed to create '%s' dir: %s", dir_name, strerror(-ret));
+               goto out;
+       }
+       ret = btrfs_add_link(trans, root, ino, lost_found_ino,
+                            namebuf, name_len, filetype, NULL, 1, 0);
+       /*
+        * Add ".INO" suffix several times to handle case where
+        * "FILENAME.INO" is already taken by another file.
+        */
+       while (ret == -EEXIST) {
+               /*
+                * Conflicting file name, add ".INO" as suffix * +1 for '.'
+                */
+               if (name_len + count_digits(ino) + 1 > BTRFS_NAME_LEN) {
+                       ret = -EFBIG;
+                       goto out;
+               }
+               snprintf(namebuf + name_len, BTRFS_NAME_LEN - name_len,
+                        ".%llu", ino);
+               name_len += count_digits(ino) + 1;
+               ret = btrfs_add_link(trans, root, ino, lost_found_ino, namebuf,
+                                    name_len, filetype, NULL, 1, 0);
+       }
+       if (ret < 0) {
+               error("failed to link the inode %llu to %s dir: %s",
+                     ino, dir_name, strerror(-ret));
+               goto out;
+       }
+
+       ++*ref_count;
+       printf("Moving file '%.*s' to '%s' dir since it has no valid backref\n",
+              name_len, namebuf, dir_name);
+out:
+       btrfs_release_path(path);
+       if (ret)
+               error("failed to move file '%.*s' to '%s' dir", name_len,
+                               namebuf, dir_name);
+       return ret;
+}
+
 static int repair_inode_nlinks(struct btrfs_trans_handle *trans,
                               struct btrfs_root *root,
                               struct btrfs_path *path,
                               struct inode_record *rec)
 {
-       char *dir_name = "lost+found";
        char namebuf[BTRFS_NAME_LEN] = {0};
-       u64 lost_found_ino;
-       u32 mode = 0700;
        u8 type = 0;
        int namelen = 0;
        int name_recovered = 0;
@@ -3063,55 +3127,11 @@ static int repair_inode_nlinks(struct btrfs_trans_handle *trans,
        }
 
        if (rec->found_link == 0) {
-               ret = get_highest_inode(trans, root, path, &lost_found_ino);
-               if (ret < 0)
-                       goto out;
-               lost_found_ino++;
-               ret = btrfs_mkdir(trans, root, dir_name, strlen(dir_name),
-                                 BTRFS_FIRST_FREE_OBJECTID, &lost_found_ino,
-                                 mode);
-               if (ret < 0) {
-                       fprintf(stderr, "Failed to create '%s' dir: %s\n",
-                               dir_name, strerror(-ret));
-                       goto out;
-               }
-               ret = btrfs_add_link(trans, root, rec->ino, lost_found_ino,
-                                    namebuf, namelen, type, NULL, 1, 0);
-               /*
-                * Add ".INO" suffix several times to handle case where
-                * "FILENAME.INO" is already taken by another file.
-                */
-               while (ret == -EEXIST) {
-                       /*
-                        * Conflicting file name, add ".INO" as suffix * +1 for '.'
-                        */
-                       if (namelen + count_digits(rec->ino) + 1 >
-                           BTRFS_NAME_LEN) {
-                               ret = -EFBIG;
-                               goto out;
-                       }
-                       snprintf(namebuf + namelen, BTRFS_NAME_LEN - namelen,
-                                ".%llu", rec->ino);
-                       namelen += count_digits(rec->ino) + 1;
-                       ret = btrfs_add_link(trans, root, rec->ino,
-                                            lost_found_ino, namebuf,
-                                            namelen, type, NULL, 1, 0);
-               }
-               if (ret < 0) {
-                       fprintf(stderr,
-                               "Failed to link the inode %llu to %s dir: %s\n",
-                               rec->ino, dir_name, strerror(-ret));
+               ret = link_inode_to_lostfound(trans, root, path, rec->ino,
+                                             namebuf, namelen, type,
+                                             (u64 *)&rec->found_link);
+               if (ret)
                        goto out;
-               }
-               /*
-                * Just increase the found_link, don't actually add the
-                * backref. This will make things easier and this inode
-                * record will be freed after the repair is done.
-                * So fsck will not report problem about this inode.
-                */
-               rec->found_link++;
-               printf("Moving file '%.*s' to '%s' dir since it has no valid backref\n",
-                      namelen, namebuf, dir_name);
        }
        printf("Fixed the nlink of inode %llu\n", rec->ino);
 out:
@@ -5592,6 +5612,92 @@ out:
        return err;
 }
 
+/* Set inode_item nlink to @ref_count.
+ * If @ref_count == 0, move it to "lost+found" and increase @ref_count.
+ *
+ * Returns 0 on success
+ */
+static int repair_inode_nlinks_lowmem(struct btrfs_root *root,
+                                     struct btrfs_path *path, u64 ino,
+                                     const char *name, u32 namelen,
+                                     u64 ref_count, u8 filetype, u64 *nlink)
+{
+       struct btrfs_trans_handle *trans;
+       struct btrfs_inode_item *ii;
+       struct btrfs_key key;
+       struct btrfs_key old_key;
+       char namebuf[BTRFS_NAME_LEN] = {0};
+       int name_len;
+       int ret;
+       int ret2;
+
+       /* save the key */
+       btrfs_item_key_to_cpu(path->nodes[0], &old_key, path->slots[0]);
+
+       if (name && namelen) {
+               ASSERT(namelen <= BTRFS_NAME_LEN);
+               memcpy(namebuf, name, namelen);
+               name_len = namelen;
+       } else {
+               sprintf(namebuf, "%llu", ino);
+               name_len = count_digits(ino);
+               printf("Can't find file name for inode %llu, use %s instead\n",
+                      ino, namebuf);
+       }
+
+       trans = btrfs_start_transaction(root, 1);
+       if (IS_ERR(trans)) {
+               ret = PTR_ERR(trans);
+               goto out;
+       }
+
+       btrfs_release_path(path);
+       /* if refs is 0, put it into lostfound */
+       if (ref_count == 0) {
+               ret = link_inode_to_lostfound(trans, root, path, ino, namebuf,
+                                             name_len, filetype, &ref_count);
+               if (ret)
+                       goto fail;
+       }
+
+       /* reset inode_item's nlink to ref_count */
+       key.objectid = ino;
+       key.type = BTRFS_INODE_ITEM_KEY;
+       key.offset = 0;
+
+       btrfs_release_path(path);
+       ret = btrfs_search_slot(trans, root, &key, path, 0, 1);
+       if (ret > 0)
+               ret = -ENOENT;
+       if (ret)
+               goto fail;
+
+       ii = btrfs_item_ptr(path->nodes[0], path->slots[0],
+                           struct btrfs_inode_item);
+       btrfs_set_inode_nlink(path->nodes[0], ii, ref_count);
+       btrfs_mark_buffer_dirty(path->nodes[0]);
+
+       if (nlink)
+               *nlink = ref_count;
+fail:
+       btrfs_commit_transaction(trans, root);
+out:
+       if (ret)
+               error(
+       "fail to repair nlink of inode %llu root %llu name %s filetype %u",
+                      root->objectid, ino, namebuf, filetype);
+       else
+               printf("Fixed nlink of inode %llu root %llu name %s filetype %u\n",
+                      root->objectid, ino, namebuf, filetype);
+
+       /* research */
+       btrfs_release_path(path);
+       ret2 = btrfs_search_slot(NULL, root, &old_key, path, 0, 0);
+       if (ret2 < 0)
+               return ret |= ret2;
+       return ret;
+}
+
 /*
  * Check INODE_ITEM and related ITEMs (the same inode number)
  * 1. check link count
@@ -5715,6 +5821,13 @@ out:
                        err &= ~DIR_COUNT_AGAIN;
                        count_dir_isize(root, inode_id, &size);
                }
+
+               if ((nlink != 1 || refs != 1) && repair) {
+                       ret = repair_inode_nlinks_lowmem(root, path, inode_id,
+                               namebuf, name_len, refs, imode_to_type(mode),
+                               &nlink);
+               }
+
                if (nlink != 1) {
                        err |= LINK_COUNT_ERROR;
                        error("root %llu DIR INODE[%llu] shouldn't have more than one link(%llu)",
@@ -5744,9 +5857,16 @@ out:
                }
        } else {
                if (nlink != refs) {
-                       err |= LINK_COUNT_ERROR;
-                       error("root %llu INODE[%llu] nlink(%llu) not equal to inode_refs(%llu)",
-                             root->objectid, inode_id, nlink, refs);
+                       if (repair)
+                               ret = repair_inode_nlinks_lowmem(root, path,
+                                        inode_id, namebuf, name_len, refs,
+                                        imode_to_type(mode), &nlink);
+                       if (!repair || ret) {
+                               err |= LINK_COUNT_ERROR;
+                               error(
+               "root %llu INODE[%llu] nlink(%llu) not equal to inode_refs(%llu)",
+                                     root->objectid, inode_id, nlink, refs);
+                       }
                } else if (!nlink) {
                        if (repair)
                                ret = repair_inode_orphan_item_lowmem(root,