#include "btree_key_cache.h"
#include "btree_update.h"
#include "error.h"
+#include "fs.h"
#include "subvolume.h"
/* Snapshot tree: */
return NULL;
}
-int bch2_mark_snapshot(struct bch_fs *c,
+int bch2_mark_snapshot(struct btree_trans *trans,
struct bkey_s_c old, struct bkey_s_c new,
- u64 journal_seq, unsigned flags)
+ unsigned flags)
{
+ struct bch_fs *c = trans->c;
struct snapshot_t *t;
t = genradix_ptr_alloc(&c->snapshots,
for_each_btree_key(trans, iter, BTREE_ID_snapshots,
POS_MIN, 0, k, ret) {
u32 id = k.k->p.offset, child[2];
- unsigned nr_live = 0, live_idx;
+ unsigned nr_live = 0, live_idx = 0;
if (k.k->type != KEY_TYPE_snapshot)
continue;
for (i = 0; i < 2; i++) {
ret = snapshot_live(trans, child[i]);
if (ret < 0)
- break;
+ goto err;
if (ret)
live_idx = i;
? snapshot_t(c, child[live_idx])->equiv
: id;
}
+err:
bch2_trans_iter_exit(trans, &iter);
if (ret)
if (BCH_SNAPSHOT_DELETED(bkey_s_c_to_snapshot(k).v))
have_deleted = true;
- ret = bch2_mark_snapshot(c, bkey_s_c_null, k, 0, 0);
+ ret = bch2_mark_snapshot(&trans, bkey_s_c_null, k, 0);
if (ret)
break;
}
return ret;
}
-static int bch2_snapshot_node_create(struct btree_trans *trans, u32 parent,
- u32 *new_snapids,
- u32 *snapshot_subvols,
- unsigned nr_snapids)
+int bch2_snapshot_node_create(struct btree_trans *trans, u32 parent,
+ u32 *new_snapids,
+ u32 *snapshot_subvols,
+ unsigned nr_snapids)
{
struct btree_iter iter;
struct bkey_i_snapshot *n;
n = bch2_trans_kmalloc(trans, sizeof(*n));
ret = PTR_ERR_OR_ZERO(n);
if (ret)
- return ret;
+ goto err;
bkey_snapshot_init(&n->k_i);
n->k.p = iter.pos;
n->v.pad = 0;
SET_BCH_SNAPSHOT_SUBVOL(&n->v, true);
- bch2_trans_update(trans, &iter, &n->k_i, 0);
-
- ret = bch2_mark_snapshot(trans->c, bkey_s_c_null, bkey_i_to_s_c(&n->k_i), 0, 0);
+ ret = bch2_trans_update(trans, &iter, &n->k_i, 0) ?:
+ bch2_mark_snapshot(trans, bkey_s_c_null, bkey_i_to_s_c(&n->k_i), 0);
if (ret)
- break;
+ goto err;
new_snapids[i] = iter.pos.offset;
}
n = bch2_trans_kmalloc(trans, sizeof(*n));
ret = PTR_ERR_OR_ZERO(n);
if (ret)
- return ret;
+ goto err;
bkey_reassemble(&n->k_i, k);
n->v.children[0] = cpu_to_le32(new_snapids[0]);
n->v.children[1] = cpu_to_le32(new_snapids[1]);
SET_BCH_SNAPSHOT_SUBVOL(&n->v, false);
- bch2_trans_update(trans, &iter, &n->k_i, 0);
+ ret = bch2_trans_update(trans, &iter, &n->k_i, 0);
+ if (ret)
+ goto err;
}
err:
bch2_trans_iter_exit(trans, &iter);
return ret;
}
-/* List of snapshot IDs that are being deleted: */
-struct snapshot_id_list {
- u32 nr;
- u32 size;
- u32 *d;
-};
-
-static bool snapshot_list_has_id(struct snapshot_id_list *s, u32 id)
-{
- unsigned i;
-
- for (i = 0; i < s->nr; i++)
- if (id == s->d[i])
- return true;
- return false;
-}
-
-static int snapshot_id_add(struct snapshot_id_list *s, u32 id)
+static int snapshot_id_add(snapshot_id_list *s, u32 id)
{
BUG_ON(snapshot_list_has_id(s, id));
- if (s->nr == s->size) {
- size_t new_size = max(8U, s->size * 2);
- void *n = krealloc(s->d,
- new_size * sizeof(s->d[0]),
- GFP_KERNEL);
- if (!n) {
- pr_err("error allocating snapshot ID list");
- return -ENOMEM;
- }
-
- s->d = n;
- s->size = new_size;
- };
-
- s->d[s->nr++] = id;
- return 0;
+ return darray_push(*s, id);
}
static int bch2_snapshot_delete_keys_btree(struct btree_trans *trans,
- struct snapshot_id_list *deleted,
+ snapshot_id_list *deleted,
enum btree_id btree_id)
{
struct bch_fs *c = trans->c;
struct btree_iter iter;
struct bkey_s_c k;
- struct snapshot_id_list equiv_seen = { 0 };
+ snapshot_id_list equiv_seen = { 0 };
struct bpos last_pos = POS_MIN;
int ret = 0;
}
bch2_trans_iter_exit(trans, &iter);
- kfree(equiv_seen.d);
+ darray_exit(equiv_seen);
return ret;
}
struct btree_iter iter;
struct bkey_s_c k;
struct bkey_s_c_snapshot snap;
- struct snapshot_id_list deleted = { 0 };
+ snapshot_id_list deleted = { 0 };
u32 i, id, children[2];
int ret = 0;
for (i = 0; i < deleted.nr; i++) {
ret = __bch2_trans_do(&trans, NULL, NULL, 0,
- bch2_snapshot_node_delete(&trans, deleted.d[i]));
+ bch2_snapshot_node_delete(&trans, deleted.data[i]));
if (ret) {
bch_err(c, "error deleting snapshot %u: %i",
- deleted.d[i], ret);
+ deleted.data[i], ret);
goto err;
}
}
err:
- kfree(deleted.d);
+ darray_exit(deleted);
bch2_trans_exit(&trans);
percpu_ref_put(&c->writes);
}
return ret;
}
+int bch2_snapshot_get_subvol(struct btree_trans *trans, u32 snapshot,
+ struct bch_subvolume *subvol)
+{
+ struct bch_snapshot snap;
+
+ return snapshot_lookup(trans, snapshot, &snap) ?:
+ bch2_subvolume_get(trans, le32_to_cpu(snap.subvol), true, 0, subvol);
+}
+
int bch2_subvolume_get_snapshot(struct btree_trans *trans, u32 subvol,
u32 *snapid)
{
return ret;
}
-/* XXX: mark snapshot id for deletion, walk btree and delete: */
-int bch2_subvolume_delete(struct btree_trans *trans, u32 subvolid,
- int deleting_snapshot)
+/*
+ * Delete subvolume, mark snapshot ID as deleted, queue up snapshot
+ * deletion/cleanup:
+ */
+int bch2_subvolume_delete(struct btree_trans *trans, u32 subvolid)
{
struct btree_iter iter;
struct bkey_s_c k;
subvol = bkey_s_c_to_subvolume(k);
snapid = le32_to_cpu(subvol.v->snapshot);
- if (deleting_snapshot >= 0 &&
- deleting_snapshot != BCH_SUBVOLUME_SNAP(subvol.v)) {
- ret = -ENOENT;
- goto err;
- }
-
delete = bch2_trans_kmalloc(trans, sizeof(*delete));
ret = PTR_ERR_OR_ZERO(delete);
if (ret)
return ret;
}
+void bch2_subvolume_wait_for_pagecache_and_delete(struct work_struct *work)
+{
+ struct bch_fs *c = container_of(work, struct bch_fs,
+ snapshot_wait_for_pagecache_and_delete_work);
+ snapshot_id_list s;
+ u32 *id;
+ int ret = 0;
+
+ while (!ret) {
+ mutex_lock(&c->snapshots_unlinked_lock);
+ s = c->snapshots_unlinked;
+ darray_init(c->snapshots_unlinked);
+ mutex_unlock(&c->snapshots_unlinked_lock);
+
+ if (!s.nr)
+ break;
+
+ bch2_evict_subvolume_inodes(c, &s);
+
+ for (id = s.data; id < s.data + s.nr; id++) {
+ ret = bch2_trans_do(c, NULL, NULL, BTREE_INSERT_NOFAIL,
+ bch2_subvolume_delete(&trans, *id));
+ if (ret) {
+ bch_err(c, "error %i deleting subvolume %u", ret, *id);
+ break;
+ }
+ }
+
+ darray_exit(s);
+ }
+
+ percpu_ref_put(&c->writes);
+}
+
+struct subvolume_unlink_hook {
+ struct btree_trans_commit_hook h;
+ u32 subvol;
+};
+
+int bch2_subvolume_wait_for_pagecache_and_delete_hook(struct btree_trans *trans,
+ struct btree_trans_commit_hook *_h)
+{
+ struct subvolume_unlink_hook *h = container_of(_h, struct subvolume_unlink_hook, h);
+ struct bch_fs *c = trans->c;
+ int ret = 0;
+
+ mutex_lock(&c->snapshots_unlinked_lock);
+ if (!snapshot_list_has_id(&c->snapshots_unlinked, h->subvol))
+ ret = snapshot_id_add(&c->snapshots_unlinked, h->subvol);
+ mutex_unlock(&c->snapshots_unlinked_lock);
+
+ if (ret)
+ return ret;
+
+ if (unlikely(!percpu_ref_tryget(&c->writes)))
+ return -EROFS;
+
+ if (!queue_work(system_long_wq, &c->snapshot_wait_for_pagecache_and_delete_work))
+ percpu_ref_put(&c->writes);
+ return 0;
+}
+
+int bch2_subvolume_unlink(struct btree_trans *trans, u32 subvolid)
+{
+ struct btree_iter iter;
+ struct bkey_s_c k;
+ struct bkey_i_subvolume *n;
+ struct subvolume_unlink_hook *h;
+ int ret = 0;
+
+ bch2_trans_iter_init(trans, &iter, BTREE_ID_subvolumes,
+ POS(0, subvolid),
+ BTREE_ITER_CACHED|
+ BTREE_ITER_INTENT);
+ k = bch2_btree_iter_peek_slot(&iter);
+ ret = bkey_err(k);
+ if (ret)
+ goto err;
+
+ if (k.k->type != KEY_TYPE_subvolume) {
+ bch2_fs_inconsistent(trans->c, "missing subvolume %u", subvolid);
+ ret = -EIO;
+ goto err;
+ }
+
+ n = bch2_trans_kmalloc(trans, sizeof(*n));
+ ret = PTR_ERR_OR_ZERO(n);
+ if (ret)
+ goto err;
+
+ bkey_reassemble(&n->k_i, k);
+ SET_BCH_SUBVOLUME_UNLINKED(&n->v, true);
+
+ ret = bch2_trans_update(trans, &iter, &n->k_i, 0);
+ if (ret)
+ goto err;
+
+ h = bch2_trans_kmalloc(trans, sizeof(*h));
+ ret = PTR_ERR_OR_ZERO(h);
+ if (ret)
+ goto err;
+
+ h->h.fn = bch2_subvolume_wait_for_pagecache_and_delete_hook;
+ h->subvol = subvolid;
+ bch2_trans_commit_hook(trans, &h->h);
+err:
+ bch2_trans_iter_exit(trans, &iter);
+ return ret;
+}
+
int bch2_subvolume_create(struct btree_trans *trans, u64 inode,
u32 src_subvolid,
u32 *new_subvolid,
u32 *new_snapshotid,
bool ro)
{
+ struct bch_fs *c = trans->c;
struct btree_iter dst_iter, src_iter = (struct btree_iter) { NULL };
struct bkey_i_subvolume *new_subvol = NULL;
struct bkey_i_subvolume *src_subvol = NULL;
BTREE_ITER_SLOTS|BTREE_ITER_INTENT, k, ret) {
if (bkey_cmp(k.k->p, SUBVOL_POS_MAX) > 0)
break;
- if (bkey_deleted(k.k))
+
+ /*
+ * bch2_subvolume_delete() doesn't flush the btree key cache -
+ * ideally it would but that's tricky
+ */
+ if (bkey_deleted(k.k) &&
+ !bch2_btree_key_cache_find(c, BTREE_ID_subvolumes, dst_iter.pos))
goto found_slot;
}
goto err;
if (k.k->type != KEY_TYPE_subvolume) {
- bch_err(trans->c, "subvolume %u not found", src_subvolid);
+ bch_err(c, "subvolume %u not found", src_subvolid);
ret = -ENOENT;
goto err;
}
if (src_subvolid) {
src_subvol->v.snapshot = cpu_to_le32(new_nodes[1]);
- bch2_trans_update(trans, &src_iter, &src_subvol->k_i, 0);
+ ret = bch2_trans_update(trans, &src_iter, &src_subvol->k_i, 0);
+ if (ret)
+ goto err;
}
new_subvol = bch2_trans_kmalloc(trans, sizeof(*new_subvol));
SET_BCH_SUBVOLUME_RO(&new_subvol->v, ro);
SET_BCH_SUBVOLUME_SNAP(&new_subvol->v, src_subvolid != 0);
new_subvol->k.p = dst_iter.pos;
- bch2_trans_update(trans, &dst_iter, &new_subvol->k_i, 0);
+ ret = bch2_trans_update(trans, &dst_iter, &new_subvol->k_i, 0);
+ if (ret)
+ goto err;
*new_subvolid = new_subvol->k.p.offset;
*new_snapshotid = new_nodes[0];
int bch2_fs_subvolumes_init(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);
return 0;
}