bcachefs: Dirent repair code
authorKent Overstreet <kent.overstreet@gmail.com>
Tue, 21 Aug 2018 23:42:00 +0000 (19:42 -0400)
committerKent Overstreet <kent.overstreet@linux.dev>
Sun, 22 Oct 2023 21:08:09 +0000 (17:08 -0400)
There was a bug for awhile in previous kernels where we weren't
computing dirent name lengths correctly and we weren't zeroing out
padding at the end of dirents (due to struct bch_dirent changing size by
adding __attribute__((aligned)), and not updating other code to use
offsetof).

This patch fixes dirents with junk at the end, by going off of the
dirent's hash.

Signed-off-by: Kent Overstreet <kent.overstreet@linux.dev>
fs/bcachefs/dirent.c
fs/bcachefs/dirent.h
fs/bcachefs/fsck.c

index d5e174e1e59f4fc8bd98e645f928030667240bce..0651f5575131ada73352fd26e6e77ed0bebb5046 100644 (file)
@@ -16,16 +16,7 @@ unsigned bch2_dirent_name_bytes(struct bkey_s_c_dirent d)
        unsigned len = bkey_val_bytes(d.k) -
                offsetof(struct bch_dirent, d_name);
 
-       while (len && !d.v->d_name[len - 1])
-               --len;
-
-       return len;
-}
-
-static unsigned dirent_val_u64s(unsigned len)
-{
-       return DIV_ROUND_UP(offsetof(struct bch_dirent, d_name) + len,
-                           sizeof(u64));
+       return strnlen(d.v->d_name, len);
 }
 
 static u64 bch2_dirent_hash(const struct bch_hash_info *info,
@@ -108,9 +99,6 @@ const char *bch2_dirent_invalid(const struct bch_fs *c, struct bkey_s_c k)
                if (len > BCH_NAME_MAX)
                        return "dirent name too big";
 
-               if (memchr(d.v->d_name, '/', len))
-                       return "dirent name has invalid characters";
-
                return NULL;
        case BCH_DIRENT_WHITEOUT:
                return bkey_val_bytes(k.k) != 0
index ac28f83d6b2daa2c878c43012d1db62474d72403..30d2143d4ca7c71f7e7b397076932d9266b38db2 100644 (file)
@@ -23,6 +23,12 @@ struct bch_inode_info;
 
 unsigned bch2_dirent_name_bytes(struct bkey_s_c_dirent);
 
+static inline unsigned dirent_val_u64s(unsigned len)
+{
+       return DIV_ROUND_UP(offsetof(struct bch_dirent, d_name) + len,
+                           sizeof(u64));
+}
+
 int __bch2_dirent_create(struct btree_trans *, u64,
                         const struct bch_hash_info *, u8,
                         const struct qstr *, u64, int);
index 2430833dbce8a9c2bc1e9557b903f32b6d49c1f3..1bdb31c5d5def2df320f94a08d4b4d015a720139 100644 (file)
@@ -248,6 +248,29 @@ fsck_err:
        return ret;
 }
 
+static bool key_has_correct_hash(const struct bch_hash_desc desc,
+                                struct hash_check *h, struct bch_fs *c,
+                                struct btree_iter *k_iter, struct bkey_s_c k)
+{
+       u64 hash;
+
+       if (k.k->type != desc.whiteout_type &&
+           k.k->type != desc.key_type)
+               return true;
+
+       if (k.k->p.offset != h->next)
+               bch2_btree_iter_copy(h->chain, k_iter);
+       h->next = k.k->p.offset + 1;
+
+       if (k.k->type != desc.key_type)
+               return true;
+
+       hash = desc.hash_bkey(&h->info, k);
+
+       return hash >= h->chain->pos.offset &&
+               hash <= k.k->p.offset;
+}
+
 static int hash_check_key(const struct bch_hash_desc desc,
                          struct hash_check *h, struct bch_fs *c,
                          struct btree_iter *k_iter, struct bkey_s_c k)
@@ -271,9 +294,10 @@ static int hash_check_key(const struct bch_hash_desc desc,
 
        if (fsck_err_on(hashed < h->chain->pos.offset ||
                        hashed > k.k->p.offset, c,
-                       "hash table key at wrong offset: %llu, "
+                       "hash table key at wrong offset: btree %u, %llu, "
                        "hashed to %llu chain starts at %llu\n%s",
-                       k.k->p.offset, hashed, h->chain->pos.offset,
+                       desc.btree_id, k.k->p.offset,
+                       hashed, h->chain->pos.offset,
                        (bch2_bkey_val_to_text(c, bkey_type(0, desc.btree_id),
                                               buf, sizeof(buf), k), buf))) {
                ret = hash_redo_key(desc, h, c, k_iter, k, hashed);
@@ -289,6 +313,90 @@ fsck_err:
        return ret;
 }
 
+static int check_dirent_hash(struct hash_check *h, struct bch_fs *c,
+                            struct btree_iter *iter, struct bkey_s_c *k)
+{
+       struct bkey_i_dirent *d = NULL;
+       int ret = -EINVAL;
+       char buf[200];
+       unsigned len;
+       u64 hash;
+
+       if (key_has_correct_hash(bch2_dirent_hash_desc, h, c, iter, *k))
+               return 0;
+
+       len = bch2_dirent_name_bytes(bkey_s_c_to_dirent(*k));
+       BUG_ON(!len);
+
+       memcpy(buf, bkey_s_c_to_dirent(*k).v->d_name, len);
+       buf[len] = '\0';
+
+       d = kmalloc(bkey_bytes(k->k), GFP_KERNEL);
+       if (!d) {
+               bch_err(c, "memory allocation failure");
+               return -ENOMEM;
+       }
+
+       bkey_reassemble(&d->k_i, *k);
+
+       do {
+               --len;
+               if (!len)
+                       goto err_redo;
+
+               d->k.u64s = BKEY_U64s + dirent_val_u64s(len);
+
+               BUG_ON(bkey_val_bytes(&d->k) <
+                      offsetof(struct bch_dirent, d_name) + len);
+
+               memset(d->v.d_name + len, 0,
+                      bkey_val_bytes(&d->k) -
+                      offsetof(struct bch_dirent, d_name) - len);
+
+               hash = bch2_dirent_hash_desc.hash_bkey(&h->info,
+                                               bkey_i_to_s_c(&d->k_i));
+       } while (hash < h->chain->pos.offset ||
+                hash > k->k->p.offset);
+
+       if (fsck_err(c, "dirent with junk at end, was %s (%zu) now %s (%u)",
+                    buf, strlen(buf), d->v.d_name, len)) {
+               ret = bch2_btree_insert_at(c, NULL, NULL,
+                                          BTREE_INSERT_NOFAIL,
+                                          BTREE_INSERT_ENTRY(iter, &d->k_i));
+               if (ret)
+                       goto err;
+
+               *k = bch2_btree_iter_peek(iter);
+
+               BUG_ON(k->k->type != BCH_DIRENT);
+       }
+err:
+fsck_err:
+       kfree(d);
+       return ret;
+err_redo:
+       bch_err(c, "cannot fix dirent by removing trailing garbage %s (%zu)",
+               buf, strlen(buf));
+
+       hash = bch2_dirent_hash_desc.hash_bkey(&h->info, *k);
+
+       if (fsck_err(c, "hash table key at wrong offset: btree %u, offset %llu, "
+                       "hashed to %llu chain starts at %llu\n%s",
+                       BTREE_ID_DIRENTS,
+                       k->k->p.offset, hash, h->chain->pos.offset,
+                       (bch2_bkey_val_to_text(c, bkey_type(0, BTREE_ID_DIRENTS),
+                                              buf, sizeof(buf), *k), buf))) {
+               ret = hash_redo_key(bch2_dirent_hash_desc,
+                                   h, c, iter, *k, hash);
+               if (ret)
+                       bch_err(c, "hash_redo_key err %i", ret);
+               else
+                       ret = 1;
+       }
+
+       goto err;
+}
+
 static int bch2_inode_truncate(struct bch_fs *c, u64 inode_nr, u64 new_size)
 {
        return bch2_btree_delete_range(c, BTREE_ID_EXTENTS,
@@ -435,11 +543,13 @@ static int check_dirents(struct bch_fs *c)
                if (w.first_this_inode && w.have_inode)
                        hash_check_set_inode(&h, c, &w.inode);
 
-               ret = hash_check_key(bch2_dirent_hash_desc, &h, c, iter, k);
+               ret = check_dirent_hash(&h, c, iter, &k);
                if (ret > 0) {
                        ret = 0;
                        continue;
                }
+               if (ret)
+                       goto fsck_err;
 
                if (ret)
                        goto fsck_err;
@@ -458,7 +568,12 @@ static int check_dirents(struct bch_fs *c)
                                ". dirent") ||
                    fsck_err_on(name_len == 2 &&
                                !memcmp(d.v->d_name, "..", 2), c,
-                               ".. dirent")) {
+                               ".. dirent") ||
+                   fsck_err_on(name_len == 2 &&
+                               !memcmp(d.v->d_name, "..", 2), c,
+                               ".. dirent") ||
+                   fsck_err_on(memchr(d.v->d_name, '/', name_len), c,
+                               "dirent name has invalid chars")) {
                        ret = remove_dirent(c, iter, d);
                        if (ret)
                                goto err;