mirror of
https://github.com/koverstreet/bcachefs-tools.git
synced 2025-02-22 00:00:03 +03:00
Update bcachefs sources to a8115093df bcachefs: Fix divide by zero in rebalance_work()
This commit is contained in:
parent
4d04fe4262
commit
505c326cbe
@ -1 +1 @@
|
||||
b0788c47d97935856809bd1357423978dbfcdf9f
|
||||
a8115093df3e08c3e4bf6dab222e1cac132fff69
|
||||
|
4
Makefile
4
Makefile
@ -199,10 +199,6 @@ update-bcachefs-sources:
|
||||
git add include/linux/xxhash.h
|
||||
cp $(LINUX_DIR)/lib/xxhash.c linux/
|
||||
git add linux/xxhash.c
|
||||
cp $(LINUX_DIR)/kernel/locking/six.c linux/
|
||||
git add linux/six.c
|
||||
cp $(LINUX_DIR)/include/linux/six.h include/linux/
|
||||
git add include/linux/six.h
|
||||
cp $(LINUX_DIR)/include/linux/list_nulls.h include/linux/
|
||||
git add include/linux/list_nulls.h
|
||||
cp $(LINUX_DIR)/include/linux/poison.h include/linux/
|
||||
|
@ -107,7 +107,12 @@ extern __thread struct task_struct *current;
|
||||
#define set_current_state(state_value) \
|
||||
smp_store_mb(current->state, (state_value))
|
||||
|
||||
#define get_task_struct(tsk) do { atomic_inc(&(tsk)->usage); } while(0)
|
||||
static inline struct task_struct *get_task_struct(struct task_struct *task)
|
||||
{
|
||||
atomic_inc(&task->usage);
|
||||
return task;
|
||||
|
||||
}
|
||||
|
||||
extern void __put_task_struct(struct task_struct *t);
|
||||
|
||||
|
@ -989,7 +989,6 @@ retry_blocking:
|
||||
cl = _cl;
|
||||
goto retry_blocking;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return ret;
|
||||
@ -1031,6 +1030,16 @@ static int open_bucket_add_buckets(struct btree_trans *trans,
|
||||
return ret < 0 ? ret : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* should_drop_bucket - check if this is open_bucket should go away
|
||||
* @ca: if set, we're killing buckets for a particular device
|
||||
* @ec: if true, we're shutting down erasure coding and killing all ec
|
||||
* open_buckets
|
||||
* otherwise, return true
|
||||
*
|
||||
* We're killing open_buckets because we're shutting down a device, erasure
|
||||
* coding, or the entire filesystem - check if this open_bucket matches:
|
||||
*/
|
||||
static bool should_drop_bucket(struct open_bucket *ob, struct bch_fs *c,
|
||||
struct bch_dev *ca, bool ec)
|
||||
{
|
||||
@ -1516,25 +1525,47 @@ static const char * const bch2_write_point_states[] = {
|
||||
NULL
|
||||
};
|
||||
|
||||
static void bch2_write_point_to_text(struct printbuf *out, struct bch_fs *c,
|
||||
struct write_point *wp)
|
||||
{
|
||||
struct open_bucket *ob;
|
||||
unsigned i;
|
||||
|
||||
prt_printf(out, "%lu: ", wp->write_point);
|
||||
prt_human_readable_u64(out, wp->sectors_allocated);
|
||||
|
||||
prt_printf(out, " last wrote: ");
|
||||
bch2_pr_time_units(out, sched_clock() - wp->last_used);
|
||||
|
||||
for (i = 0; i < WRITE_POINT_STATE_NR; i++) {
|
||||
prt_printf(out, " %s: ", bch2_write_point_states[i]);
|
||||
bch2_pr_time_units(out, wp->time[i]);
|
||||
}
|
||||
|
||||
prt_newline(out);
|
||||
|
||||
printbuf_indent_add(out, 2);
|
||||
open_bucket_for_each(c, &wp->ptrs, ob, i)
|
||||
bch2_open_bucket_to_text(out, c, ob);
|
||||
printbuf_indent_sub(out, 2);
|
||||
}
|
||||
|
||||
void bch2_write_points_to_text(struct printbuf *out, struct bch_fs *c)
|
||||
{
|
||||
struct write_point *wp;
|
||||
unsigned i;
|
||||
|
||||
prt_str(out, "Foreground write points\n");
|
||||
for (wp = c->write_points;
|
||||
wp < c->write_points + ARRAY_SIZE(c->write_points);
|
||||
wp++) {
|
||||
prt_printf(out, "%lu: ", wp->write_point);
|
||||
prt_human_readable_u64(out, wp->sectors_allocated);
|
||||
wp++)
|
||||
bch2_write_point_to_text(out, c, wp);
|
||||
|
||||
prt_printf(out, " last wrote: ");
|
||||
bch2_pr_time_units(out, sched_clock() - wp->last_used);
|
||||
prt_str(out, "Copygc write point\n");
|
||||
bch2_write_point_to_text(out, c, &c->copygc_write_point);
|
||||
|
||||
for (i = 0; i < WRITE_POINT_STATE_NR; i++) {
|
||||
prt_printf(out, " %s: ", bch2_write_point_states[i]);
|
||||
bch2_pr_time_units(out, wp->time[i]);
|
||||
}
|
||||
prt_str(out, "Rebalance write point\n");
|
||||
bch2_write_point_to_text(out, c, &c->rebalance_write_point);
|
||||
|
||||
prt_newline(out);
|
||||
}
|
||||
prt_str(out, "Btree write point\n");
|
||||
bch2_write_point_to_text(out, c, &c->btree_write_point);
|
||||
}
|
||||
|
@ -916,9 +916,7 @@ struct bch_dirent {
|
||||
#define DT_SUBVOL 16
|
||||
#define BCH_DT_MAX 17
|
||||
|
||||
#define BCH_NAME_MAX ((unsigned) (U8_MAX * sizeof(__u64) - \
|
||||
sizeof(struct bkey) - \
|
||||
offsetof(struct bch_dirent, d_name)))
|
||||
#define BCH_NAME_MAX 512
|
||||
|
||||
/* Xattrs */
|
||||
|
||||
@ -1126,6 +1124,11 @@ struct bch_subvolume {
|
||||
__le32 flags;
|
||||
__le32 snapshot;
|
||||
__le64 inode;
|
||||
/*
|
||||
* Snapshot subvolumes form a tree, separate from the snapshot nodes
|
||||
* tree - if this subvolume is a snapshot, this is the ID of the
|
||||
* subvolume it was created from:
|
||||
*/
|
||||
__le32 parent;
|
||||
__le32 pad;
|
||||
bch_le128 otime;
|
||||
|
@ -591,8 +591,10 @@ struct bkey_format bch2_bkey_format_done(struct bkey_format_state *s)
|
||||
|
||||
/* allow for extent merging: */
|
||||
if (ret.bits_per_field[BKEY_FIELD_SIZE]) {
|
||||
ret.bits_per_field[BKEY_FIELD_SIZE] += 4;
|
||||
bits += 4;
|
||||
unsigned b = min(4U, 32U - ret.bits_per_field[BKEY_FIELD_SIZE]);
|
||||
|
||||
ret.bits_per_field[BKEY_FIELD_SIZE] += b;
|
||||
bits += b;
|
||||
}
|
||||
|
||||
ret.key_u64s = DIV_ROUND_UP(bits, 64);
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include "lru.h"
|
||||
#include "quota.h"
|
||||
#include "reflink.h"
|
||||
#include "snapshot.h"
|
||||
#include "subvolume.h"
|
||||
#include "xattr.h"
|
||||
|
||||
|
@ -14,7 +14,7 @@
|
||||
#include "extents.h"
|
||||
#include "journal.h"
|
||||
#include "replicas.h"
|
||||
#include "subvolume.h"
|
||||
#include "snapshot.h"
|
||||
#include "trace.h"
|
||||
|
||||
#include <linux/random.h>
|
||||
@ -2898,12 +2898,14 @@ static void bch2_trans_alloc_paths(struct btree_trans *trans, struct bch_fs *c)
|
||||
#ifdef __KERNEL__
|
||||
p = this_cpu_xchg(c->btree_paths_bufs->path, NULL);
|
||||
#endif
|
||||
if (!p)
|
||||
if (!p) {
|
||||
p = mempool_alloc(&trans->c->btree_paths_pool, GFP_NOFS);
|
||||
/*
|
||||
* paths need to be zeroed, bch2_check_for_deadlock looks at paths in
|
||||
* other threads
|
||||
*/
|
||||
/*
|
||||
* paths need to be zeroed, bch2_check_for_deadlock looks at
|
||||
* paths in other threads
|
||||
*/
|
||||
memset(p, 0, paths_bytes);
|
||||
}
|
||||
|
||||
trans->paths = p; p += paths_bytes;
|
||||
trans->updates = p; p += updates_bytes;
|
||||
|
@ -10,9 +10,8 @@
|
||||
* updating the iterator state
|
||||
*/
|
||||
|
||||
#include <linux/six.h>
|
||||
|
||||
#include "btree_iter.h"
|
||||
#include "six.h"
|
||||
|
||||
void bch2_btree_lock_init(struct btree_bkey_cached_common *, enum six_lock_init_flags);
|
||||
|
||||
|
@ -14,7 +14,7 @@
|
||||
#include "journal.h"
|
||||
#include "journal_reclaim.h"
|
||||
#include "replicas.h"
|
||||
#include "subvolume.h"
|
||||
#include "snapshot.h"
|
||||
|
||||
#include <linux/prefetch.h>
|
||||
|
||||
|
@ -4,7 +4,6 @@
|
||||
|
||||
#include <linux/list.h>
|
||||
#include <linux/rhashtable.h>
|
||||
#include <linux/six.h>
|
||||
|
||||
//#include "bkey_methods.h"
|
||||
#include "buckets_types.h"
|
||||
@ -12,6 +11,7 @@
|
||||
#include "errcode.h"
|
||||
#include "journal_types.h"
|
||||
#include "replicas_types.h"
|
||||
#include "six.h"
|
||||
|
||||
struct open_bucket;
|
||||
struct btree_update;
|
||||
|
@ -11,7 +11,7 @@
|
||||
#include "error.h"
|
||||
#include "extents.h"
|
||||
#include "keylist.h"
|
||||
#include "subvolume.h"
|
||||
#include "snapshot.h"
|
||||
#include "trace.h"
|
||||
|
||||
static inline int btree_insert_entry_cmp(const struct btree_insert_entry *l,
|
||||
|
@ -2385,7 +2385,7 @@ void bch2_btree_updates_to_text(struct printbuf *out, struct bch_fs *c)
|
||||
as,
|
||||
as->mode,
|
||||
as->nodes_written,
|
||||
atomic_read(&as->cl.remaining) & CLOSURE_REMAINING_MASK,
|
||||
closure_nr_remaining(&as->cl),
|
||||
as->journal.seq);
|
||||
mutex_unlock(&c->btree_interior_update_lock);
|
||||
}
|
||||
|
@ -415,7 +415,7 @@ void bch2_update_unwritten_extent(struct btree_trans *trans,
|
||||
break;
|
||||
}
|
||||
|
||||
if ((atomic_read(&cl.remaining) & CLOSURE_REMAINING_MASK) != 1) {
|
||||
if (closure_nr_remaining(&cl) != 1) {
|
||||
bch2_trans_unlock(trans);
|
||||
closure_sync(&cl);
|
||||
}
|
||||
|
@ -13,12 +13,25 @@
|
||||
|
||||
#include <linux/dcache.h>
|
||||
|
||||
unsigned bch2_dirent_name_bytes(struct bkey_s_c_dirent d)
|
||||
static unsigned bch2_dirent_name_bytes(struct bkey_s_c_dirent d)
|
||||
{
|
||||
unsigned len = bkey_val_bytes(d.k) -
|
||||
offsetof(struct bch_dirent, d_name);
|
||||
unsigned bkey_u64s = bkey_val_u64s(d.k);
|
||||
unsigned bkey_bytes = bkey_u64s * sizeof(u64);
|
||||
u64 last_u64 = ((u64*)d.v)[bkey_u64s - 1];
|
||||
#if CPU_BIG_ENDIAN
|
||||
unsigned trailing_nuls = last_u64 ? __builtin_ctzll(last_u64) / 8 : 64 / 8;
|
||||
#else
|
||||
unsigned trailing_nuls = last_u64 ? __builtin_clzll(last_u64) / 8 : 64 / 8;
|
||||
#endif
|
||||
|
||||
return strnlen(d.v->d_name, len);
|
||||
return bkey_bytes -
|
||||
offsetof(struct bch_dirent, d_name) -
|
||||
trailing_nuls;
|
||||
}
|
||||
|
||||
struct qstr bch2_dirent_get_name(struct bkey_s_c_dirent d)
|
||||
{
|
||||
return (struct qstr) QSTR_INIT(d.v->d_name, bch2_dirent_name_bytes(d));
|
||||
}
|
||||
|
||||
static u64 bch2_dirent_hash(const struct bch_hash_info *info,
|
||||
@ -41,7 +54,7 @@ static u64 dirent_hash_key(const struct bch_hash_info *info, const void *key)
|
||||
static u64 dirent_hash_bkey(const struct bch_hash_info *info, struct bkey_s_c k)
|
||||
{
|
||||
struct bkey_s_c_dirent d = bkey_s_c_to_dirent(k);
|
||||
struct qstr name = QSTR_INIT(d.v->d_name, bch2_dirent_name_bytes(d));
|
||||
struct qstr name = bch2_dirent_get_name(d);
|
||||
|
||||
return bch2_dirent_hash(info, &name);
|
||||
}
|
||||
@ -49,20 +62,20 @@ static u64 dirent_hash_bkey(const struct bch_hash_info *info, struct bkey_s_c k)
|
||||
static bool dirent_cmp_key(struct bkey_s_c _l, const void *_r)
|
||||
{
|
||||
struct bkey_s_c_dirent l = bkey_s_c_to_dirent(_l);
|
||||
int len = bch2_dirent_name_bytes(l);
|
||||
const struct qstr *r = _r;
|
||||
const struct qstr l_name = bch2_dirent_get_name(l);
|
||||
const struct qstr *r_name = _r;
|
||||
|
||||
return len - r->len ?: memcmp(l.v->d_name, r->name, len);
|
||||
return l_name.len - r_name->len ?: memcmp(l_name.name, r_name->name, l_name.len);
|
||||
}
|
||||
|
||||
static bool dirent_cmp_bkey(struct bkey_s_c _l, struct bkey_s_c _r)
|
||||
{
|
||||
struct bkey_s_c_dirent l = bkey_s_c_to_dirent(_l);
|
||||
struct bkey_s_c_dirent r = bkey_s_c_to_dirent(_r);
|
||||
int l_len = bch2_dirent_name_bytes(l);
|
||||
int r_len = bch2_dirent_name_bytes(r);
|
||||
const struct qstr l_name = bch2_dirent_get_name(l);
|
||||
const struct qstr r_name = bch2_dirent_get_name(r);
|
||||
|
||||
return l_len - r_len ?: memcmp(l.v->d_name, r.v->d_name, l_len);
|
||||
return l_name.len - r_name.len ?: memcmp(l_name.name, r_name.name, l_name.len);
|
||||
}
|
||||
|
||||
static bool dirent_is_visible(subvol_inum inum, struct bkey_s_c k)
|
||||
@ -89,37 +102,45 @@ int bch2_dirent_invalid(const struct bch_fs *c, struct bkey_s_c k,
|
||||
struct printbuf *err)
|
||||
{
|
||||
struct bkey_s_c_dirent d = bkey_s_c_to_dirent(k);
|
||||
unsigned len;
|
||||
struct qstr d_name = bch2_dirent_get_name(d);
|
||||
|
||||
len = bch2_dirent_name_bytes(d);
|
||||
if (!len) {
|
||||
if (!d_name.len) {
|
||||
prt_printf(err, "empty name");
|
||||
return -BCH_ERR_invalid_bkey;
|
||||
}
|
||||
|
||||
if (bkey_val_u64s(k.k) > dirent_val_u64s(len)) {
|
||||
if (bkey_val_u64s(k.k) > dirent_val_u64s(d_name.len)) {
|
||||
prt_printf(err, "value too big (%zu > %u)",
|
||||
bkey_val_u64s(k.k), dirent_val_u64s(len));
|
||||
bkey_val_u64s(k.k), dirent_val_u64s(d_name.len));
|
||||
return -BCH_ERR_invalid_bkey;
|
||||
}
|
||||
|
||||
if (len > BCH_NAME_MAX) {
|
||||
/*
|
||||
* Check new keys don't exceed the max length
|
||||
* (older keys may be larger.)
|
||||
*/
|
||||
if ((flags & BKEY_INVALID_COMMIT) && d_name.len > BCH_NAME_MAX) {
|
||||
prt_printf(err, "dirent name too big (%u > %u)",
|
||||
len, BCH_NAME_MAX);
|
||||
d_name.len, BCH_NAME_MAX);
|
||||
return -BCH_ERR_invalid_bkey;
|
||||
}
|
||||
|
||||
if (len == 1 && !memcmp(d.v->d_name, ".", 1)) {
|
||||
if (d_name.len != strnlen(d_name.name, d_name.len)) {
|
||||
prt_printf(err, "dirent has stray data after name's NUL");
|
||||
return -BCH_ERR_invalid_bkey;
|
||||
}
|
||||
|
||||
if (d_name.len == 1 && !memcmp(d_name.name, ".", 1)) {
|
||||
prt_printf(err, "invalid name");
|
||||
return -BCH_ERR_invalid_bkey;
|
||||
}
|
||||
|
||||
if (len == 2 && !memcmp(d.v->d_name, "..", 2)) {
|
||||
if (d_name.len == 2 && !memcmp(d_name.name, "..", 2)) {
|
||||
prt_printf(err, "invalid name");
|
||||
return -BCH_ERR_invalid_bkey;
|
||||
}
|
||||
|
||||
if (memchr(d.v->d_name, '/', len)) {
|
||||
if (memchr(d_name.name, '/', d_name.len)) {
|
||||
prt_printf(err, "invalid name");
|
||||
return -BCH_ERR_invalid_bkey;
|
||||
}
|
||||
@ -137,10 +158,11 @@ void bch2_dirent_to_text(struct printbuf *out, struct bch_fs *c,
|
||||
struct bkey_s_c k)
|
||||
{
|
||||
struct bkey_s_c_dirent d = bkey_s_c_to_dirent(k);
|
||||
struct qstr d_name = bch2_dirent_get_name(d);
|
||||
|
||||
prt_printf(out, "%.*s -> %llu type %s",
|
||||
bch2_dirent_name_bytes(d),
|
||||
d.v->d_name,
|
||||
d_name.len,
|
||||
d_name.name,
|
||||
d.v->d_type != DT_SUBVOL
|
||||
? le64_to_cpu(d.v->d_inum)
|
||||
: le32_to_cpu(d.v->d_child_subvol),
|
||||
@ -507,6 +529,7 @@ int bch2_readdir(struct bch_fs *c, subvol_inum inum, struct dir_context *ctx)
|
||||
subvol_inum target;
|
||||
u32 snapshot;
|
||||
struct bkey_buf sk;
|
||||
struct qstr name;
|
||||
int ret;
|
||||
|
||||
bch2_bkey_buf_init(&sk);
|
||||
@ -537,9 +560,11 @@ retry:
|
||||
dirent = bkey_i_to_s_c_dirent(sk.k);
|
||||
bch2_trans_unlock(&trans);
|
||||
|
||||
name = bch2_dirent_get_name(dirent);
|
||||
|
||||
ctx->pos = dirent.k->p.offset;
|
||||
if (!dir_emit(ctx, dirent.v->d_name,
|
||||
bch2_dirent_name_bytes(dirent),
|
||||
if (!dir_emit(ctx, name.name,
|
||||
name.len,
|
||||
target.inum,
|
||||
vfs_d_type(dirent.v->d_type)))
|
||||
break;
|
||||
|
@ -24,7 +24,7 @@ struct bch_fs;
|
||||
struct bch_hash_info;
|
||||
struct bch_inode_info;
|
||||
|
||||
unsigned bch2_dirent_name_bytes(struct bkey_s_c_dirent);
|
||||
struct qstr bch2_dirent_get_name(struct bkey_s_c_dirent d);
|
||||
|
||||
static inline unsigned dirent_val_u64s(unsigned len)
|
||||
{
|
||||
|
@ -1059,6 +1059,7 @@ void bch2_bkey_ptrs_to_text(struct printbuf *out, struct bch_fs *c,
|
||||
|
||||
static int extent_ptr_invalid(const struct bch_fs *c,
|
||||
struct bkey_s_c k,
|
||||
enum bkey_invalid_flags flags,
|
||||
const struct bch_extent_ptr *ptr,
|
||||
unsigned size_ondisk,
|
||||
bool metadata,
|
||||
@ -1071,6 +1072,14 @@ static int extent_ptr_invalid(const struct bch_fs *c,
|
||||
struct bch_dev *ca;
|
||||
|
||||
if (!bch2_dev_exists2(c, ptr->dev)) {
|
||||
/*
|
||||
* If we're in the write path this key might have already been
|
||||
* overwritten, and we could be seeing a device that doesn't
|
||||
* exist anymore due to racing with device removal:
|
||||
*/
|
||||
if (flags & BKEY_INVALID_WRITE)
|
||||
return 0;
|
||||
|
||||
prt_printf(err, "pointer to invalid device (%u)", ptr->dev);
|
||||
return -BCH_ERR_invalid_bkey;
|
||||
}
|
||||
@ -1136,8 +1145,8 @@ int bch2_bkey_ptrs_invalid(const struct bch_fs *c, struct bkey_s_c k,
|
||||
|
||||
switch (extent_entry_type(entry)) {
|
||||
case BCH_EXTENT_ENTRY_ptr:
|
||||
ret = extent_ptr_invalid(c, k, &entry->ptr, size_ondisk,
|
||||
false, err);
|
||||
ret = extent_ptr_invalid(c, k, flags, &entry->ptr,
|
||||
size_ondisk, false, err);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
|
@ -909,6 +909,7 @@ static int __bch2_buffered_write(struct bch_inode_info *inode,
|
||||
if (!folio_test_uptodate(f) &&
|
||||
f_copied != folio_size(f) &&
|
||||
pos + copied + f_copied < inode->v.i_size) {
|
||||
iov_iter_revert(iter, f_copied);
|
||||
folio_zero_range(f, 0, folio_size(f));
|
||||
folios_trunc(&folios, fi);
|
||||
break;
|
||||
|
@ -698,20 +698,26 @@ loff_t bch2_seek_pagecache_data(struct inode *vinode,
|
||||
return end_offset;
|
||||
}
|
||||
|
||||
/*
|
||||
* Search for a hole in a folio.
|
||||
*
|
||||
* The filemap layer returns -ENOENT if no folio exists, so reuse the same error
|
||||
* code to indicate a pagecache hole exists at the returned offset. Otherwise
|
||||
* return 0 if the folio is filled with data, or an error code. This function
|
||||
* can return -EAGAIN if nonblock is specified.
|
||||
*/
|
||||
static int folio_hole_offset(struct address_space *mapping, loff_t *offset,
|
||||
unsigned min_replicas, bool nonblock)
|
||||
{
|
||||
struct folio *folio;
|
||||
struct bch_folio *s;
|
||||
unsigned i, sectors;
|
||||
bool ret = true;
|
||||
int ret = -ENOENT;
|
||||
|
||||
folio = __filemap_get_folio(mapping, *offset >> PAGE_SHIFT,
|
||||
FGP_LOCK|(nonblock ? FGP_NOWAIT : 0), 0);
|
||||
if (folio == ERR_PTR(-EAGAIN))
|
||||
return -EAGAIN;
|
||||
if (IS_ERR_OR_NULL(folio))
|
||||
return true;
|
||||
if (IS_ERR(folio))
|
||||
return PTR_ERR(folio);
|
||||
|
||||
s = bch2_folio(folio);
|
||||
if (!s)
|
||||
@ -727,7 +733,7 @@ static int folio_hole_offset(struct address_space *mapping, loff_t *offset,
|
||||
}
|
||||
|
||||
*offset = folio_end_pos(folio);
|
||||
ret = false;
|
||||
ret = 0;
|
||||
unlock:
|
||||
folio_unlock(folio);
|
||||
folio_put(folio);
|
||||
@ -742,11 +748,13 @@ loff_t bch2_seek_pagecache_hole(struct inode *vinode,
|
||||
{
|
||||
struct address_space *mapping = vinode->i_mapping;
|
||||
loff_t offset = start_offset;
|
||||
loff_t ret = 0;
|
||||
|
||||
while (offset < end_offset &&
|
||||
!folio_hole_offset(mapping, &offset, min_replicas, nonblock))
|
||||
;
|
||||
while (!ret && offset < end_offset)
|
||||
ret = folio_hole_offset(mapping, &offset, min_replicas, nonblock);
|
||||
|
||||
if (ret && ret != -ENOENT)
|
||||
return ret;
|
||||
return min(offset, end_offset);
|
||||
}
|
||||
|
||||
|
@ -109,7 +109,8 @@ struct inode_new_size {
|
||||
unsigned fields;
|
||||
};
|
||||
|
||||
static int inode_set_size(struct bch_inode_info *inode,
|
||||
static int inode_set_size(struct btree_trans *trans,
|
||||
struct bch_inode_info *inode,
|
||||
struct bch_inode_unpacked *bi,
|
||||
void *p)
|
||||
{
|
||||
@ -390,7 +391,8 @@ static int bch2_extend(struct mnt_idmap *idmap,
|
||||
return bch2_setattr_nonsize(idmap, inode, iattr);
|
||||
}
|
||||
|
||||
static int bch2_truncate_finish_fn(struct bch_inode_info *inode,
|
||||
static int bch2_truncate_finish_fn(struct btree_trans *trans,
|
||||
struct bch_inode_info *inode,
|
||||
struct bch_inode_unpacked *bi,
|
||||
void *p)
|
||||
{
|
||||
@ -398,7 +400,8 @@ static int bch2_truncate_finish_fn(struct bch_inode_info *inode,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bch2_truncate_start_fn(struct bch_inode_info *inode,
|
||||
static int bch2_truncate_start_fn(struct btree_trans *trans,
|
||||
struct bch_inode_info *inode,
|
||||
struct bch_inode_unpacked *bi, void *p)
|
||||
{
|
||||
u64 *new_i_size = p;
|
||||
@ -519,7 +522,8 @@ err:
|
||||
|
||||
/* fallocate: */
|
||||
|
||||
static int inode_update_times_fn(struct bch_inode_info *inode,
|
||||
static int inode_update_times_fn(struct btree_trans *trans,
|
||||
struct bch_inode_info *inode,
|
||||
struct bch_inode_unpacked *bi, void *p)
|
||||
{
|
||||
struct bch_fs *c = inode->v.i_sb->s_fs_info;
|
||||
|
@ -108,15 +108,15 @@ static inline int bch2_quota_reservation_add(struct bch_fs *c,
|
||||
|
||||
#else
|
||||
|
||||
static void __bch2_quota_reservation_put(struct bch_fs *c,
|
||||
static inline void __bch2_quota_reservation_put(struct bch_fs *c,
|
||||
struct bch_inode_info *inode,
|
||||
struct quota_res *res) {}
|
||||
|
||||
static void bch2_quota_reservation_put(struct bch_fs *c,
|
||||
static inline void bch2_quota_reservation_put(struct bch_fs *c,
|
||||
struct bch_inode_info *inode,
|
||||
struct quota_res *res) {}
|
||||
|
||||
static int bch2_quota_reservation_add(struct bch_fs *c,
|
||||
static inline int bch2_quota_reservation_add(struct bch_fs *c,
|
||||
struct bch_inode_info *inode,
|
||||
struct quota_res *res,
|
||||
unsigned sectors,
|
||||
|
@ -31,7 +31,8 @@ struct flags_set {
|
||||
bool projinherit;
|
||||
};
|
||||
|
||||
static int bch2_inode_flags_set(struct bch_inode_info *inode,
|
||||
static int bch2_inode_flags_set(struct btree_trans *trans,
|
||||
struct bch_inode_info *inode,
|
||||
struct bch_inode_unpacked *bi,
|
||||
void *p)
|
||||
{
|
||||
@ -124,7 +125,8 @@ static int bch2_ioc_fsgetxattr(struct bch_inode_info *inode,
|
||||
return copy_to_user(arg, &fa, sizeof(fa));
|
||||
}
|
||||
|
||||
static int fssetxattr_inode_update_fn(struct bch_inode_info *inode,
|
||||
static int fssetxattr_inode_update_fn(struct btree_trans *trans,
|
||||
struct bch_inode_info *inode,
|
||||
struct bch_inode_unpacked *bi,
|
||||
void *p)
|
||||
{
|
||||
@ -135,7 +137,7 @@ static int fssetxattr_inode_update_fn(struct bch_inode_info *inode,
|
||||
bi->bi_project = s->projid;
|
||||
}
|
||||
|
||||
return bch2_inode_flags_set(inode, bi, p);
|
||||
return bch2_inode_flags_set(trans, inode, bi, p);
|
||||
}
|
||||
|
||||
static int bch2_ioc_fssetxattr(struct bch_fs *c,
|
||||
@ -192,7 +194,8 @@ err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int bch2_reinherit_attrs_fn(struct bch_inode_info *inode,
|
||||
static int bch2_reinherit_attrs_fn(struct btree_trans *trans,
|
||||
struct bch_inode_info *inode,
|
||||
struct bch_inode_unpacked *bi,
|
||||
void *p)
|
||||
{
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include "journal.h"
|
||||
#include "keylist.h"
|
||||
#include "quota.h"
|
||||
#include "snapshot.h"
|
||||
#include "super.h"
|
||||
#include "xattr.h"
|
||||
|
||||
@ -92,7 +93,7 @@ retry:
|
||||
|
||||
ret = bch2_inode_peek(&trans, &iter, &inode_u, inode_inum(inode),
|
||||
BTREE_ITER_INTENT) ?:
|
||||
(set ? set(inode, &inode_u, p) : 0) ?:
|
||||
(set ? set(&trans, inode, &inode_u, p) : 0) ?:
|
||||
bch2_inode_write(&trans, &iter, &inode_u) ?:
|
||||
bch2_trans_commit(&trans, NULL, NULL, BTREE_INSERT_NOFAIL);
|
||||
|
||||
@ -1237,7 +1238,8 @@ static int bch2_get_name(struct dentry *parent, char *name, struct dentry *child
|
||||
struct bch_inode_unpacked inode_u;
|
||||
subvol_inum target;
|
||||
u32 snapshot;
|
||||
unsigned name_len;
|
||||
struct qstr dirent_name;
|
||||
unsigned name_len = 0;
|
||||
int ret;
|
||||
|
||||
if (!S_ISDIR(dir->v.i_mode))
|
||||
@ -1314,9 +1316,10 @@ retry:
|
||||
ret = -ENOENT;
|
||||
goto err;
|
||||
found:
|
||||
name_len = min_t(unsigned, bch2_dirent_name_bytes(d), NAME_MAX);
|
||||
dirent_name = bch2_dirent_get_name(d);
|
||||
|
||||
memcpy(name, d.v->d_name, name_len);
|
||||
name_len = min_t(unsigned, dirent_name.len, NAME_MAX);
|
||||
memcpy(name, dirent_name.name, name_len);
|
||||
name[name_len] = '\0';
|
||||
err:
|
||||
if (bch2_err_matches(ret, BCH_ERR_transaction_restart))
|
||||
@ -1414,7 +1417,8 @@ static void bch2_destroy_inode(struct inode *vinode)
|
||||
call_rcu(&vinode->i_rcu, bch2_i_callback);
|
||||
}
|
||||
|
||||
static int inode_update_times_fn(struct bch_inode_info *inode,
|
||||
static int inode_update_times_fn(struct btree_trans *trans,
|
||||
struct bch_inode_info *inode,
|
||||
struct bch_inode_unpacked *bi,
|
||||
void *p)
|
||||
{
|
||||
|
@ -174,7 +174,8 @@ static inline int bch2_set_projid(struct bch_fs *c,
|
||||
struct inode *bch2_vfs_inode_get(struct bch_fs *, subvol_inum);
|
||||
|
||||
/* returns 0 if we want to do the update, or error is passed up */
|
||||
typedef int (*inode_set_fn)(struct bch_inode_info *,
|
||||
typedef int (*inode_set_fn)(struct btree_trans *,
|
||||
struct bch_inode_info *,
|
||||
struct bch_inode_unpacked *, void *);
|
||||
|
||||
void bch2_inode_update_after_write(struct btree_trans *,
|
||||
|
@ -12,7 +12,7 @@
|
||||
#include "inode.h"
|
||||
#include "keylist.h"
|
||||
#include "recovery.h"
|
||||
#include "subvolume.h"
|
||||
#include "snapshot.h"
|
||||
#include "super.h"
|
||||
#include "xattr.h"
|
||||
|
||||
@ -409,6 +409,28 @@ static inline void snapshots_seen_init(struct snapshots_seen *s)
|
||||
memset(s, 0, sizeof(*s));
|
||||
}
|
||||
|
||||
static int snapshots_seen_add_inorder(struct bch_fs *c, struct snapshots_seen *s, u32 id)
|
||||
{
|
||||
struct snapshots_seen_entry *i, n = {
|
||||
.id = id,
|
||||
.equiv = bch2_snapshot_equiv(c, id),
|
||||
};
|
||||
int ret = 0;
|
||||
|
||||
darray_for_each(s->ids, i) {
|
||||
if (i->id == id)
|
||||
return 0;
|
||||
if (i->id > id)
|
||||
break;
|
||||
}
|
||||
|
||||
ret = darray_insert_item(&s->ids, i - s->ids.data, n);
|
||||
if (ret)
|
||||
bch_err(c, "error reallocating snapshots_seen table (size %zu)",
|
||||
s->ids.size);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int snapshots_seen_update(struct bch_fs *c, struct snapshots_seen *s,
|
||||
enum btree_id btree_id, struct bpos pos)
|
||||
{
|
||||
@ -1123,7 +1145,8 @@ static int extent_ends_at(struct bch_fs *c,
|
||||
|
||||
static int overlapping_extents_found(struct btree_trans *trans,
|
||||
enum btree_id btree,
|
||||
struct bpos pos1, struct bkey pos2,
|
||||
struct bpos pos1, struct snapshots_seen *pos1_seen,
|
||||
struct bkey pos2,
|
||||
bool *fixed,
|
||||
struct extent_end *extent_end)
|
||||
{
|
||||
@ -1209,10 +1232,24 @@ static int overlapping_extents_found(struct btree_trans *trans,
|
||||
|
||||
*fixed = true;
|
||||
|
||||
if (pos1.snapshot == pos2.p.snapshot)
|
||||
if (pos1.snapshot == pos2.p.snapshot) {
|
||||
/*
|
||||
* We overwrote the first extent, and did the overwrite
|
||||
* in the same snapshot:
|
||||
*/
|
||||
extent_end->offset = bkey_start_offset(&pos2);
|
||||
else
|
||||
} else if (pos1.snapshot > pos2.p.snapshot) {
|
||||
/*
|
||||
* We overwrote the first extent in pos2's snapshot:
|
||||
*/
|
||||
ret = snapshots_seen_add_inorder(c, pos1_seen, pos2.p.snapshot);
|
||||
} else {
|
||||
/*
|
||||
* We overwrote the second extent - restart
|
||||
* check_extent() from the top:
|
||||
*/
|
||||
ret = -BCH_ERR_transaction_restart_nested;
|
||||
}
|
||||
}
|
||||
fsck_err:
|
||||
err:
|
||||
@ -1254,6 +1291,7 @@ static int check_overlapping_extents(struct btree_trans *trans,
|
||||
SPOS(iter->pos.inode,
|
||||
i->offset,
|
||||
i->snapshot),
|
||||
&i->seen,
|
||||
*k.k, fixed, i);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include "extent_update.h"
|
||||
#include "inode.h"
|
||||
#include "str_hash.h"
|
||||
#include "snapshot.h"
|
||||
#include "subvolume.h"
|
||||
#include "varint.h"
|
||||
|
||||
@ -1048,6 +1049,11 @@ static int may_delete_deleted_inode(struct btree_trans *trans, struct bpos pos)
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
if (fsck_err_on(S_ISDIR(inode.bi_mode), c,
|
||||
"directory %llu:%u in deleted_inodes btree",
|
||||
pos.offset, pos.snapshot))
|
||||
goto delete;
|
||||
|
||||
if (fsck_err_on(!(inode.bi_flags & BCH_INODE_UNLINKED), c,
|
||||
"non-deleted inode %llu:%u in deleted_inodes btree",
|
||||
pos.offset, pos.snapshot))
|
||||
|
@ -380,10 +380,10 @@ int bch2_extent_fallocate(struct btree_trans *trans,
|
||||
struct bch_fs *c = trans->c;
|
||||
struct disk_reservation disk_res = { 0 };
|
||||
struct closure cl;
|
||||
struct open_buckets open_buckets;
|
||||
struct open_buckets open_buckets = { 0 };
|
||||
struct bkey_s_c k;
|
||||
struct bkey_buf old, new;
|
||||
unsigned sectors_allocated;
|
||||
unsigned sectors_allocated = 0;
|
||||
bool have_reservation = false;
|
||||
bool unwritten = opts.nocow &&
|
||||
c->sb.version >= bcachefs_metadata_version_unwritten_extents;
|
||||
@ -392,9 +392,6 @@ int bch2_extent_fallocate(struct btree_trans *trans,
|
||||
bch2_bkey_buf_init(&old);
|
||||
bch2_bkey_buf_init(&new);
|
||||
closure_init_stack(&cl);
|
||||
open_buckets.nr = 0;
|
||||
retry:
|
||||
sectors_allocated = 0;
|
||||
|
||||
k = bch2_btree_iter_peek_slot(iter);
|
||||
ret = bkey_err(k);
|
||||
@ -413,14 +410,14 @@ retry:
|
||||
*/
|
||||
ret = bch2_disk_reservation_get(c, &disk_res, sectors, new_replicas, 0);
|
||||
if (unlikely(ret))
|
||||
goto out;
|
||||
goto err;
|
||||
|
||||
bch2_bkey_buf_reassemble(&old, c, k);
|
||||
}
|
||||
|
||||
if (have_reservation) {
|
||||
if (!bch2_extents_match(k, bkey_i_to_s_c(old.k)))
|
||||
goto out;
|
||||
goto err;
|
||||
|
||||
bch2_key_resize(&new.k->k, sectors);
|
||||
} else if (!unwritten) {
|
||||
@ -452,13 +449,10 @@ retry:
|
||||
opts.data_replicas,
|
||||
opts.data_replicas,
|
||||
BCH_WATERMARK_normal, 0, &cl, &wp);
|
||||
if (ret) {
|
||||
bch2_trans_unlock(trans);
|
||||
closure_sync(&cl);
|
||||
if (bch2_err_matches(ret, BCH_ERR_operation_blocked))
|
||||
goto retry;
|
||||
return ret;
|
||||
}
|
||||
if (bch2_err_matches(ret, BCH_ERR_operation_blocked))
|
||||
ret = -BCH_ERR_transaction_restart_nested;
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
sectors = min(sectors, wp->sectors_free);
|
||||
sectors_allocated = sectors;
|
||||
@ -477,17 +471,7 @@ retry:
|
||||
|
||||
ret = bch2_extent_update(trans, inum, iter, new.k, &disk_res,
|
||||
0, i_sectors_delta, true);
|
||||
out:
|
||||
if ((atomic_read(&cl.remaining) & CLOSURE_REMAINING_MASK) != 1) {
|
||||
bch2_trans_unlock(trans);
|
||||
closure_sync(&cl);
|
||||
}
|
||||
|
||||
if (bch2_err_matches(ret, BCH_ERR_transaction_restart)) {
|
||||
bch2_trans_begin(trans);
|
||||
goto retry;
|
||||
}
|
||||
|
||||
err:
|
||||
if (!ret && sectors_allocated)
|
||||
bch2_increment_clock(c, sectors_allocated, WRITE);
|
||||
|
||||
@ -496,6 +480,11 @@ out:
|
||||
bch2_bkey_buf_exit(&new, c);
|
||||
bch2_bkey_buf_exit(&old, c);
|
||||
|
||||
if (closure_nr_remaining(&cl) != 1) {
|
||||
bch2_trans_unlock(trans);
|
||||
closure_sync(&cl);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -710,13 +699,15 @@ static void bch2_write_done(struct closure *cl)
|
||||
struct bch_write_op *op = container_of(cl, struct bch_write_op, cl);
|
||||
struct bch_fs *c = op->c;
|
||||
|
||||
EBUG_ON(op->open_buckets.nr);
|
||||
|
||||
bch2_time_stats_update(&c->times[BCH_TIME_data_write], op->start_time);
|
||||
bch2_disk_reservation_put(c, &op->res);
|
||||
|
||||
if (!(op->flags & BCH_WRITE_MOVE))
|
||||
bch2_write_ref_put(c, BCH_WRITE_REF_write);
|
||||
bch2_keylist_free(&op->insert_keys, op->inline_keys);
|
||||
|
||||
bch2_time_stats_update(&c->times[BCH_TIME_data_write], op->start_time);
|
||||
|
||||
EBUG_ON(cl->parent);
|
||||
closure_debug_destroy(cl);
|
||||
if (op->end_io)
|
||||
|
@ -5,7 +5,7 @@
|
||||
#include "error.h"
|
||||
#include "inode.h"
|
||||
#include "quota.h"
|
||||
#include "subvolume.h"
|
||||
#include "snapshot.h"
|
||||
#include "super-io.h"
|
||||
|
||||
static const char * const bch2_quota_types[] = {
|
||||
|
@ -113,6 +113,10 @@ static void rebalance_work_accumulate(struct rebalance_work *w,
|
||||
unsigned percent_full;
|
||||
u64 work = dev_work + unknown_dev;
|
||||
|
||||
/* avoid divide by 0 */
|
||||
if (!capacity)
|
||||
return;
|
||||
|
||||
if (work < dev_work || work < unknown_dev)
|
||||
work = U64_MAX;
|
||||
work = min(work, capacity);
|
||||
|
@ -25,6 +25,7 @@
|
||||
#include "recovery.h"
|
||||
#include "replicas.h"
|
||||
#include "sb-clean.h"
|
||||
#include "snapshot.h"
|
||||
#include "subvolume.h"
|
||||
#include "super-io.h"
|
||||
|
||||
|
@ -8,11 +8,13 @@
|
||||
#include <linux/sched.h>
|
||||
#include <linux/sched/clock.h>
|
||||
#include <linux/sched/rt.h>
|
||||
#include <linux/six.h>
|
||||
#include <linux/sched/task.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include <trace/events/lock.h>
|
||||
|
||||
#include "six.h"
|
||||
|
||||
#ifdef DEBUG
|
||||
#define EBUG_ON(cond) BUG_ON(cond)
|
||||
#else
|
||||
@ -222,15 +224,23 @@ again:
|
||||
if (ret <= 0)
|
||||
goto unlock;
|
||||
|
||||
__list_del(w->list.prev, w->list.next);
|
||||
task = w->task;
|
||||
/*
|
||||
* Do no writes to @w besides setting lock_acquired - otherwise
|
||||
* we would need a memory barrier:
|
||||
* Similar to percpu_rwsem_wake_function(), we need to guard
|
||||
* against the wakee noticing w->lock_acquired, returning, and
|
||||
* then exiting before we do the wakeup:
|
||||
*/
|
||||
barrier();
|
||||
w->lock_acquired = true;
|
||||
task = get_task_struct(w->task);
|
||||
__list_del(w->list.prev, w->list.next);
|
||||
/*
|
||||
* The release barrier here ensures the ordering of the
|
||||
* __list_del before setting w->lock_acquired; @w is on the
|
||||
* stack of the thread doing the waiting and will be reused
|
||||
* after it sees w->lock_acquired with no other locking:
|
||||
* pairs with smp_load_acquire() in six_lock_slowpath()
|
||||
*/
|
||||
smp_store_release(&w->lock_acquired, true);
|
||||
wake_up_process(task);
|
||||
put_task_struct(task);
|
||||
}
|
||||
|
||||
six_clear_bitmask(lock, SIX_LOCK_WAITING_read << lock_type);
|
||||
@ -501,17 +511,32 @@ static int six_lock_slowpath(struct six_lock *lock, enum six_lock_type type,
|
||||
while (1) {
|
||||
set_current_state(TASK_UNINTERRUPTIBLE);
|
||||
|
||||
if (wait->lock_acquired)
|
||||
/*
|
||||
* Ensures that writes to the waitlist entry happen after we see
|
||||
* wait->lock_acquired: pairs with the smp_store_release in
|
||||
* __six_lock_wakeup
|
||||
*/
|
||||
if (smp_load_acquire(&wait->lock_acquired))
|
||||
break;
|
||||
|
||||
ret = should_sleep_fn ? should_sleep_fn(lock, p) : 0;
|
||||
if (unlikely(ret)) {
|
||||
bool acquired;
|
||||
|
||||
/*
|
||||
* If should_sleep_fn() returns an error, we are
|
||||
* required to return that error even if we already
|
||||
* acquired the lock - should_sleep_fn() might have
|
||||
* modified external state (e.g. when the deadlock cycle
|
||||
* detector in bcachefs issued a transaction restart)
|
||||
*/
|
||||
raw_spin_lock(&lock->wait_lock);
|
||||
if (!wait->lock_acquired)
|
||||
acquired = wait->lock_acquired;
|
||||
if (!acquired)
|
||||
list_del(&wait->list);
|
||||
raw_spin_unlock(&lock->wait_lock);
|
||||
|
||||
if (unlikely(wait->lock_acquired))
|
||||
if (unlikely(acquired))
|
||||
do_six_unlock_type(lock, type);
|
||||
break;
|
||||
}
|
886
libbcachefs/snapshot.c
Normal file
886
libbcachefs/snapshot.c
Normal file
@ -0,0 +1,886 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
#include "bcachefs.h"
|
||||
#include "btree_key_cache.h"
|
||||
#include "btree_update.h"
|
||||
#include "errcode.h"
|
||||
#include "error.h"
|
||||
#include "fs.h"
|
||||
#include "snapshot.h"
|
||||
|
||||
#include <linux/random.h>
|
||||
|
||||
/*
|
||||
* Snapshot trees:
|
||||
*
|
||||
* Keys in BTREE_ID_snapshot_trees identify a whole tree of snapshot nodes; they
|
||||
* exist to provide a stable identifier for the whole lifetime of a snapshot
|
||||
* tree.
|
||||
*/
|
||||
|
||||
void bch2_snapshot_tree_to_text(struct printbuf *out, struct bch_fs *c,
|
||||
struct bkey_s_c k)
|
||||
{
|
||||
struct bkey_s_c_snapshot_tree t = bkey_s_c_to_snapshot_tree(k);
|
||||
|
||||
prt_printf(out, "subvol %u root snapshot %u",
|
||||
le32_to_cpu(t.v->master_subvol),
|
||||
le32_to_cpu(t.v->root_snapshot));
|
||||
}
|
||||
|
||||
int bch2_snapshot_tree_invalid(const struct bch_fs *c, struct bkey_s_c k,
|
||||
enum bkey_invalid_flags flags,
|
||||
struct printbuf *err)
|
||||
{
|
||||
if (bkey_gt(k.k->p, POS(0, U32_MAX)) ||
|
||||
bkey_lt(k.k->p, POS(0, 1))) {
|
||||
prt_printf(err, "bad pos");
|
||||
return -BCH_ERR_invalid_bkey;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int bch2_snapshot_tree_lookup(struct btree_trans *trans, u32 id,
|
||||
struct bch_snapshot_tree *s)
|
||||
{
|
||||
int ret = bch2_bkey_get_val_typed(trans, BTREE_ID_snapshot_trees, POS(0, id),
|
||||
BTREE_ITER_WITH_UPDATES, snapshot_tree, s);
|
||||
|
||||
if (bch2_err_matches(ret, ENOENT))
|
||||
ret = -BCH_ERR_ENOENT_snapshot_tree;
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct bkey_i_snapshot_tree *
|
||||
__bch2_snapshot_tree_create(struct btree_trans *trans)
|
||||
{
|
||||
struct btree_iter iter;
|
||||
int ret = bch2_bkey_get_empty_slot(trans, &iter,
|
||||
BTREE_ID_snapshot_trees, POS(0, U32_MAX));
|
||||
struct bkey_i_snapshot_tree *s_t;
|
||||
|
||||
if (ret == -BCH_ERR_ENOSPC_btree_slot)
|
||||
ret = -BCH_ERR_ENOSPC_snapshot_tree;
|
||||
if (ret)
|
||||
return ERR_PTR(ret);
|
||||
|
||||
s_t = bch2_bkey_alloc(trans, &iter, 0, snapshot_tree);
|
||||
ret = PTR_ERR_OR_ZERO(s_t);
|
||||
bch2_trans_iter_exit(trans, &iter);
|
||||
return ret ? ERR_PTR(ret) : s_t;
|
||||
}
|
||||
|
||||
static int bch2_snapshot_tree_create(struct btree_trans *trans,
|
||||
u32 root_id, u32 subvol_id, u32 *tree_id)
|
||||
{
|
||||
struct bkey_i_snapshot_tree *n_tree =
|
||||
__bch2_snapshot_tree_create(trans);
|
||||
|
||||
if (IS_ERR(n_tree))
|
||||
return PTR_ERR(n_tree);
|
||||
|
||||
n_tree->v.master_subvol = cpu_to_le32(subvol_id);
|
||||
n_tree->v.root_snapshot = cpu_to_le32(root_id);
|
||||
*tree_id = n_tree->k.p.offset;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Snapshot nodes: */
|
||||
|
||||
static inline u32 get_ancestor_below(struct snapshot_table *t, u32 id, u32 ancestor)
|
||||
{
|
||||
const struct snapshot_t *s = __snapshot_t(t, id);
|
||||
|
||||
if (s->skip[2] <= ancestor)
|
||||
return s->skip[2];
|
||||
if (s->skip[1] <= ancestor)
|
||||
return s->skip[1];
|
||||
if (s->skip[0] <= ancestor)
|
||||
return s->skip[0];
|
||||
return s->parent;
|
||||
}
|
||||
|
||||
bool __bch2_snapshot_is_ancestor(struct bch_fs *c, u32 id, u32 ancestor)
|
||||
{
|
||||
struct snapshot_table *t;
|
||||
bool ret;
|
||||
|
||||
EBUG_ON(c->curr_recovery_pass <= BCH_RECOVERY_PASS_check_snapshots);
|
||||
|
||||
rcu_read_lock();
|
||||
t = rcu_dereference(c->snapshots);
|
||||
|
||||
while (id && id < ancestor - IS_ANCESTOR_BITMAP)
|
||||
id = get_ancestor_below(t, id, ancestor);
|
||||
|
||||
ret = id && id < ancestor
|
||||
? test_bit(ancestor - id - 1, __snapshot_t(t, id)->is_ancestor)
|
||||
: id == ancestor;
|
||||
rcu_read_unlock();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool bch2_snapshot_is_ancestor_early(struct bch_fs *c, u32 id, u32 ancestor)
|
||||
{
|
||||
struct snapshot_table *t;
|
||||
|
||||
rcu_read_lock();
|
||||
t = rcu_dereference(c->snapshots);
|
||||
|
||||
while (id && id < ancestor)
|
||||
id = __snapshot_t(t, id)->parent;
|
||||
rcu_read_unlock();
|
||||
|
||||
return id == ancestor;
|
||||
}
|
||||
|
||||
static noinline struct snapshot_t *__snapshot_t_mut(struct bch_fs *c, u32 id)
|
||||
{
|
||||
size_t idx = U32_MAX - id;
|
||||
size_t new_size;
|
||||
struct snapshot_table *new, *old;
|
||||
|
||||
new_size = max(16UL, roundup_pow_of_two(idx + 1));
|
||||
|
||||
new = kvzalloc(struct_size(new, s, new_size), GFP_KERNEL);
|
||||
if (!new)
|
||||
return NULL;
|
||||
|
||||
old = rcu_dereference_protected(c->snapshots, true);
|
||||
if (old)
|
||||
memcpy(new->s,
|
||||
rcu_dereference_protected(c->snapshots, true)->s,
|
||||
sizeof(new->s[0]) * c->snapshot_table_size);
|
||||
|
||||
rcu_assign_pointer(c->snapshots, new);
|
||||
c->snapshot_table_size = new_size;
|
||||
if (old)
|
||||
kvfree_rcu(old);
|
||||
|
||||
return &rcu_dereference_protected(c->snapshots, true)->s[idx];
|
||||
}
|
||||
|
||||
static inline struct snapshot_t *snapshot_t_mut(struct bch_fs *c, u32 id)
|
||||
{
|
||||
size_t idx = U32_MAX - id;
|
||||
|
||||
lockdep_assert_held(&c->snapshot_table_lock);
|
||||
|
||||
if (likely(idx < c->snapshot_table_size))
|
||||
return &rcu_dereference_protected(c->snapshots, true)->s[idx];
|
||||
|
||||
return __snapshot_t_mut(c, id);
|
||||
}
|
||||
|
||||
void bch2_snapshot_to_text(struct printbuf *out, struct bch_fs *c,
|
||||
struct bkey_s_c k)
|
||||
{
|
||||
struct bkey_s_c_snapshot s = bkey_s_c_to_snapshot(k);
|
||||
|
||||
prt_printf(out, "is_subvol %llu deleted %llu parent %10u children %10u %10u subvol %u tree %u",
|
||||
BCH_SNAPSHOT_SUBVOL(s.v),
|
||||
BCH_SNAPSHOT_DELETED(s.v),
|
||||
le32_to_cpu(s.v->parent),
|
||||
le32_to_cpu(s.v->children[0]),
|
||||
le32_to_cpu(s.v->children[1]),
|
||||
le32_to_cpu(s.v->subvol),
|
||||
le32_to_cpu(s.v->tree));
|
||||
|
||||
if (bkey_val_bytes(k.k) > offsetof(struct bch_snapshot, depth))
|
||||
prt_printf(out, " depth %u skiplist %u %u %u",
|
||||
le32_to_cpu(s.v->depth),
|
||||
le32_to_cpu(s.v->skip[0]),
|
||||
le32_to_cpu(s.v->skip[1]),
|
||||
le32_to_cpu(s.v->skip[2]));
|
||||
}
|
||||
|
||||
int bch2_snapshot_invalid(const struct bch_fs *c, struct bkey_s_c k,
|
||||
enum bkey_invalid_flags flags,
|
||||
struct printbuf *err)
|
||||
{
|
||||
struct bkey_s_c_snapshot s;
|
||||
u32 i, id;
|
||||
|
||||
if (bkey_gt(k.k->p, POS(0, U32_MAX)) ||
|
||||
bkey_lt(k.k->p, POS(0, 1))) {
|
||||
prt_printf(err, "bad pos");
|
||||
return -BCH_ERR_invalid_bkey;
|
||||
}
|
||||
|
||||
s = bkey_s_c_to_snapshot(k);
|
||||
|
||||
id = le32_to_cpu(s.v->parent);
|
||||
if (id && id <= k.k->p.offset) {
|
||||
prt_printf(err, "bad parent node (%u <= %llu)",
|
||||
id, k.k->p.offset);
|
||||
return -BCH_ERR_invalid_bkey;
|
||||
}
|
||||
|
||||
if (le32_to_cpu(s.v->children[0]) < le32_to_cpu(s.v->children[1])) {
|
||||
prt_printf(err, "children not normalized");
|
||||
return -BCH_ERR_invalid_bkey;
|
||||
}
|
||||
|
||||
if (s.v->children[0] &&
|
||||
s.v->children[0] == s.v->children[1]) {
|
||||
prt_printf(err, "duplicate child nodes");
|
||||
return -BCH_ERR_invalid_bkey;
|
||||
}
|
||||
|
||||
for (i = 0; i < 2; i++) {
|
||||
id = le32_to_cpu(s.v->children[i]);
|
||||
|
||||
if (id >= k.k->p.offset) {
|
||||
prt_printf(err, "bad child node (%u >= %llu)",
|
||||
id, k.k->p.offset);
|
||||
return -BCH_ERR_invalid_bkey;
|
||||
}
|
||||
}
|
||||
|
||||
if (bkey_val_bytes(k.k) > offsetof(struct bch_snapshot, skip)) {
|
||||
if (le32_to_cpu(s.v->skip[0]) > le32_to_cpu(s.v->skip[1]) ||
|
||||
le32_to_cpu(s.v->skip[1]) > le32_to_cpu(s.v->skip[2])) {
|
||||
prt_printf(err, "skiplist not normalized");
|
||||
return -BCH_ERR_invalid_bkey;
|
||||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(s.v->skip); i++) {
|
||||
id = le32_to_cpu(s.v->skip[i]);
|
||||
|
||||
if (!id != !s.v->parent ||
|
||||
(s.v->parent &&
|
||||
id <= k.k->p.offset)) {
|
||||
prt_printf(err, "bad skiplist node %u)", id);
|
||||
return -BCH_ERR_invalid_bkey;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int bch2_mark_snapshot(struct btree_trans *trans,
|
||||
enum btree_id btree, unsigned level,
|
||||
struct bkey_s_c old, struct bkey_s_c new,
|
||||
unsigned flags)
|
||||
{
|
||||
struct bch_fs *c = trans->c;
|
||||
struct snapshot_t *t;
|
||||
u32 id = new.k->p.offset;
|
||||
int ret = 0;
|
||||
|
||||
mutex_lock(&c->snapshot_table_lock);
|
||||
|
||||
t = snapshot_t_mut(c, id);
|
||||
if (!t) {
|
||||
ret = -BCH_ERR_ENOMEM_mark_snapshot;
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (new.k->type == KEY_TYPE_snapshot) {
|
||||
struct bkey_s_c_snapshot s = bkey_s_c_to_snapshot(new);
|
||||
u32 parent = id;
|
||||
|
||||
t->parent = le32_to_cpu(s.v->parent);
|
||||
t->children[0] = le32_to_cpu(s.v->children[0]);
|
||||
t->children[1] = le32_to_cpu(s.v->children[1]);
|
||||
t->subvol = BCH_SNAPSHOT_SUBVOL(s.v) ? le32_to_cpu(s.v->subvol) : 0;
|
||||
t->tree = le32_to_cpu(s.v->tree);
|
||||
|
||||
if (bkey_val_bytes(s.k) > offsetof(struct bch_snapshot, depth)) {
|
||||
t->depth = le32_to_cpu(s.v->depth);
|
||||
t->skip[0] = le32_to_cpu(s.v->skip[0]);
|
||||
t->skip[1] = le32_to_cpu(s.v->skip[1]);
|
||||
t->skip[2] = le32_to_cpu(s.v->skip[2]);
|
||||
} else {
|
||||
t->depth = 0;
|
||||
t->skip[0] = 0;
|
||||
t->skip[1] = 0;
|
||||
t->skip[2] = 0;
|
||||
}
|
||||
|
||||
while ((parent = bch2_snapshot_parent_early(c, parent)) &&
|
||||
parent - id - 1 < IS_ANCESTOR_BITMAP)
|
||||
__set_bit(parent - id - 1, t->is_ancestor);
|
||||
|
||||
if (BCH_SNAPSHOT_DELETED(s.v)) {
|
||||
set_bit(BCH_FS_HAVE_DELETED_SNAPSHOTS, &c->flags);
|
||||
c->recovery_passes_explicit |= BIT_ULL(BCH_RECOVERY_PASS_delete_dead_snapshots);
|
||||
}
|
||||
} else {
|
||||
memset(t, 0, sizeof(*t));
|
||||
}
|
||||
err:
|
||||
mutex_unlock(&c->snapshot_table_lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int bch2_snapshot_lookup(struct btree_trans *trans, u32 id,
|
||||
struct bch_snapshot *s)
|
||||
{
|
||||
return bch2_bkey_get_val_typed(trans, BTREE_ID_snapshots, POS(0, id),
|
||||
BTREE_ITER_WITH_UPDATES, snapshot, s);
|
||||
}
|
||||
|
||||
int bch2_snapshot_live(struct btree_trans *trans, u32 id)
|
||||
{
|
||||
struct bch_snapshot v;
|
||||
int ret;
|
||||
|
||||
if (!id)
|
||||
return 0;
|
||||
|
||||
ret = bch2_snapshot_lookup(trans, id, &v);
|
||||
if (bch2_err_matches(ret, ENOENT))
|
||||
bch_err(trans->c, "snapshot node %u not found", id);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return !BCH_SNAPSHOT_DELETED(&v);
|
||||
}
|
||||
|
||||
/*
|
||||
* If @k is a snapshot with just one live child, it's part of a linear chain,
|
||||
* which we consider to be an equivalence class: and then after snapshot
|
||||
* deletion cleanup, there should only be a single key at a given position in
|
||||
* this equivalence class.
|
||||
*
|
||||
* This sets the equivalence class of @k to be the child's equivalence class, if
|
||||
* it's part of such a linear chain: this correctly sets equivalence classes on
|
||||
* startup if we run leaf to root (i.e. in natural key order).
|
||||
*/
|
||||
int bch2_snapshot_set_equiv(struct btree_trans *trans, struct bkey_s_c k)
|
||||
{
|
||||
struct bch_fs *c = trans->c;
|
||||
unsigned i, nr_live = 0, live_idx = 0;
|
||||
struct bkey_s_c_snapshot snap;
|
||||
u32 id = k.k->p.offset, child[2];
|
||||
|
||||
if (k.k->type != KEY_TYPE_snapshot)
|
||||
return 0;
|
||||
|
||||
snap = bkey_s_c_to_snapshot(k);
|
||||
|
||||
child[0] = le32_to_cpu(snap.v->children[0]);
|
||||
child[1] = le32_to_cpu(snap.v->children[1]);
|
||||
|
||||
for (i = 0; i < 2; i++) {
|
||||
int ret = bch2_snapshot_live(trans, child[i]);
|
||||
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (ret)
|
||||
live_idx = i;
|
||||
nr_live += ret;
|
||||
}
|
||||
|
||||
mutex_lock(&c->snapshot_table_lock);
|
||||
|
||||
snapshot_t_mut(c, id)->equiv = nr_live == 1
|
||||
? snapshot_t_mut(c, child[live_idx])->equiv
|
||||
: id;
|
||||
|
||||
mutex_unlock(&c->snapshot_table_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* fsck: */
|
||||
|
||||
static u32 bch2_snapshot_child(struct bch_fs *c, u32 id, unsigned child)
|
||||
{
|
||||
return snapshot_t(c, id)->children[child];
|
||||
}
|
||||
|
||||
static u32 bch2_snapshot_left_child(struct bch_fs *c, u32 id)
|
||||
{
|
||||
return bch2_snapshot_child(c, id, 0);
|
||||
}
|
||||
|
||||
static u32 bch2_snapshot_right_child(struct bch_fs *c, u32 id)
|
||||
{
|
||||
return bch2_snapshot_child(c, id, 1);
|
||||
}
|
||||
|
||||
static u32 bch2_snapshot_tree_next(struct bch_fs *c, u32 id)
|
||||
{
|
||||
u32 n, parent;
|
||||
|
||||
n = bch2_snapshot_left_child(c, id);
|
||||
if (n)
|
||||
return n;
|
||||
|
||||
while ((parent = bch2_snapshot_parent(c, id))) {
|
||||
n = bch2_snapshot_right_child(c, parent);
|
||||
if (n && n != id)
|
||||
return n;
|
||||
id = parent;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static u32 bch2_snapshot_tree_oldest_subvol(struct bch_fs *c, u32 snapshot_root)
|
||||
{
|
||||
u32 id = snapshot_root;
|
||||
u32 subvol = 0, s;
|
||||
|
||||
while (id) {
|
||||
s = snapshot_t(c, id)->subvol;
|
||||
|
||||
if (s && (!subvol || s < subvol))
|
||||
subvol = s;
|
||||
|
||||
id = bch2_snapshot_tree_next(c, id);
|
||||
}
|
||||
|
||||
return subvol;
|
||||
}
|
||||
|
||||
static int bch2_snapshot_tree_master_subvol(struct btree_trans *trans,
|
||||
u32 snapshot_root, u32 *subvol_id)
|
||||
{
|
||||
struct bch_fs *c = trans->c;
|
||||
struct btree_iter iter;
|
||||
struct bkey_s_c k;
|
||||
struct bkey_s_c_subvolume s;
|
||||
bool found = false;
|
||||
int ret;
|
||||
|
||||
for_each_btree_key_norestart(trans, iter, BTREE_ID_subvolumes, POS_MIN,
|
||||
0, k, ret) {
|
||||
if (k.k->type != KEY_TYPE_subvolume)
|
||||
continue;
|
||||
|
||||
s = bkey_s_c_to_subvolume(k);
|
||||
if (!bch2_snapshot_is_ancestor(c, le32_to_cpu(s.v->snapshot), snapshot_root))
|
||||
continue;
|
||||
if (!BCH_SUBVOLUME_SNAP(s.v)) {
|
||||
*subvol_id = s.k->p.offset;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bch2_trans_iter_exit(trans, &iter);
|
||||
|
||||
if (!ret && !found) {
|
||||
struct bkey_i_subvolume *s;
|
||||
|
||||
*subvol_id = bch2_snapshot_tree_oldest_subvol(c, snapshot_root);
|
||||
|
||||
s = bch2_bkey_get_mut_typed(trans, &iter,
|
||||
BTREE_ID_subvolumes, POS(0, *subvol_id),
|
||||
0, subvolume);
|
||||
ret = PTR_ERR_OR_ZERO(s);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
SET_BCH_SUBVOLUME_SNAP(&s->v, false);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int check_snapshot_tree(struct btree_trans *trans,
|
||||
struct btree_iter *iter,
|
||||
struct bkey_s_c k)
|
||||
{
|
||||
struct bch_fs *c = trans->c;
|
||||
struct bkey_s_c_snapshot_tree st;
|
||||
struct bch_snapshot s;
|
||||
struct bch_subvolume subvol;
|
||||
struct printbuf buf = PRINTBUF;
|
||||
u32 root_id;
|
||||
int ret;
|
||||
|
||||
if (k.k->type != KEY_TYPE_snapshot_tree)
|
||||
return 0;
|
||||
|
||||
st = bkey_s_c_to_snapshot_tree(k);
|
||||
root_id = le32_to_cpu(st.v->root_snapshot);
|
||||
|
||||
ret = bch2_snapshot_lookup(trans, root_id, &s);
|
||||
if (ret && !bch2_err_matches(ret, ENOENT))
|
||||
goto err;
|
||||
|
||||
if (fsck_err_on(ret ||
|
||||
root_id != bch2_snapshot_root(c, root_id) ||
|
||||
st.k->p.offset != le32_to_cpu(s.tree),
|
||||
c,
|
||||
"snapshot tree points to missing/incorrect snapshot:\n %s",
|
||||
(bch2_bkey_val_to_text(&buf, c, st.s_c), buf.buf))) {
|
||||
ret = bch2_btree_delete_at(trans, iter, 0);
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = bch2_subvolume_get(trans, le32_to_cpu(st.v->master_subvol),
|
||||
false, 0, &subvol);
|
||||
if (ret && !bch2_err_matches(ret, ENOENT))
|
||||
goto err;
|
||||
|
||||
if (fsck_err_on(ret, c,
|
||||
"snapshot tree points to missing subvolume:\n %s",
|
||||
(printbuf_reset(&buf),
|
||||
bch2_bkey_val_to_text(&buf, c, st.s_c), buf.buf)) ||
|
||||
fsck_err_on(!bch2_snapshot_is_ancestor_early(c,
|
||||
le32_to_cpu(subvol.snapshot),
|
||||
root_id), c,
|
||||
"snapshot tree points to subvolume that does not point to snapshot in this tree:\n %s",
|
||||
(printbuf_reset(&buf),
|
||||
bch2_bkey_val_to_text(&buf, c, st.s_c), buf.buf)) ||
|
||||
fsck_err_on(BCH_SUBVOLUME_SNAP(&subvol), c,
|
||||
"snapshot tree points to snapshot subvolume:\n %s",
|
||||
(printbuf_reset(&buf),
|
||||
bch2_bkey_val_to_text(&buf, c, st.s_c), buf.buf))) {
|
||||
struct bkey_i_snapshot_tree *u;
|
||||
u32 subvol_id;
|
||||
|
||||
ret = bch2_snapshot_tree_master_subvol(trans, root_id, &subvol_id);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
u = bch2_bkey_make_mut_typed(trans, iter, &k, 0, snapshot_tree);
|
||||
ret = PTR_ERR_OR_ZERO(u);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
u->v.master_subvol = cpu_to_le32(subvol_id);
|
||||
st = snapshot_tree_i_to_s_c(u);
|
||||
}
|
||||
err:
|
||||
fsck_err:
|
||||
printbuf_exit(&buf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* For each snapshot_tree, make sure it points to the root of a snapshot tree
|
||||
* and that snapshot entry points back to it, or delete it.
|
||||
*
|
||||
* And, make sure it points to a subvolume within that snapshot tree, or correct
|
||||
* it to point to the oldest subvolume within that snapshot tree.
|
||||
*/
|
||||
int bch2_check_snapshot_trees(struct bch_fs *c)
|
||||
{
|
||||
struct btree_iter iter;
|
||||
struct bkey_s_c k;
|
||||
int ret;
|
||||
|
||||
ret = bch2_trans_run(c,
|
||||
for_each_btree_key_commit(&trans, iter,
|
||||
BTREE_ID_snapshot_trees, POS_MIN,
|
||||
BTREE_ITER_PREFETCH, k,
|
||||
NULL, NULL, BTREE_INSERT_LAZY_RW|BTREE_INSERT_NOFAIL,
|
||||
check_snapshot_tree(&trans, &iter, k)));
|
||||
|
||||
if (ret)
|
||||
bch_err(c, "error %i checking snapshot trees", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Look up snapshot tree for @tree_id and find root,
|
||||
* make sure @snap_id is a descendent:
|
||||
*/
|
||||
static int snapshot_tree_ptr_good(struct btree_trans *trans,
|
||||
u32 snap_id, u32 tree_id)
|
||||
{
|
||||
struct bch_snapshot_tree s_t;
|
||||
int ret = bch2_snapshot_tree_lookup(trans, tree_id, &s_t);
|
||||
|
||||
if (bch2_err_matches(ret, ENOENT))
|
||||
return 0;
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return bch2_snapshot_is_ancestor_early(trans->c, snap_id, le32_to_cpu(s_t.root_snapshot));
|
||||
}
|
||||
|
||||
u32 bch2_snapshot_skiplist_get(struct bch_fs *c, u32 id)
|
||||
{
|
||||
const struct snapshot_t *s;
|
||||
|
||||
if (!id)
|
||||
return 0;
|
||||
|
||||
rcu_read_lock();
|
||||
s = snapshot_t(c, id);
|
||||
if (s->parent)
|
||||
id = bch2_snapshot_nth_parent(c, id, get_random_u32_below(s->depth));
|
||||
rcu_read_unlock();
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
static int snapshot_skiplist_good(struct btree_trans *trans, struct bch_snapshot s)
|
||||
{
|
||||
struct bch_snapshot a;
|
||||
unsigned i;
|
||||
int ret;
|
||||
|
||||
for (i = 0; i < 3; i++) {
|
||||
if (!s.parent != !s.skip[i])
|
||||
return false;
|
||||
|
||||
if (!s.parent)
|
||||
continue;
|
||||
|
||||
ret = bch2_snapshot_lookup(trans, le32_to_cpu(s.skip[i]), &a);
|
||||
if (bch2_err_matches(ret, ENOENT))
|
||||
return false;
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (a.tree != s.tree)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* snapshot_tree pointer was incorrect: look up root snapshot node, make sure
|
||||
* its snapshot_tree pointer is correct (allocate new one if necessary), then
|
||||
* update this node's pointer to root node's pointer:
|
||||
*/
|
||||
static int snapshot_tree_ptr_repair(struct btree_trans *trans,
|
||||
struct btree_iter *iter,
|
||||
struct bkey_s_c k,
|
||||
struct bch_snapshot *s)
|
||||
{
|
||||
struct bch_fs *c = trans->c;
|
||||
struct btree_iter root_iter;
|
||||
struct bch_snapshot_tree s_t;
|
||||
struct bkey_s_c_snapshot root;
|
||||
struct bkey_i_snapshot *u;
|
||||
u32 root_id = bch2_snapshot_root(c, k.k->p.offset), tree_id;
|
||||
int ret;
|
||||
|
||||
root = bch2_bkey_get_iter_typed(trans, &root_iter,
|
||||
BTREE_ID_snapshots, POS(0, root_id),
|
||||
BTREE_ITER_WITH_UPDATES, snapshot);
|
||||
ret = bkey_err(root);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
tree_id = le32_to_cpu(root.v->tree);
|
||||
|
||||
ret = bch2_snapshot_tree_lookup(trans, tree_id, &s_t);
|
||||
if (ret && !bch2_err_matches(ret, ENOENT))
|
||||
return ret;
|
||||
|
||||
if (ret || le32_to_cpu(s_t.root_snapshot) != root_id) {
|
||||
u = bch2_bkey_make_mut_typed(trans, &root_iter, &root.s_c, 0, snapshot);
|
||||
ret = PTR_ERR_OR_ZERO(u) ?:
|
||||
bch2_snapshot_tree_create(trans, root_id,
|
||||
bch2_snapshot_tree_oldest_subvol(c, root_id),
|
||||
&tree_id);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
u->v.tree = cpu_to_le32(tree_id);
|
||||
if (k.k->p.offset == root_id)
|
||||
*s = u->v;
|
||||
}
|
||||
|
||||
if (k.k->p.offset != root_id) {
|
||||
u = bch2_bkey_make_mut_typed(trans, iter, &k, 0, snapshot);
|
||||
ret = PTR_ERR_OR_ZERO(u);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
u->v.tree = cpu_to_le32(tree_id);
|
||||
*s = u->v;
|
||||
}
|
||||
err:
|
||||
bch2_trans_iter_exit(trans, &root_iter);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int check_snapshot(struct btree_trans *trans,
|
||||
struct btree_iter *iter,
|
||||
struct bkey_s_c k)
|
||||
{
|
||||
struct bch_fs *c = trans->c;
|
||||
struct bch_snapshot s;
|
||||
struct bch_subvolume subvol;
|
||||
struct bch_snapshot v;
|
||||
struct bkey_i_snapshot *u;
|
||||
u32 parent_id = bch2_snapshot_parent_early(c, k.k->p.offset);
|
||||
u32 real_depth;
|
||||
struct printbuf buf = PRINTBUF;
|
||||
bool should_have_subvol;
|
||||
u32 i, id;
|
||||
int ret = 0;
|
||||
|
||||
if (k.k->type != KEY_TYPE_snapshot)
|
||||
return 0;
|
||||
|
||||
memset(&s, 0, sizeof(s));
|
||||
memcpy(&s, k.v, bkey_val_bytes(k.k));
|
||||
|
||||
id = le32_to_cpu(s.parent);
|
||||
if (id) {
|
||||
ret = bch2_snapshot_lookup(trans, id, &v);
|
||||
if (bch2_err_matches(ret, ENOENT))
|
||||
bch_err(c, "snapshot with nonexistent parent:\n %s",
|
||||
(bch2_bkey_val_to_text(&buf, c, k), buf.buf));
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
if (le32_to_cpu(v.children[0]) != k.k->p.offset &&
|
||||
le32_to_cpu(v.children[1]) != k.k->p.offset) {
|
||||
bch_err(c, "snapshot parent %u missing pointer to child %llu",
|
||||
id, k.k->p.offset);
|
||||
ret = -EINVAL;
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < 2 && s.children[i]; i++) {
|
||||
id = le32_to_cpu(s.children[i]);
|
||||
|
||||
ret = bch2_snapshot_lookup(trans, id, &v);
|
||||
if (bch2_err_matches(ret, ENOENT))
|
||||
bch_err(c, "snapshot node %llu has nonexistent child %u",
|
||||
k.k->p.offset, id);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
if (le32_to_cpu(v.parent) != k.k->p.offset) {
|
||||
bch_err(c, "snapshot child %u has wrong parent (got %u should be %llu)",
|
||||
id, le32_to_cpu(v.parent), k.k->p.offset);
|
||||
ret = -EINVAL;
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
should_have_subvol = BCH_SNAPSHOT_SUBVOL(&s) &&
|
||||
!BCH_SNAPSHOT_DELETED(&s);
|
||||
|
||||
if (should_have_subvol) {
|
||||
id = le32_to_cpu(s.subvol);
|
||||
ret = bch2_subvolume_get(trans, id, 0, false, &subvol);
|
||||
if (bch2_err_matches(ret, ENOENT))
|
||||
bch_err(c, "snapshot points to nonexistent subvolume:\n %s",
|
||||
(bch2_bkey_val_to_text(&buf, c, k), buf.buf));
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
if (BCH_SNAPSHOT_SUBVOL(&s) != (le32_to_cpu(subvol.snapshot) == k.k->p.offset)) {
|
||||
bch_err(c, "snapshot node %llu has wrong BCH_SNAPSHOT_SUBVOL",
|
||||
k.k->p.offset);
|
||||
ret = -EINVAL;
|
||||
goto err;
|
||||
}
|
||||
} else {
|
||||
if (fsck_err_on(s.subvol, c, "snapshot should not point to subvol:\n %s",
|
||||
(bch2_bkey_val_to_text(&buf, c, k), buf.buf))) {
|
||||
u = bch2_bkey_make_mut_typed(trans, iter, &k, 0, snapshot);
|
||||
ret = PTR_ERR_OR_ZERO(u);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
u->v.subvol = 0;
|
||||
s = u->v;
|
||||
}
|
||||
}
|
||||
|
||||
ret = snapshot_tree_ptr_good(trans, k.k->p.offset, le32_to_cpu(s.tree));
|
||||
if (ret < 0)
|
||||
goto err;
|
||||
|
||||
if (fsck_err_on(!ret, c, "snapshot points to missing/incorrect tree:\n %s",
|
||||
(bch2_bkey_val_to_text(&buf, c, k), buf.buf))) {
|
||||
ret = snapshot_tree_ptr_repair(trans, iter, k, &s);
|
||||
if (ret)
|
||||
goto err;
|
||||
}
|
||||
ret = 0;
|
||||
|
||||
real_depth = bch2_snapshot_depth(c, parent_id);
|
||||
|
||||
if (le32_to_cpu(s.depth) != real_depth &&
|
||||
(c->sb.version_upgrade_complete < bcachefs_metadata_version_snapshot_skiplists ||
|
||||
fsck_err(c, "snapshot with incorrect depth field, should be %u:\n %s",
|
||||
real_depth, (bch2_bkey_val_to_text(&buf, c, k), buf.buf)))) {
|
||||
u = bch2_bkey_make_mut_typed(trans, iter, &k, 0, snapshot);
|
||||
ret = PTR_ERR_OR_ZERO(u);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
u->v.depth = cpu_to_le32(real_depth);
|
||||
s = u->v;
|
||||
}
|
||||
|
||||
ret = snapshot_skiplist_good(trans, s);
|
||||
if (ret < 0)
|
||||
goto err;
|
||||
|
||||
if (!ret &&
|
||||
(c->sb.version_upgrade_complete < bcachefs_metadata_version_snapshot_skiplists ||
|
||||
fsck_err(c, "snapshot with bad skiplist field:\n %s",
|
||||
(bch2_bkey_val_to_text(&buf, c, k), buf.buf)))) {
|
||||
u = bch2_bkey_make_mut_typed(trans, iter, &k, 0, snapshot);
|
||||
ret = PTR_ERR_OR_ZERO(u);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(u->v.skip); i++)
|
||||
u->v.skip[i] = cpu_to_le32(bch2_snapshot_skiplist_get(c, parent_id));
|
||||
|
||||
bubble_sort(u->v.skip, ARRAY_SIZE(u->v.skip), cmp_le32);
|
||||
s = u->v;
|
||||
}
|
||||
ret = 0;
|
||||
err:
|
||||
fsck_err:
|
||||
printbuf_exit(&buf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int bch2_check_snapshots(struct bch_fs *c)
|
||||
{
|
||||
struct btree_iter iter;
|
||||
struct bkey_s_c k;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* We iterate backwards as checking/fixing the depth field requires that
|
||||
* the parent's depth already be correct:
|
||||
*/
|
||||
ret = bch2_trans_run(c,
|
||||
for_each_btree_key_reverse_commit(&trans, iter,
|
||||
BTREE_ID_snapshots, POS_MAX,
|
||||
BTREE_ITER_PREFETCH, k,
|
||||
NULL, NULL, BTREE_INSERT_LAZY_RW|BTREE_INSERT_NOFAIL,
|
||||
check_snapshot(&trans, &iter, k)));
|
||||
if (ret)
|
||||
bch_err_fn(c, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int bch2_snapshots_read(struct bch_fs *c)
|
||||
{
|
||||
struct btree_iter iter;
|
||||
struct bkey_s_c k;
|
||||
int ret = 0;
|
||||
|
||||
ret = bch2_trans_run(c,
|
||||
for_each_btree_key2(&trans, iter, BTREE_ID_snapshots,
|
||||
POS_MIN, 0, k,
|
||||
bch2_mark_snapshot(&trans, BTREE_ID_snapshots, 0, bkey_s_c_null, k, 0) ?:
|
||||
bch2_snapshot_set_equiv(&trans, k)));
|
||||
if (ret)
|
||||
bch_err_fn(c, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void bch2_fs_snapshots_exit(struct bch_fs *c)
|
||||
{
|
||||
kfree(rcu_dereference_protected(c->snapshots, true));
|
||||
}
|
250
libbcachefs/snapshot.h
Normal file
250
libbcachefs/snapshot.h
Normal file
@ -0,0 +1,250 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
#ifndef _BCACHEFS_SNAPSHOT_H
|
||||
#define _BCACHEFS_SNAPSHOT_H
|
||||
|
||||
enum bkey_invalid_flags;
|
||||
|
||||
void bch2_snapshot_tree_to_text(struct printbuf *, struct bch_fs *, struct bkey_s_c);
|
||||
int bch2_snapshot_tree_invalid(const struct bch_fs *, struct bkey_s_c,
|
||||
enum bkey_invalid_flags, struct printbuf *);
|
||||
|
||||
#define bch2_bkey_ops_snapshot_tree ((struct bkey_ops) { \
|
||||
.key_invalid = bch2_snapshot_tree_invalid, \
|
||||
.val_to_text = bch2_snapshot_tree_to_text, \
|
||||
.min_val_size = 8, \
|
||||
})
|
||||
|
||||
struct bkey_i_snapshot_tree *__bch2_snapshot_tree_create(struct btree_trans *);
|
||||
|
||||
int bch2_snapshot_tree_lookup(struct btree_trans *, u32, struct bch_snapshot_tree *);
|
||||
|
||||
void bch2_snapshot_to_text(struct printbuf *, struct bch_fs *, struct bkey_s_c);
|
||||
int bch2_snapshot_invalid(const struct bch_fs *, struct bkey_s_c,
|
||||
enum bkey_invalid_flags, struct printbuf *);
|
||||
int bch2_mark_snapshot(struct btree_trans *, enum btree_id, unsigned,
|
||||
struct bkey_s_c, struct bkey_s_c, unsigned);
|
||||
|
||||
#define bch2_bkey_ops_snapshot ((struct bkey_ops) { \
|
||||
.key_invalid = bch2_snapshot_invalid, \
|
||||
.val_to_text = bch2_snapshot_to_text, \
|
||||
.atomic_trigger = bch2_mark_snapshot, \
|
||||
.min_val_size = 24, \
|
||||
})
|
||||
|
||||
static inline struct snapshot_t *__snapshot_t(struct snapshot_table *t, u32 id)
|
||||
{
|
||||
return &t->s[U32_MAX - id];
|
||||
}
|
||||
|
||||
static inline const struct snapshot_t *snapshot_t(struct bch_fs *c, u32 id)
|
||||
{
|
||||
return __snapshot_t(rcu_dereference(c->snapshots), id);
|
||||
}
|
||||
|
||||
static inline u32 bch2_snapshot_tree(struct bch_fs *c, u32 id)
|
||||
{
|
||||
rcu_read_lock();
|
||||
id = snapshot_t(c, id)->tree;
|
||||
rcu_read_unlock();
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
static inline u32 __bch2_snapshot_parent_early(struct bch_fs *c, u32 id)
|
||||
{
|
||||
return snapshot_t(c, id)->parent;
|
||||
}
|
||||
|
||||
static inline u32 bch2_snapshot_parent_early(struct bch_fs *c, u32 id)
|
||||
{
|
||||
rcu_read_lock();
|
||||
id = __bch2_snapshot_parent_early(c, id);
|
||||
rcu_read_unlock();
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
static inline u32 __bch2_snapshot_parent(struct bch_fs *c, u32 id)
|
||||
{
|
||||
#ifdef CONFIG_BCACHEFS_DEBUG
|
||||
u32 parent = snapshot_t(c, id)->parent;
|
||||
|
||||
if (parent &&
|
||||
snapshot_t(c, id)->depth != snapshot_t(c, parent)->depth + 1)
|
||||
panic("id %u depth=%u parent %u depth=%u\n",
|
||||
id, snapshot_t(c, id)->depth,
|
||||
parent, snapshot_t(c, parent)->depth);
|
||||
|
||||
return parent;
|
||||
#else
|
||||
return snapshot_t(c, id)->parent;
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline u32 bch2_snapshot_parent(struct bch_fs *c, u32 id)
|
||||
{
|
||||
rcu_read_lock();
|
||||
id = __bch2_snapshot_parent(c, id);
|
||||
rcu_read_unlock();
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
static inline u32 bch2_snapshot_nth_parent(struct bch_fs *c, u32 id, u32 n)
|
||||
{
|
||||
rcu_read_lock();
|
||||
while (n--)
|
||||
id = __bch2_snapshot_parent(c, id);
|
||||
rcu_read_unlock();
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
u32 bch2_snapshot_skiplist_get(struct bch_fs *, u32);
|
||||
|
||||
static inline u32 bch2_snapshot_root(struct bch_fs *c, u32 id)
|
||||
{
|
||||
u32 parent;
|
||||
|
||||
rcu_read_lock();
|
||||
while ((parent = __bch2_snapshot_parent(c, id)))
|
||||
id = parent;
|
||||
rcu_read_unlock();
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
static inline u32 __bch2_snapshot_equiv(struct bch_fs *c, u32 id)
|
||||
{
|
||||
return snapshot_t(c, id)->equiv;
|
||||
}
|
||||
|
||||
static inline u32 bch2_snapshot_equiv(struct bch_fs *c, u32 id)
|
||||
{
|
||||
rcu_read_lock();
|
||||
id = __bch2_snapshot_equiv(c, id);
|
||||
rcu_read_unlock();
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
static inline bool bch2_snapshot_is_equiv(struct bch_fs *c, u32 id)
|
||||
{
|
||||
return id == bch2_snapshot_equiv(c, id);
|
||||
}
|
||||
|
||||
static inline bool bch2_snapshot_is_internal_node(struct bch_fs *c, u32 id)
|
||||
{
|
||||
const struct snapshot_t *s;
|
||||
bool ret;
|
||||
|
||||
rcu_read_lock();
|
||||
s = snapshot_t(c, id);
|
||||
ret = s->children[0];
|
||||
rcu_read_unlock();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline u32 bch2_snapshot_is_leaf(struct bch_fs *c, u32 id)
|
||||
{
|
||||
return !bch2_snapshot_is_internal_node(c, id);
|
||||
}
|
||||
|
||||
static inline u32 bch2_snapshot_sibling(struct bch_fs *c, u32 id)
|
||||
{
|
||||
const struct snapshot_t *s;
|
||||
u32 parent = __bch2_snapshot_parent(c, id);
|
||||
|
||||
if (!parent)
|
||||
return 0;
|
||||
|
||||
s = snapshot_t(c, __bch2_snapshot_parent(c, id));
|
||||
if (id == s->children[0])
|
||||
return s->children[1];
|
||||
if (id == s->children[1])
|
||||
return s->children[0];
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline u32 bch2_snapshot_depth(struct bch_fs *c, u32 parent)
|
||||
{
|
||||
u32 depth;
|
||||
|
||||
rcu_read_lock();
|
||||
depth = parent ? snapshot_t(c, parent)->depth + 1 : 0;
|
||||
rcu_read_unlock();
|
||||
|
||||
return depth;
|
||||
}
|
||||
|
||||
bool __bch2_snapshot_is_ancestor(struct bch_fs *, u32, u32);
|
||||
|
||||
static inline bool bch2_snapshot_is_ancestor(struct bch_fs *c, u32 id, u32 ancestor)
|
||||
{
|
||||
return id == ancestor
|
||||
? true
|
||||
: __bch2_snapshot_is_ancestor(c, id, ancestor);
|
||||
}
|
||||
|
||||
static inline bool bch2_snapshot_has_children(struct bch_fs *c, u32 id)
|
||||
{
|
||||
const struct snapshot_t *t;
|
||||
bool ret;
|
||||
|
||||
rcu_read_lock();
|
||||
t = snapshot_t(c, id);
|
||||
ret = (t->children[0]|t->children[1]) != 0;
|
||||
rcu_read_unlock();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline bool snapshot_list_has_id(snapshot_id_list *s, u32 id)
|
||||
{
|
||||
u32 *i;
|
||||
|
||||
darray_for_each(*s, i)
|
||||
if (*i == id)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline bool snapshot_list_has_ancestor(struct bch_fs *c, snapshot_id_list *s, u32 id)
|
||||
{
|
||||
u32 *i;
|
||||
|
||||
darray_for_each(*s, i)
|
||||
if (bch2_snapshot_is_ancestor(c, id, *i))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline int snapshot_list_add(struct bch_fs *c, snapshot_id_list *s, u32 id)
|
||||
{
|
||||
int ret;
|
||||
|
||||
BUG_ON(snapshot_list_has_id(s, id));
|
||||
ret = darray_push(s, id);
|
||||
if (ret)
|
||||
bch_err(c, "error reallocating snapshot_id_list (size %zu)", s->size);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int bch2_snapshot_lookup(struct btree_trans *trans, u32 id,
|
||||
struct bch_snapshot *s);
|
||||
int bch2_snapshot_get_subvol(struct btree_trans *, u32,
|
||||
struct bch_subvolume *);
|
||||
int bch2_snapshot_live(struct btree_trans *trans, u32 id);
|
||||
int bch2_snapshot_set_equiv(struct btree_trans *trans, struct bkey_s_c k);
|
||||
|
||||
/* only exported for tests: */
|
||||
int bch2_snapshot_node_create(struct btree_trans *, u32,
|
||||
u32 *, u32 *, unsigned);
|
||||
|
||||
int bch2_check_snapshot_trees(struct bch_fs *);
|
||||
int bch2_check_snapshots(struct bch_fs *);
|
||||
int bch2_snapshots_read(struct bch_fs *);
|
||||
void bch2_fs_snapshots_exit(struct bch_fs *);
|
||||
|
||||
#endif /* _BCACHEFS_SNAPSHOT_H */
|
File diff suppressed because it is too large
Load Diff
@ -7,225 +7,8 @@
|
||||
|
||||
enum bkey_invalid_flags;
|
||||
|
||||
void bch2_snapshot_tree_to_text(struct printbuf *, struct bch_fs *, struct bkey_s_c);
|
||||
int bch2_snapshot_tree_invalid(const struct bch_fs *, struct bkey_s_c,
|
||||
enum bkey_invalid_flags, struct printbuf *);
|
||||
|
||||
#define bch2_bkey_ops_snapshot_tree ((struct bkey_ops) { \
|
||||
.key_invalid = bch2_snapshot_tree_invalid, \
|
||||
.val_to_text = bch2_snapshot_tree_to_text, \
|
||||
.min_val_size = 8, \
|
||||
})
|
||||
|
||||
int bch2_snapshot_tree_lookup(struct btree_trans *, u32, struct bch_snapshot_tree *);
|
||||
|
||||
void bch2_snapshot_to_text(struct printbuf *, struct bch_fs *, struct bkey_s_c);
|
||||
int bch2_snapshot_invalid(const struct bch_fs *, struct bkey_s_c,
|
||||
enum bkey_invalid_flags, struct printbuf *);
|
||||
int bch2_mark_snapshot(struct btree_trans *, enum btree_id, unsigned,
|
||||
struct bkey_s_c, struct bkey_s_c, unsigned);
|
||||
|
||||
#define bch2_bkey_ops_snapshot ((struct bkey_ops) { \
|
||||
.key_invalid = bch2_snapshot_invalid, \
|
||||
.val_to_text = bch2_snapshot_to_text, \
|
||||
.atomic_trigger = bch2_mark_snapshot, \
|
||||
.min_val_size = 24, \
|
||||
})
|
||||
|
||||
static inline struct snapshot_t *__snapshot_t(struct snapshot_table *t, u32 id)
|
||||
{
|
||||
return &t->s[U32_MAX - id];
|
||||
}
|
||||
|
||||
static inline const struct snapshot_t *snapshot_t(struct bch_fs *c, u32 id)
|
||||
{
|
||||
return __snapshot_t(rcu_dereference(c->snapshots), id);
|
||||
}
|
||||
|
||||
static inline u32 bch2_snapshot_tree(struct bch_fs *c, u32 id)
|
||||
{
|
||||
rcu_read_lock();
|
||||
id = snapshot_t(c, id)->tree;
|
||||
rcu_read_unlock();
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
static inline u32 __bch2_snapshot_parent_early(struct bch_fs *c, u32 id)
|
||||
{
|
||||
return snapshot_t(c, id)->parent;
|
||||
}
|
||||
|
||||
static inline u32 bch2_snapshot_parent_early(struct bch_fs *c, u32 id)
|
||||
{
|
||||
rcu_read_lock();
|
||||
id = __bch2_snapshot_parent_early(c, id);
|
||||
rcu_read_unlock();
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
static inline u32 __bch2_snapshot_parent(struct bch_fs *c, u32 id)
|
||||
{
|
||||
#ifdef CONFIG_BCACHEFS_DEBUG
|
||||
u32 parent = snapshot_t(c, id)->parent;
|
||||
|
||||
if (parent &&
|
||||
snapshot_t(c, id)->depth != snapshot_t(c, parent)->depth + 1)
|
||||
panic("id %u depth=%u parent %u depth=%u\n",
|
||||
id, snapshot_t(c, id)->depth,
|
||||
parent, snapshot_t(c, parent)->depth);
|
||||
|
||||
return parent;
|
||||
#else
|
||||
return snapshot_t(c, id)->parent;
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline u32 bch2_snapshot_parent(struct bch_fs *c, u32 id)
|
||||
{
|
||||
rcu_read_lock();
|
||||
id = __bch2_snapshot_parent(c, id);
|
||||
rcu_read_unlock();
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
static inline u32 bch2_snapshot_nth_parent(struct bch_fs *c, u32 id, u32 n)
|
||||
{
|
||||
rcu_read_lock();
|
||||
while (n--)
|
||||
id = __bch2_snapshot_parent(c, id);
|
||||
rcu_read_unlock();
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
static inline u32 bch2_snapshot_root(struct bch_fs *c, u32 id)
|
||||
{
|
||||
u32 parent;
|
||||
|
||||
rcu_read_lock();
|
||||
while ((parent = __bch2_snapshot_parent(c, id)))
|
||||
id = parent;
|
||||
rcu_read_unlock();
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
static inline u32 __bch2_snapshot_equiv(struct bch_fs *c, u32 id)
|
||||
{
|
||||
return snapshot_t(c, id)->equiv;
|
||||
}
|
||||
|
||||
static inline u32 bch2_snapshot_equiv(struct bch_fs *c, u32 id)
|
||||
{
|
||||
rcu_read_lock();
|
||||
id = __bch2_snapshot_equiv(c, id);
|
||||
rcu_read_unlock();
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
static inline bool bch2_snapshot_is_equiv(struct bch_fs *c, u32 id)
|
||||
{
|
||||
return id == bch2_snapshot_equiv(c, id);
|
||||
}
|
||||
|
||||
static inline bool bch2_snapshot_is_internal_node(struct bch_fs *c, u32 id)
|
||||
{
|
||||
const struct snapshot_t *s;
|
||||
bool ret;
|
||||
|
||||
rcu_read_lock();
|
||||
s = snapshot_t(c, id);
|
||||
ret = s->children[0];
|
||||
rcu_read_unlock();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline u32 bch2_snapshot_is_leaf(struct bch_fs *c, u32 id)
|
||||
{
|
||||
return !bch2_snapshot_is_internal_node(c, id);
|
||||
}
|
||||
|
||||
static inline u32 bch2_snapshot_sibling(struct bch_fs *c, u32 id)
|
||||
{
|
||||
const struct snapshot_t *s;
|
||||
u32 parent = __bch2_snapshot_parent(c, id);
|
||||
|
||||
if (!parent)
|
||||
return 0;
|
||||
|
||||
s = snapshot_t(c, __bch2_snapshot_parent(c, id));
|
||||
if (id == s->children[0])
|
||||
return s->children[1];
|
||||
if (id == s->children[1])
|
||||
return s->children[0];
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool __bch2_snapshot_is_ancestor(struct bch_fs *, u32, u32);
|
||||
|
||||
static inline bool bch2_snapshot_is_ancestor(struct bch_fs *c, u32 id, u32 ancestor)
|
||||
{
|
||||
return id == ancestor
|
||||
? true
|
||||
: __bch2_snapshot_is_ancestor(c, id, ancestor);
|
||||
}
|
||||
|
||||
static inline bool bch2_snapshot_has_children(struct bch_fs *c, u32 id)
|
||||
{
|
||||
const struct snapshot_t *t;
|
||||
bool ret;
|
||||
|
||||
rcu_read_lock();
|
||||
t = snapshot_t(c, id);
|
||||
ret = (t->children[0]|t->children[1]) != 0;
|
||||
rcu_read_unlock();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline bool snapshot_list_has_id(snapshot_id_list *s, u32 id)
|
||||
{
|
||||
u32 *i;
|
||||
|
||||
darray_for_each(*s, i)
|
||||
if (*i == id)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline bool snapshot_list_has_ancestor(struct bch_fs *c, snapshot_id_list *s, u32 id)
|
||||
{
|
||||
u32 *i;
|
||||
|
||||
darray_for_each(*s, i)
|
||||
if (bch2_snapshot_is_ancestor(c, id, *i))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline int snapshot_list_add(struct bch_fs *c, snapshot_id_list *s, u32 id)
|
||||
{
|
||||
int ret;
|
||||
|
||||
BUG_ON(snapshot_list_has_id(s, id));
|
||||
ret = darray_push(s, id);
|
||||
if (ret)
|
||||
bch_err(c, "error reallocating snapshot_id_list (size %zu)", s->size);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int bch2_check_snapshot_trees(struct bch_fs *);
|
||||
int bch2_check_snapshots(struct bch_fs *);
|
||||
int bch2_check_subvols(struct bch_fs *);
|
||||
|
||||
void bch2_fs_snapshots_exit(struct bch_fs *);
|
||||
int bch2_snapshots_read(struct bch_fs *);
|
||||
|
||||
int bch2_subvolume_invalid(const struct bch_fs *, struct bkey_s_c,
|
||||
unsigned, struct printbuf *);
|
||||
void bch2_subvolume_to_text(struct printbuf *, struct bch_fs *, struct bkey_s_c);
|
||||
@ -238,14 +21,8 @@ void bch2_subvolume_to_text(struct printbuf *, struct bch_fs *, struct bkey_s_c)
|
||||
|
||||
int bch2_subvolume_get(struct btree_trans *, unsigned,
|
||||
bool, int, struct bch_subvolume *);
|
||||
int bch2_snapshot_get_subvol(struct btree_trans *, u32,
|
||||
struct bch_subvolume *);
|
||||
int bch2_subvolume_get_snapshot(struct btree_trans *, u32, u32 *);
|
||||
|
||||
/* only exported for tests: */
|
||||
int bch2_snapshot_node_create(struct btree_trans *, u32,
|
||||
u32 *, u32 *, unsigned);
|
||||
|
||||
int bch2_delete_dead_snapshots(struct bch_fs *);
|
||||
void bch2_delete_dead_snapshots_async(struct bch_fs *);
|
||||
|
||||
|
@ -48,6 +48,7 @@
|
||||
#include "recovery.h"
|
||||
#include "replicas.h"
|
||||
#include "sb-clean.h"
|
||||
#include "snapshot.h"
|
||||
#include "subvolume.h"
|
||||
#include "super.h"
|
||||
#include "super-io.h"
|
||||
|
@ -4,7 +4,7 @@
|
||||
#include "bcachefs.h"
|
||||
#include "btree_update.h"
|
||||
#include "journal_reclaim.h"
|
||||
#include "subvolume.h"
|
||||
#include "snapshot.h"
|
||||
#include "tests.h"
|
||||
|
||||
#include "linux/kthread.h"
|
||||
|
@ -8,9 +8,9 @@
|
||||
#include "btree_update_interior.h"
|
||||
#include "keylist.h"
|
||||
#include "opts.h"
|
||||
#include "six.h"
|
||||
|
||||
#include <linux/blktrace_api.h>
|
||||
#include <linux/six.h>
|
||||
|
||||
#define CREATE_TRACE_POINTS
|
||||
#include "trace.h"
|
||||
|
@ -269,6 +269,7 @@ void bch2_print_string_as_lines(const char *prefix, const char *lines)
|
||||
|
||||
int bch2_save_backtrace(bch_stacktrace *stack, struct task_struct *task)
|
||||
{
|
||||
#ifdef CONFIG_STACKTRACE
|
||||
unsigned nr_entries = 0;
|
||||
int ret = 0;
|
||||
|
||||
@ -289,6 +290,9 @@ int bch2_save_backtrace(bch_stacktrace *stack, struct task_struct *task)
|
||||
up_read(&task->signal->exec_update_lock);
|
||||
|
||||
return ret;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
void bch2_prt_backtrace(struct printbuf *out, bch_stacktrace *stack)
|
||||
|
@ -845,6 +845,11 @@ static inline int u8_cmp(u8 l, u8 r)
|
||||
return cmp_int(l, r);
|
||||
}
|
||||
|
||||
static inline int cmp_le32(__le32 l, __le32 r)
|
||||
{
|
||||
return cmp_int(le32_to_cpu(l), le32_to_cpu(r));
|
||||
}
|
||||
|
||||
#include <linux/uuid.h>
|
||||
|
||||
#endif /* _BCACHEFS_UTIL_H */
|
||||
|
@ -494,7 +494,8 @@ struct inode_opt_set {
|
||||
bool defined;
|
||||
};
|
||||
|
||||
static int inode_opt_set_fn(struct bch_inode_info *inode,
|
||||
static int inode_opt_set_fn(struct btree_trans *trans,
|
||||
struct bch_inode_info *inode,
|
||||
struct bch_inode_unpacked *bi,
|
||||
void *p)
|
||||
{
|
||||
|
@ -17,8 +17,9 @@ static inline void closure_put_after_sub(struct closure *cl, int flags)
|
||||
{
|
||||
int r = flags & CLOSURE_REMAINING_MASK;
|
||||
|
||||
BUG_ON(flags & CLOSURE_GUARD_MASK);
|
||||
BUG_ON(!r && (flags & ~CLOSURE_DESTRUCTOR));
|
||||
if ((flags & CLOSURE_GUARD_MASK) ||
|
||||
(!r && (flags & ~CLOSURE_DESTRUCTOR)))
|
||||
panic("closure_put_after_sub: bogus flags %x remaining %i", flags, r);
|
||||
|
||||
if (!r) {
|
||||
if (cl->fn && !(flags & CLOSURE_DESTRUCTOR)) {
|
||||
|
Loading…
Reference in New Issue
Block a user