summaryrefslogtreecommitdiff
path: root/cmds-check.c
diff options
context:
space:
mode:
Diffstat (limited to 'cmds-check.c')
-rw-r--r--cmds-check.c193
1 files changed, 189 insertions, 4 deletions
diff --git a/cmds-check.c b/cmds-check.c
index 9c0189d9..2ea273c2 100644
--- a/cmds-check.c
+++ b/cmds-check.c
@@ -1837,6 +1837,18 @@ static int repair_inode_backrefs(struct btrfs_root *root,
struct btrfs_trans_handle *trans;
struct btrfs_key location;
+ ret = check_dir_conflict(root, backref->name,
+ backref->namelen,
+ backref->dir,
+ backref->index);
+ if (ret) {
+ /*
+ * let nlink fixing routine to handle it,
+ * which can do it better.
+ */
+ ret = 0;
+ break;
+ }
location.objectid = rec->ino;
location.type = BTRFS_INODE_ITEM_KEY;
location.offset = 0;
@@ -1915,20 +1927,191 @@ static int find_file_name(struct inode_record *rec,
return -ENOENT;
}
+/* Reset the nlink of the inode to the correct one */
+static int reset_nlink(struct btrfs_trans_handle *trans,
+ struct btrfs_root *root,
+ struct btrfs_path *path,
+ struct inode_record *rec)
+{
+ struct inode_backref *backref;
+ struct inode_backref *tmp;
+ struct btrfs_key key;
+ struct btrfs_inode_item *inode_item;
+ int ret = 0;
+
+ /* Remove all backref including the valid ones */
+ list_for_each_entry_safe(backref, tmp, &rec->backrefs, list) {
+ ret = btrfs_unlink(trans, root, rec->ino, backref->dir,
+ backref->index, backref->name,
+ backref->namelen, 0);
+ if (ret < 0)
+ goto out;
+
+ /* remove invalid backref, so it won't be added back */
+ if (!(backref->found_dir_index &&
+ backref->found_dir_item &&
+ backref->found_inode_ref)) {
+ list_del(&backref->list);
+ free(backref);
+ }
+ }
+
+ /* Set nlink to 0 */
+ key.objectid = rec->ino;
+ key.type = BTRFS_INODE_ITEM_KEY;
+ key.offset = 0;
+ ret = btrfs_search_slot(trans, root, &key, path, 0, 1);
+ if (ret < 0)
+ goto out;
+ if (ret > 0) {
+ ret = -ENOENT;
+ goto out;
+ }
+ inode_item = btrfs_item_ptr(path->nodes[0], path->slots[0],
+ struct btrfs_inode_item);
+ btrfs_set_inode_nlink(path->nodes[0], inode_item, 0);
+ btrfs_mark_buffer_dirty(path->nodes[0]);
+ btrfs_release_path(path);
+
+ /*
+ * Add back valid inode_ref/dir_item/dir_index,
+ * add_link() will handle the nlink inc, so new nlink must be correct
+ */
+ list_for_each_entry(backref, &rec->backrefs, list) {
+ ret = btrfs_add_link(trans, root, rec->ino, backref->dir,
+ backref->name, backref->namelen,
+ backref->ref_type, &backref->index, 1);
+ if (ret < 0)
+ goto out;
+ }
+out:
+ btrfs_release_path(path);
+ 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;
+ int type_recovered = 0;
+ int ret = 0;
+
+ /*
+ * Get file name and type first before these invalid inode ref
+ * are deleted by remove_all_invalid_backref()
+ */
+ name_recovered = !find_file_name(rec, namebuf, &namelen);
+ type_recovered = !find_file_type(rec, &type);
+
+ if (!name_recovered) {
+ printf("Can't get file name for inode %llu, using '%llu' as fallback\n",
+ rec->ino, rec->ino);
+ namelen = count_digits(rec->ino);
+ sprintf(namebuf, "%llu", rec->ino);
+ name_recovered = 1;
+ }
+ if (!type_recovered) {
+ printf("Can't get file type for inode %llu, using FILE as fallback\n",
+ rec->ino);
+ type = BTRFS_FT_REG_FILE;
+ type_recovered = 1;
+ }
+
+ ret = reset_nlink(trans, root, path, rec);
+ if (ret < 0) {
+ fprintf(stderr,
+ "Failed to reset nlink for inode %llu: %s\n",
+ rec->ino, strerror(-ret));
+ goto out;
+ }
+
+ if (rec->found_link == 0) {
+ lost_found_ino = root->highest_inode;
+ if (lost_found_ino >= BTRFS_LAST_FREE_OBJECTID) {
+ ret = -EOVERFLOW;
+ 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",
+ dir_name, strerror(-ret));
+ goto out;
+ }
+ ret = btrfs_add_link(trans, root, rec->ino, lost_found_ino,
+ namebuf, namelen, type, NULL, 1);
+ if (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);
+ }
+ if (ret < 0) {
+ fprintf(stderr,
+ "Failed to link the inode %llu to %s dir: %s",
+ 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);
+ }
+ rec->errors &= ~I_ERR_LINK_COUNT_WRONG;
+ printf("Fixed the nlink of inode %llu\n", rec->ino);
+out:
+ btrfs_release_path(path);
+ return ret;
+}
+
static int try_repair_inode(struct btrfs_root *root, struct inode_record *rec)
{
struct btrfs_trans_handle *trans;
struct btrfs_path *path;
int ret = 0;
- if (!(rec->errors & (I_ERR_DIR_ISIZE_WRONG | I_ERR_NO_ORPHAN_ITEM)))
+ if (!(rec->errors & (I_ERR_DIR_ISIZE_WRONG |
+ I_ERR_NO_ORPHAN_ITEM |
+ I_ERR_LINK_COUNT_WRONG)))
return rec->errors;
path = btrfs_alloc_path();
if (!path)
return -ENOMEM;
- trans = btrfs_start_transaction(root, 1);
+ /*
+ * For nlink repair, it may create a dir and add link, so
+ * 2 for parent(256)'s dir_index and dir_item
+ * 2 for lost+found dir's inode_item and inode_ref
+ * 1 for the new inode_ref of the file
+ * 2 for lost+found dir's dir_index and dir_item for the file
+ */
+ trans = btrfs_start_transaction(root, 7);
if (IS_ERR(trans)) {
btrfs_free_path(path);
return PTR_ERR(trans);
@@ -1938,6 +2121,8 @@ static int try_repair_inode(struct btrfs_root *root, struct inode_record *rec)
ret = repair_inode_isize(trans, root, path, rec);
if (!ret && rec->errors & I_ERR_NO_ORPHAN_ITEM)
ret = repair_inode_orphan_item(trans, root, path, rec);
+ if (!ret && rec->errors & I_ERR_LINK_COUNT_WRONG)
+ ret = repair_inode_nlinks(trans, root, path, rec);
btrfs_commit_transaction(trans, root);
btrfs_free_path(path);
return ret;
@@ -2088,6 +2273,8 @@ static int check_inode_recs(struct btrfs_root *root,
}
}
+ if (rec->found_link != rec->nlink)
+ rec->errors |= I_ERR_LINK_COUNT_WRONG;
if (repair) {
ret = try_repair_inode(root, rec);
if (ret == 0 && can_free_inode_rec(rec)) {
@@ -2100,8 +2287,6 @@ static int check_inode_recs(struct btrfs_root *root,
error++;
if (!rec->found_inode_item)
rec->errors |= I_ERR_NO_INODE_ITEM;
- if (rec->found_link != rec->nlink)
- rec->errors |= I_ERR_LINK_COUNT_WRONG;
print_inode_error(root, rec);
list_for_each_entry(backref, &rec->backrefs, list) {
if (!backref->found_dir_item)