diff --git a/cmds-check.c b/cmds-check.c index 437a0842..ffb3e0ca 100644 --- a/cmds-check.c +++ b/cmds-check.c @@ -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) + ret = link_inode_to_lostfound(trans, root, path, rec->ino, + namebuf, namelen, type, + (u64 *)&rec->found_link); + if (ret) 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)); - 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,