]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
bcachefs: snapshot delete progress indicator
authorKent Overstreet <kent.overstreet@linux.dev>
Thu, 1 May 2025 18:47:39 +0000 (14:47 -0400)
committerKent Overstreet <kent.overstreet@linux.dev>
Thu, 22 May 2025 00:14:40 +0000 (20:14 -0400)
Signed-off-by: Kent Overstreet <kent.overstreet@linux.dev>
fs/bcachefs/bcachefs.h
fs/bcachefs/snapshot.c
fs/bcachefs/snapshot.h
fs/bcachefs/snapshot_types.h [new file with mode: 0644]
fs/bcachefs/subvolume.c
fs/bcachefs/subvolume.h
fs/bcachefs/super.c
fs/bcachefs/sysfs.c

index 0369dd656d324b4bebc34f9bbb659b0ffcf2d7e8..cd35d1cf3fbbc54644a6fa59a5f73e28ea75b690 100644 (file)
 #include "recovery_passes_types.h"
 #include "sb-errors_types.h"
 #include "seqmutex.h"
+#include "snapshot_types.h"
 #include "time_stats.h"
 #include "util.h"
 
@@ -869,7 +870,7 @@ struct bch_fs {
        struct mutex            snapshot_table_lock;
        struct rw_semaphore     snapshot_create_lock;
 
-       struct work_struct      snapshot_delete_work;
+       struct snapshot_delete  snapshot_delete;
        struct work_struct      snapshot_wait_for_pagecache_and_delete_work;
        snapshot_id_list        snapshots_unlinked;
        struct mutex            snapshots_unlinked_lock;
index 94cf60f76b64260434b35983c1497e768eb51653..aaf64271c041e3af7526e42d1a0352993b4736a0 100644 (file)
@@ -1,6 +1,7 @@
 // SPDX-License-Identifier: GPL-2.0
 
 #include "bcachefs.h"
+#include "bbpos.h"
 #include "bkey_buf.h"
 #include "btree_cache.h"
 #include "btree_key_cache.h"
@@ -1346,12 +1347,6 @@ int bch2_snapshot_node_create(struct btree_trans *trans, u32 parent,
  * that key to snapshot leaf nodes, where we can mutate it
  */
 
-struct snapshot_interior_delete {
-       u32     id;
-       u32     live_child;
-};
-typedef DARRAY(struct snapshot_interior_delete) interior_delete_list;
-
 static inline u32 interior_delete_has_id(interior_delete_list *l, u32 id)
 {
        darray_for_each(*l, i)
@@ -1385,28 +1380,28 @@ static unsigned __live_child(struct snapshot_table *t, u32 id,
        return 0;
 }
 
-static unsigned live_child(struct bch_fs *c, u32 id,
-                          snapshot_id_list *delete_leaves,
-                          interior_delete_list *delete_interior)
+static unsigned live_child(struct bch_fs *c, u32 id)
 {
+       struct snapshot_delete *d = &c->snapshot_delete;
+
        rcu_read_lock();
        u32 ret = __live_child(rcu_dereference(c->snapshots), id,
-                              delete_leaves, delete_interior);
+                              &d->delete_leaves, &d->delete_interior);
        rcu_read_unlock();
        return ret;
 }
 
 static int delete_dead_snapshots_process_key(struct btree_trans *trans,
                                             struct btree_iter *iter,
-                                            struct bkey_s_c k,
-                                            snapshot_id_list *delete_leaves,
-                                            interior_delete_list *delete_interior)
+                                            struct bkey_s_c k)
 {
-       if (snapshot_list_has_id(delete_leaves, k.k->p.snapshot))
+       struct snapshot_delete *d = &trans->c->snapshot_delete;
+
+       if (snapshot_list_has_id(&d->delete_leaves, k.k->p.snapshot))
                return bch2_btree_delete_at(trans, iter,
                                            BTREE_UPDATE_internal_snapshot_node);
 
-       u32 live_child = interior_delete_has_id(delete_interior, k.k->p.snapshot);
+       u32 live_child = interior_delete_has_id(&d->delete_interior, k.k->p.snapshot);
        if (live_child) {
                struct bkey_i *new = bch2_bkey_make_mut_noupdate(trans, k);
                int ret = PTR_ERR_OR_ZERO(new);
@@ -1442,44 +1437,46 @@ static int delete_dead_snapshots_process_key(struct btree_trans *trans,
  * it doesn't have child snapshot nodes - it's now redundant and we can mark it
  * as deleted.
  */
-static int check_should_delete_snapshot(struct btree_trans *trans, struct bkey_s_c k,
-                                       snapshot_id_list *delete_leaves,
-                                       interior_delete_list *delete_interior)
+static int check_should_delete_snapshot(struct btree_trans *trans, struct bkey_s_c k)
 {
        if (k.k->type != KEY_TYPE_snapshot)
                return 0;
 
        struct bch_fs *c = trans->c;
+       struct snapshot_delete *d = &c->snapshot_delete;
        struct bkey_s_c_snapshot s = bkey_s_c_to_snapshot(k);
        unsigned live_children = 0;
+       int ret = 0;
 
        if (BCH_SNAPSHOT_SUBVOL(s.v))
                return 0;
 
+       mutex_lock(&d->progress_lock);
        for (unsigned i = 0; i < 2; i++) {
                u32 child = le32_to_cpu(s.v->children[i]);
 
                live_children += child &&
-                       !snapshot_list_has_id(delete_leaves, child);
+                       !snapshot_list_has_id(&d->delete_leaves, child);
        }
 
        if (live_children == 0) {
-               return snapshot_list_add(c, delete_leaves, s.k->p.offset);
+               ret = snapshot_list_add(c, &d->delete_leaves, s.k->p.offset);
        } else if (live_children == 1) {
-               struct snapshot_interior_delete d = {
+               struct snapshot_interior_delete n = {
                        .id             = s.k->p.offset,
-                       .live_child     = live_child(c, s.k->p.offset, delete_leaves, delete_interior),
+                       .live_child     = live_child(c, s.k->p.offset),
                };
 
-               if (!d.live_child) {
-                       bch_err(c, "error finding live child of snapshot %u", d.id);
-                       return -EINVAL;
+               if (!n.live_child) {
+                       bch_err(c, "error finding live child of snapshot %u", n.id);
+                       ret = -EINVAL;
+               } else {
+                       ret = darray_push(&d->delete_interior, n);
                }
-
-               return darray_push(delete_interior, d);
-       } else {
-               return 0;
        }
+       mutex_unlock(&d->progress_lock);
+
+       return ret;
 }
 
 static inline u32 bch2_snapshot_nth_parent_skip(struct bch_fs *c, u32 id, u32 n,
@@ -1555,39 +1552,48 @@ static int bch2_fix_child_of_deleted_snapshot(struct btree_trans *trans,
        return bch2_trans_update(trans, iter, &s->k_i, 0);
 }
 
+static void bch2_snapshot_delete_nodes_to_text(struct printbuf *out, struct snapshot_delete *d)
+{
+       prt_printf(out, "deleting leaves");
+       darray_for_each(d->delete_leaves, i)
+               prt_printf(out, " %u", *i);
+       prt_newline(out);
+
+       prt_printf(out, "interior");
+       darray_for_each(d->delete_interior, i)
+               prt_printf(out, " %u->%u", i->id, i->live_child);
+       prt_newline(out);
+}
+
 int bch2_delete_dead_snapshots(struct bch_fs *c)
 {
        if (!test_and_clear_bit(BCH_FS_need_delete_dead_snapshots, &c->flags))
                return 0;
 
        struct btree_trans *trans = bch2_trans_get(c);
-       snapshot_id_list delete_leaves = {};
-       interior_delete_list delete_interior = {};
+       struct snapshot_delete *d = &c->snapshot_delete;
        int ret = 0;
 
        /*
         * For every snapshot node: If we have no live children and it's not
         * pointed to by a subvolume, delete it:
         */
+       d->running = true;
+       d->pos = BBPOS_MIN;
+
        ret = for_each_btree_key(trans, iter, BTREE_ID_snapshots, POS_MIN, 0, k,
-               check_should_delete_snapshot(trans, k, &delete_leaves, &delete_interior));
+               check_should_delete_snapshot(trans, k));
        if (!bch2_err_matches(ret, EROFS))
                bch_err_msg(c, ret, "walking snapshots");
        if (ret)
                goto err;
 
-       if (!delete_leaves.nr && !delete_interior.nr)
+       if (!d->delete_leaves.nr && !d->delete_interior.nr)
                goto err;
 
        {
                struct printbuf buf = PRINTBUF;
-               prt_printf(&buf, "deleting leaves");
-               darray_for_each(delete_leaves, i)
-                       prt_printf(&buf, " %u", *i);
-
-               prt_printf(&buf, " interior");
-               darray_for_each(delete_interior, i)
-                       prt_printf(&buf, " %u->%u", i->id, i->live_child);
+               bch2_snapshot_delete_nodes_to_text(&buf, d);
 
                ret = commit_do(trans, NULL, NULL, 0, bch2_trans_log_msg(trans, &buf));
                printbuf_exit(&buf);
@@ -1595,19 +1601,21 @@ int bch2_delete_dead_snapshots(struct bch_fs *c)
                        goto err;
        }
 
-       for (unsigned btree = 0; btree < BTREE_ID_NR; btree++) {
+       for (d->pos.btree = 0; d->pos.btree < BTREE_ID_NR; d->pos.btree++) {
                struct disk_reservation res = { 0 };
 
-               if (!btree_type_has_snapshots(btree))
+               d->pos.pos = POS_MIN;
+
+               if (!btree_type_has_snapshots(d->pos.btree))
                        continue;
 
                ret = for_each_btree_key_commit(trans, iter,
-                               btree, POS_MIN,
+                               d->pos.btree, POS_MIN,
                                BTREE_ITER_prefetch|BTREE_ITER_all_snapshots, k,
-                               &res, NULL, BCH_TRANS_COMMIT_no_enospc,
-                       delete_dead_snapshots_process_key(trans, &iter, k,
-                                                         &delete_leaves,
-                                                         &delete_interior));
+                               &res, NULL, BCH_TRANS_COMMIT_no_enospc, ({
+                       d->pos.pos = iter.pos;
+                       delete_dead_snapshots_process_key(trans, &iter, k);
+               }));
 
                bch2_disk_reservation_put(c, &res);
 
@@ -1617,7 +1625,7 @@ int bch2_delete_dead_snapshots(struct bch_fs *c)
                        goto err;
        }
 
-       darray_for_each(delete_leaves, i) {
+       darray_for_each(d->delete_leaves, i) {
                ret = commit_do(trans, NULL, NULL, 0,
                        bch2_snapshot_node_delete(trans, *i));
                if (!bch2_err_matches(ret, EROFS))
@@ -1634,11 +1642,11 @@ int bch2_delete_dead_snapshots(struct bch_fs *c)
        ret = for_each_btree_key_commit(trans, iter, BTREE_ID_snapshots, POS_MIN,
                                  BTREE_ITER_intent, k,
                                  NULL, NULL, BCH_TRANS_COMMIT_no_enospc,
-               bch2_fix_child_of_deleted_snapshot(trans, &iter, k, &delete_interior));
+               bch2_fix_child_of_deleted_snapshot(trans, &iter, k, &d->delete_interior));
        if (ret)
                goto err;
 
-       darray_for_each(delete_interior, i) {
+       darray_for_each(d->delete_interior, i) {
                ret = commit_do(trans, NULL, NULL, 0,
                        bch2_snapshot_node_delete(trans, i->id));
                if (!bch2_err_matches(ret, EROFS))
@@ -1647,8 +1655,11 @@ int bch2_delete_dead_snapshots(struct bch_fs *c)
                        goto err;
        }
 err:
-       darray_exit(&delete_interior);
-       darray_exit(&delete_leaves);
+       mutex_lock(&d->progress_lock);
+       darray_exit(&d->delete_interior);
+       darray_exit(&d->delete_leaves);
+       d->running = false;
+       mutex_unlock(&d->progress_lock);
        bch2_trans_put(trans);
        if (!bch2_err_matches(ret, EROFS))
                bch_err_fn(c, ret);
@@ -1657,7 +1668,7 @@ err:
 
 void bch2_delete_dead_snapshots_work(struct work_struct *work)
 {
-       struct bch_fs *c = container_of(work, struct bch_fs, snapshot_delete_work);
+       struct bch_fs *c = container_of(work, struct bch_fs, snapshot_delete.work);
 
        set_worker_desc("bcachefs-delete-dead-snapshots/%s", c->name);
 
@@ -1672,10 +1683,26 @@ void bch2_delete_dead_snapshots_async(struct bch_fs *c)
 
        BUG_ON(!test_bit(BCH_FS_may_go_rw, &c->flags));
 
-       if (!queue_work(c->write_ref_wq, &c->snapshot_delete_work))
+       if (!queue_work(c->write_ref_wq, &c->snapshot_delete.work))
                enumerated_ref_put(&c->writes, BCH_WRITE_REF_delete_dead_snapshots);
 }
 
+void bch2_snapshot_delete_status_to_text(struct printbuf *out, struct bch_fs *c)
+{
+       struct snapshot_delete *d = &c->snapshot_delete;
+
+       if (!d->running) {
+               prt_str(out, "(not running)");
+               return;
+       }
+
+       mutex_lock(&d->progress_lock);
+       bch2_snapshot_delete_nodes_to_text(out, d);
+
+       bch2_bbpos_to_text(out, d->pos);
+       mutex_unlock(&d->progress_lock);
+}
+
 int __bch2_key_has_snapshot_overwrites(struct btree_trans *trans,
                                       enum btree_id id,
                                       struct bpos pos)
@@ -1750,3 +1777,10 @@ void bch2_fs_snapshots_exit(struct bch_fs *c)
 {
        kvfree(rcu_dereference_protected(c->snapshots, true));
 }
+
+void bch2_fs_snapshots_init_early(struct bch_fs *c)
+{
+       INIT_WORK(&c->snapshot_delete.work, bch2_delete_dead_snapshots_work);
+       mutex_init(&c->snapshot_delete.progress_lock);
+       mutex_init(&c->snapshots_unlinked_lock);
+}
index 81180181d7c9c68bd4b4ca95840126b996c5a27b..24a451bb7024f53285683e425b7f1ec7367965ad 100644 (file)
@@ -244,7 +244,6 @@ int bch2_reconstruct_snapshots(struct bch_fs *);
 int bch2_check_key_has_snapshot(struct btree_trans *, struct btree_iter *, struct bkey_s_c);
 
 int bch2_snapshot_node_set_deleted(struct btree_trans *, u32);
-void bch2_delete_dead_snapshots_work(struct work_struct *);
 
 int __bch2_key_has_snapshot_overwrites(struct btree_trans *, enum btree_id, struct bpos);
 
@@ -259,7 +258,13 @@ static inline int bch2_key_has_snapshot_overwrites(struct btree_trans *trans,
        return __bch2_key_has_snapshot_overwrites(trans, id, pos);
 }
 
+int bch2_delete_dead_snapshots(struct bch_fs *);
+void bch2_delete_dead_snapshots_work(struct work_struct *);
+void bch2_delete_dead_snapshots_async(struct bch_fs *);
+void bch2_snapshot_delete_status_to_text(struct printbuf *, struct bch_fs *);
+
 int bch2_snapshots_read(struct bch_fs *);
 void bch2_fs_snapshots_exit(struct bch_fs *);
+void bch2_fs_snapshots_init_early(struct bch_fs *);
 
 #endif /* _BCACHEFS_SNAPSHOT_H */
diff --git a/fs/bcachefs/snapshot_types.h b/fs/bcachefs/snapshot_types.h
new file mode 100644 (file)
index 0000000..62def3d
--- /dev/null
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _BCACHEFS_SNAPSHOT_TYPES_H
+#define _BCACHEFS_SNAPSHOT_TYPES_H
+
+#include "bbpos_types.h"
+#include "subvolume_types.h"
+
+struct snapshot_interior_delete {
+       u32     id;
+       u32     live_child;
+};
+typedef DARRAY(struct snapshot_interior_delete) interior_delete_list;
+
+struct snapshot_delete {
+       struct work_struct      work;
+
+       struct mutex            progress_lock;
+       snapshot_id_list        delete_leaves;
+       interior_delete_list    delete_interior;
+
+       bool                    running;
+       struct bbpos            pos;
+};
+
+#endif /* _BCACHEFS_SNAPSHOT_TYPES_H */
index 51ab2ee10706e9dfcad0eba562defbf6f79e36d5..3c6ba1469de21bcb1e4ff1da6ea811ca2b89c54d 100644 (file)
@@ -730,8 +730,6 @@ int bch2_fs_upgrade_for_subvolumes(struct bch_fs *c)
 
 void bch2_fs_subvolumes_init_early(struct bch_fs *c)
 {
-       INIT_WORK(&c->snapshot_delete_work, bch2_delete_dead_snapshots_work);
        INIT_WORK(&c->snapshot_wait_for_pagecache_and_delete_work,
                  bch2_subvolume_wait_for_pagecache_and_delete);
-       mutex_init(&c->snapshots_unlinked_lock);
 }
index ee5e4e5a0fc8db4a0132803152b520d440dcdb96..075f55e25c7048ab7fa7af061f947c79f9a6cd2a 100644 (file)
@@ -77,9 +77,6 @@ bch2_btree_iter_peek_in_subvolume_max_type(struct btree_trans *trans, struct btr
                                        _end, _subvolid, _flags, _k, _do);      \
 })
 
-int bch2_delete_dead_snapshots(struct bch_fs *);
-void bch2_delete_dead_snapshots_async(struct bch_fs *);
-
 int bch2_subvolume_unlink(struct btree_trans *, u32);
 int bch2_subvolume_create(struct btree_trans *, u64, u32, u32, u32 *, u32 *, bool);
 
index 9381644cabeea4ec2bc5a72d20e8d78631ad3d8c..45e2b2bc8c655d5a74376a3dec41c814c390b9a1 100644 (file)
@@ -868,6 +868,7 @@ static struct bch_fs *bch2_fs_alloc(struct bch_sb *sb, struct bch_opts *opts,
        bch2_fs_quota_init(c);
        bch2_fs_rebalance_init(c);
        bch2_fs_sb_errors_init_early(c);
+       bch2_fs_snapshots_init_early(c);
        bch2_fs_subvolumes_init_early(c);
 
        INIT_LIST_HEAD(&c->list);
index 1d0c0f24a7b9ce7c361f834e0b7181397b823004..adf99a805a624bd7e6d6c448b542ca928908a15c 100644 (file)
@@ -198,6 +198,7 @@ read_attribute(copy_gc_wait);
 
 sysfs_pd_controller_attribute(rebalance);
 read_attribute(rebalance_status);
+read_attribute(snapshot_delete_status);
 
 read_attribute(new_stripes);
 
@@ -320,6 +321,9 @@ SHOW(bch2_fs)
        if (attr == &sysfs_rebalance_status)
                bch2_rebalance_status_to_text(out, c);
 
+       if (attr == &sysfs_snapshot_delete_status)
+               bch2_snapshot_delete_status_to_text(out, c);
+
        /* Debugging: */
 
        if (attr == &sysfs_journal_debug)
@@ -466,6 +470,7 @@ struct attribute *bch2_fs_files[] = {
        &sysfs_btree_write_stats,
 
        &sysfs_rebalance_status,
+       &sysfs_snapshot_delete_status,
 
        &sysfs_compression_stats,