Commit 2bf380c0 authored by Kent Overstreet's avatar Kent Overstreet
Browse files

bcachefs: Fix dirent_casefold_mismatch repair



Instead of simply recreating a mis-casefolded dirent, use the str_hash
repair code, which will rename it if necessary - the dirent might have
been created again with the correct casefolding.

Factor out out bch2_str_hash_repair key() from
__bch2_str_hash_check_key() for the new path to use, and export
bch2_dirent_create_key() as well.

Signed-off-by: default avatarKent Overstreet <kent.overstreet@linux.dev>
parent b938d3c9
Loading
Loading
Loading
Loading
+7 −7
Original line number Diff line number Diff line
@@ -288,7 +288,7 @@ int bch2_dirent_init_name(struct bkey_i_dirent *dirent,
	return 0;
}

static struct bkey_i_dirent *dirent_create_key(struct btree_trans *trans,
struct bkey_i_dirent *bch2_dirent_create_key(struct btree_trans *trans,
				const struct bch_hash_info *hash_info,
				subvol_inum dir,
				u8 type,
@@ -332,7 +332,7 @@ int bch2_dirent_create_snapshot(struct btree_trans *trans,
	struct bkey_i_dirent *dirent;
	int ret;

	dirent = dirent_create_key(trans, hash_info, dir_inum, type, name, NULL, dst_inum);
	dirent = bch2_dirent_create_key(trans, hash_info, dir_inum, type, name, NULL, dst_inum);
	ret = PTR_ERR_OR_ZERO(dirent);
	if (ret)
		return ret;
@@ -356,7 +356,7 @@ int bch2_dirent_create(struct btree_trans *trans, subvol_inum dir,
	struct bkey_i_dirent *dirent;
	int ret;

	dirent = dirent_create_key(trans, hash_info, dir, type, name, NULL, dst_inum);
	dirent = bch2_dirent_create_key(trans, hash_info, dir, type, name, NULL, dst_inum);
	ret = PTR_ERR_OR_ZERO(dirent);
	if (ret)
		return ret;
@@ -461,7 +461,7 @@ int bch2_dirent_rename(struct btree_trans *trans,
		*src_offset = dst_iter.pos.offset;

	/* Create new dst key: */
	new_dst = dirent_create_key(trans, dst_hash, dst_dir, 0, dst_name,
	new_dst = bch2_dirent_create_key(trans, dst_hash, dst_dir, 0, dst_name,
					 dst_hash->cf_encoding ? &dst_name_lookup : NULL, 0);
	ret = PTR_ERR_OR_ZERO(new_dst);
	if (ret)
@@ -472,7 +472,7 @@ int bch2_dirent_rename(struct btree_trans *trans,

	/* Create new src key: */
	if (mode == BCH_RENAME_EXCHANGE) {
		new_src = dirent_create_key(trans, src_hash, src_dir, 0, src_name,
		new_src = bch2_dirent_create_key(trans, src_hash, src_dir, 0, src_name,
						 src_hash->cf_encoding ? &src_name_lookup : NULL, 0);
		ret = PTR_ERR_OR_ZERO(new_src);
		if (ret)
+3 −0
Original line number Diff line number Diff line
@@ -63,6 +63,9 @@ int bch2_dirent_init_name(struct bkey_i_dirent *,
			  const struct bch_hash_info *,
			  const struct qstr *,
			  const struct qstr *);
struct bkey_i_dirent *bch2_dirent_create_key(struct btree_trans *,
				const struct bch_hash_info *, subvol_inum, u8,
				const struct qstr *, const struct qstr *, u64);

int bch2_dirent_create_snapshot(struct btree_trans *, u32, u64, u32,
			const struct bch_hash_info *, u8,
+22 −20
Original line number Diff line number Diff line
@@ -2210,32 +2210,34 @@ static int check_dirent(struct btree_trans *trans, struct btree_iter *iter,
			(printbuf_reset(&buf),
			 bch2_bkey_val_to_text(&buf, c, k),
			 buf.buf))) {
		struct qstr name = bch2_dirent_get_name(d);
		u32 subvol = d.v->d_type == DT_SUBVOL
		subvol_inum dir_inum = { .subvol = d.v->d_type == DT_SUBVOL
				? le32_to_cpu(d.v->d_parent_subvol)
			: 0;
				: 0,
		};
		u64 target = d.v->d_type == DT_SUBVOL
			? le32_to_cpu(d.v->d_child_subvol)
			: le64_to_cpu(d.v->d_inum);
		u64 dir_offset;
		struct qstr name = bch2_dirent_get_name(d);

		struct bkey_i_dirent *new_d =
			bch2_dirent_create_key(trans, hash_info, dir_inum,
					       d.v->d_type, &name, NULL, target);
		ret = PTR_ERR_OR_ZERO(new_d);
		if (ret)
			goto out;

		new_d->k.p.inode	= d.k->p.inode;
		new_d->k.p.snapshot	= d.k->p.snapshot;

		struct btree_iter dup_iter = {};
		ret =	bch2_hash_delete_at(trans,
					    bch2_dirent_hash_desc, hash_info, iter,
					    BTREE_UPDATE_internal_snapshot_node) ?:
			bch2_dirent_create_snapshot(trans, subvol,
						    d.k->p.inode, d.k->p.snapshot,
						    hash_info,
						    d.v->d_type,
						    &name,
						    target,
						    &dir_offset,
						    BTREE_ITER_with_updates|
						    BTREE_UPDATE_internal_snapshot_node|
						    STR_HASH_must_create) ?:
			bch2_trans_commit(trans, NULL, NULL, BCH_TRANS_COMMIT_no_enospc);

		if (dir_offset < k.k->p.offset)
			*need_second_pass = true;
			bch2_str_hash_repair_key(trans, s,
						 &bch2_dirent_hash_desc, hash_info,
						 iter, bkey_i_to_s_c(&new_d->k_i),
						 &dup_iter, bkey_s_c_null,
						 need_second_pass);
		goto out;
	}

+122 −91
Original line number Diff line number Diff line
@@ -31,7 +31,7 @@ static int bch2_dirent_has_target(struct btree_trans *trans, struct bkey_s_c_dir
	}
}

static noinline int fsck_rename_dirent(struct btree_trans *trans,
static int bch2_fsck_rename_dirent(struct btree_trans *trans,
				   struct snapshots_seen *s,
				   const struct bch_hash_desc desc,
				   struct bch_hash_info *hash_info,
@@ -233,54 +233,20 @@ static noinline int check_inode_hash_info_matches_root(struct btree_trans *trans
	return ret;
}

int __bch2_str_hash_check_key(struct btree_trans *trans,
/* Put a str_hash key in its proper location, checking for duplicates */
int bch2_str_hash_repair_key(struct btree_trans *trans,
			     struct snapshots_seen *s,
			     const struct bch_hash_desc *desc,
			     struct bch_hash_info *hash_info,
			      struct btree_iter *k_iter, struct bkey_s_c hash_k,
			     struct btree_iter *k_iter, struct bkey_s_c k,
			     struct btree_iter *dup_iter, struct bkey_s_c dup_k,
			     bool *updated_before_k_pos)
{
	struct bch_fs *c = trans->c;
	struct btree_iter iter = {};
	struct printbuf buf = PRINTBUF;
	struct bkey_s_c k;
	bool free_snapshots_seen = false;
	int ret = 0;

	u64 hash = desc->hash_bkey(hash_info, hash_k);
	if (hash_k.k->p.offset < hash)
		goto bad_hash;

	for_each_btree_key_norestart(trans, iter, desc->btree_id,
				     SPOS(hash_k.k->p.inode, hash, hash_k.k->p.snapshot),
				     BTREE_ITER_slots|
				     BTREE_ITER_with_updates, k, ret) {
		if (bkey_eq(k.k->p, hash_k.k->p))
			break;

		if (k.k->type == desc->key_type &&
		    !desc->cmp_bkey(k, hash_k))
			goto duplicate_entries;

		if (bkey_deleted(k.k)) {
			bch2_trans_iter_exit(trans, &iter);
			goto bad_hash;
		}
	}
out:
	bch2_trans_iter_exit(trans, &iter);
	printbuf_exit(&buf);
	if (free_snapshots_seen)
		darray_exit(&s->ids);
	return ret;
bad_hash:
	/*
	 * Before doing any repair, check hash_info itself:
	 */
	ret = check_inode_hash_info_matches_root(trans, hash_k.k->p.inode, hash_info);
	if (ret)
		goto out;

	if (!s) {
		s = bch2_trans_kmalloc(trans, sizeof(*s));
		ret = PTR_ERR_OR_ZERO(s);
@@ -297,25 +263,22 @@ int __bch2_str_hash_check_key(struct btree_trans *trans,
		free_snapshots_seen = true;
	}

	if (fsck_err(trans, hash_table_key_wrong_offset,
		     "hash table key at wrong offset: btree %s inode %llu offset %llu, hashed to %llu\n%s",
		     bch2_btree_id_str(desc->btree_id), hash_k.k->p.inode, hash_k.k->p.offset, hash,
		     (printbuf_reset(&buf),
		      bch2_bkey_val_to_text(&buf, c, hash_k), buf.buf))) {
		struct bkey_i *new = bch2_bkey_make_mut_noupdate(trans, hash_k);
		if (IS_ERR(new))
			return PTR_ERR(new);

		k = bch2_hash_set_or_get_in_snapshot(trans, &iter, *desc, hash_info,
				       (subvol_inum) { 0, hash_k.k->p.inode },
				       hash_k.k->p.snapshot, new,
	if (!dup_k.k) {
		struct bkey_i *new = bch2_bkey_make_mut_noupdate(trans, k);
		ret = PTR_ERR_OR_ZERO(new);
		if (ret)
			goto out;

		dup_k = bch2_hash_set_or_get_in_snapshot(trans, dup_iter, *desc, hash_info,
				       (subvol_inum) { 0, new->k.p.inode },
				       new->k.p.snapshot, new,
				       STR_HASH_must_create|
				       BTREE_ITER_with_updates|
				       BTREE_UPDATE_internal_snapshot_node);
		ret = bkey_err(k);
		ret = bkey_err(dup_k);
		if (ret)
			goto out;
		if (k.k)
		if (dup_k.k)
			goto duplicate_entries;

		if (bpos_lt(new->k.p, k.k->p))
@@ -329,12 +292,9 @@ int __bch2_str_hash_check_key(struct btree_trans *trans,
			bch2_fsck_update_backpointers(trans, s, *desc, hash_info, new) ?:
			bch2_trans_commit(trans, NULL, NULL, BCH_TRANS_COMMIT_no_enospc) ?:
			-BCH_ERR_transaction_restart_commit;
		goto out;
	}
fsck_err:
	goto out;
	} else {
duplicate_entries:
	ret = hash_pick_winner(trans, *desc, hash_info, hash_k, k);
		ret = hash_pick_winner(trans, *desc, hash_info, k, dup_k);
		if (ret < 0)
			goto out;

@@ -342,9 +302,9 @@ int __bch2_str_hash_check_key(struct btree_trans *trans,
			      "duplicate hash table keys%s:\n%s",
			      ret != 2 ? "" : ", both point to valid inodes",
			      (printbuf_reset(&buf),
		       bch2_bkey_val_to_text(&buf, c, hash_k),
		       prt_newline(&buf),
			       bch2_bkey_val_to_text(&buf, c, k),
			       prt_newline(&buf),
			       bch2_bkey_val_to_text(&buf, c, dup_k),
			       buf.buf)))
			goto out;

@@ -353,16 +313,87 @@ int __bch2_str_hash_check_key(struct btree_trans *trans,
			ret = bch2_hash_delete_at(trans, *desc, hash_info, k_iter, 0);
			break;
		case 1:
		ret = bch2_hash_delete_at(trans, *desc, hash_info, &iter, 0);
			ret = bch2_hash_delete_at(trans, *desc, hash_info, dup_iter, 0);
			break;
		case 2:
		ret = fsck_rename_dirent(trans, s, *desc, hash_info, bkey_s_c_to_dirent(hash_k),
			ret = bch2_fsck_rename_dirent(trans, s, *desc, hash_info,
						      bkey_s_c_to_dirent(k),
						      updated_before_k_pos) ?:
			bch2_hash_delete_at(trans, *desc, hash_info, k_iter, 0);
				bch2_hash_delete_at(trans, *desc, hash_info, k_iter,
						    BTREE_ITER_with_updates);
			goto out;
		}

		ret = bch2_trans_commit(trans, NULL, NULL, 0) ?:
			-BCH_ERR_transaction_restart_commit;
	}
out:
fsck_err:
	bch2_trans_iter_exit(trans, dup_iter);
	printbuf_exit(&buf);
	if (free_snapshots_seen)
		darray_exit(&s->ids);
	return ret;
}

int __bch2_str_hash_check_key(struct btree_trans *trans,
			      struct snapshots_seen *s,
			      const struct bch_hash_desc *desc,
			      struct bch_hash_info *hash_info,
			      struct btree_iter *k_iter, struct bkey_s_c hash_k,
			      bool *updated_before_k_pos)
{
	struct bch_fs *c = trans->c;
	struct btree_iter iter = {};
	struct printbuf buf = PRINTBUF;
	struct bkey_s_c k;
	int ret = 0;

	u64 hash = desc->hash_bkey(hash_info, hash_k);
	if (hash_k.k->p.offset < hash)
		goto bad_hash;

	for_each_btree_key_norestart(trans, iter, desc->btree_id,
				     SPOS(hash_k.k->p.inode, hash, hash_k.k->p.snapshot),
				     BTREE_ITER_slots|
				     BTREE_ITER_with_updates, k, ret) {
		if (bkey_eq(k.k->p, hash_k.k->p))
			break;

		if (k.k->type == desc->key_type &&
		    !desc->cmp_bkey(k, hash_k)) {
			ret =	check_inode_hash_info_matches_root(trans, hash_k.k->p.inode,
								   hash_info) ?:
				bch2_str_hash_repair_key(trans, s, desc, hash_info,
							 k_iter, hash_k,
							 &iter, k, updated_before_k_pos);
			break;
		}

		if (bkey_deleted(k.k))
			goto bad_hash;
	}
	bch2_trans_iter_exit(trans, &iter);
out:
fsck_err:
	printbuf_exit(&buf);
	return ret;
bad_hash:
	bch2_trans_iter_exit(trans, &iter);
	/*
	 * Before doing any repair, check hash_info itself:
	 */
	ret = check_inode_hash_info_matches_root(trans, hash_k.k->p.inode, hash_info);
	if (ret)
		goto out;

	if (fsck_err(trans, hash_table_key_wrong_offset,
		     "hash table key at wrong offset: should be at %llu\n%s",
		     hash,
		     (bch2_bkey_val_to_text(&buf, c, hash_k), buf.buf)))
		ret = bch2_str_hash_repair_key(trans, s, desc, hash_info,
					       k_iter, hash_k,
					       &iter, bkey_s_c_null,
					       updated_before_k_pos);
	goto out;
}
+8 −0
Original line number Diff line number Diff line
@@ -398,6 +398,14 @@ int bch2_hash_delete(struct btree_trans *trans,
int bch2_repair_inode_hash_info(struct btree_trans *, struct bch_inode_unpacked *);

struct snapshots_seen;
int bch2_str_hash_repair_key(struct btree_trans *,
			     struct snapshots_seen *,
			     const struct bch_hash_desc *,
			     struct bch_hash_info *,
			     struct btree_iter *, struct bkey_s_c,
			     struct btree_iter *, struct bkey_s_c,
			     bool *);

int __bch2_str_hash_check_key(struct btree_trans *,
			      struct snapshots_seen *,
			      const struct bch_hash_desc *,