mirror of
https://github.com/koverstreet/bcachefs-tools.git
synced 2025-03-28 00:00:03 +03:00
Update bcachefs sources to d5e561b3cc bcachefs: BCH_DATA ioctl
This commit is contained in:
parent
a9f5937a97
commit
6976570d67
@ -1 +1 @@
|
||||
496cbe9474173ec41bf221dc8ab1f5d70a128c3b
|
||||
d5e561b3cc023dd247d2b3d08b680709ec21b477
|
||||
|
@ -257,6 +257,8 @@ static inline void bio_flush_dcache_pages(struct bio *bi)
|
||||
extern void bio_copy_data_iter(struct bio *dst, struct bvec_iter *dst_iter,
|
||||
struct bio *src, struct bvec_iter *src_iter);
|
||||
extern void bio_copy_data(struct bio *dst, struct bio *src);
|
||||
|
||||
void bio_free_pages(struct bio *bio);
|
||||
extern int bio_alloc_pages(struct bio *bio, gfp_t gfp);
|
||||
|
||||
void zero_fill_bio_iter(struct bio *bio, struct bvec_iter iter);
|
||||
|
@ -37,4 +37,6 @@ static inline void reinit_completion(struct completion *x)
|
||||
void complete(struct completion *);
|
||||
void wait_for_completion(struct completion *);
|
||||
|
||||
#define wait_for_completion_interruptible(x) (wait_for_completion(x), 0)
|
||||
|
||||
#endif
|
||||
|
@ -29,6 +29,11 @@ static inline void get_random_bytes(void *buf, int nbytes)
|
||||
BUG_ON(getrandom(buf, nbytes, 0) != nbytes);
|
||||
}
|
||||
|
||||
static inline void prandom_bytes(void *buf, int nbytes)
|
||||
{
|
||||
return get_random_bytes(buf, nbytes);
|
||||
}
|
||||
|
||||
#define get_random_type(type) \
|
||||
static inline type get_random_##type(void) \
|
||||
{ \
|
||||
|
@ -459,6 +459,11 @@ static void bch2_sb_print_quota(struct bch_sb *sb, struct bch_sb_field *f,
|
||||
{
|
||||
}
|
||||
|
||||
static void bch2_sb_print_disk_groups(struct bch_sb *sb, struct bch_sb_field *f,
|
||||
enum units units)
|
||||
{
|
||||
}
|
||||
|
||||
typedef void (*sb_field_print_fn)(struct bch_sb *, struct bch_sb_field *, enum units);
|
||||
|
||||
struct bch_sb_field_ops {
|
||||
|
@ -496,6 +496,8 @@ struct bch_fs {
|
||||
struct bch_replicas_cpu __rcu *replicas_gc;
|
||||
struct mutex replicas_gc_lock;
|
||||
|
||||
struct bch_disk_groups_cpu __rcu *disk_groups;
|
||||
|
||||
struct bch_opts opts;
|
||||
|
||||
/* Updated by bch2_sb_update():*/
|
||||
|
@ -782,7 +782,8 @@ struct bch_sb_field {
|
||||
x(members, 1) \
|
||||
x(crypt, 2) \
|
||||
x(replicas, 3) \
|
||||
x(quota, 4)
|
||||
x(quota, 4) \
|
||||
x(disk_groups, 5)
|
||||
|
||||
enum bch_sb_field_type {
|
||||
#define x(f, nr) BCH_SB_FIELD_##f = nr,
|
||||
@ -815,8 +816,9 @@ LE64_BITMASK(BCH_MEMBER_STATE, struct bch_member, flags[0], 0, 4)
|
||||
LE64_BITMASK(BCH_MEMBER_TIER, struct bch_member, flags[0], 4, 8)
|
||||
/* 8-10 unused, was HAS_(META)DATA */
|
||||
LE64_BITMASK(BCH_MEMBER_REPLACEMENT, struct bch_member, flags[0], 10, 14)
|
||||
LE64_BITMASK(BCH_MEMBER_DISCARD, struct bch_member, flags[0], 14, 15);
|
||||
LE64_BITMASK(BCH_MEMBER_DATA_ALLOWED, struct bch_member, flags[0], 15, 20);
|
||||
LE64_BITMASK(BCH_MEMBER_DISCARD, struct bch_member, flags[0], 14, 15)
|
||||
LE64_BITMASK(BCH_MEMBER_DATA_ALLOWED, struct bch_member, flags[0], 15, 20)
|
||||
LE64_BITMASK(BCH_MEMBER_GROUP, struct bch_member, flags[0], 20, 28)
|
||||
|
||||
#if 0
|
||||
LE64_BITMASK(BCH_MEMBER_NR_READ_ERRORS, struct bch_member, flags[1], 0, 20);
|
||||
@ -933,6 +935,23 @@ struct bch_sb_field_quota {
|
||||
struct bch_sb_quota_type q[QTYP_NR];
|
||||
} __attribute__((packed, aligned(8)));
|
||||
|
||||
/* BCH_SB_FIELD_disk_groups: */
|
||||
|
||||
#define BCH_SB_LABEL_SIZE 32
|
||||
|
||||
struct bch_disk_group {
|
||||
__u8 label[BCH_SB_LABEL_SIZE];
|
||||
__le64 flags[2];
|
||||
};
|
||||
|
||||
LE64_BITMASK(BCH_GROUP_DELETED, struct bch_disk_group, flags[0], 0, 1)
|
||||
LE64_BITMASK(BCH_GROUP_DATA_ALLOWED, struct bch_disk_group, flags[0], 1, 6)
|
||||
|
||||
struct bch_sb_field_disk_groups {
|
||||
struct bch_sb_field field;
|
||||
struct bch_disk_group entries[0];
|
||||
};
|
||||
|
||||
/* Superblock: */
|
||||
|
||||
/*
|
||||
@ -947,7 +966,6 @@ struct bch_sb_field_quota {
|
||||
#define BCH_SB_VERSION_MAX 9
|
||||
|
||||
#define BCH_SB_SECTOR 8
|
||||
#define BCH_SB_LABEL_SIZE 32
|
||||
#define BCH_SB_MEMBERS_MAX 64 /* XXX kill */
|
||||
|
||||
struct bch_sb_layout {
|
||||
@ -1069,20 +1087,6 @@ enum bch_sb_features {
|
||||
|
||||
#define BCH_REPLICAS_MAX 4U
|
||||
|
||||
#if 0
|
||||
#define BCH_ERROR_ACTIONS() \
|
||||
x(BCH_ON_ERROR_CONTINUE, 0, "continue") \
|
||||
x(BCH_ON_ERROR_RO, 1, "remount-ro") \
|
||||
x(BCH_ON_ERROR_PANIC, 2, "panic") \
|
||||
x(BCH_NR_ERROR_ACTIONS, 3, NULL)
|
||||
|
||||
enum bch_error_actions {
|
||||
#define x(_opt, _nr, _str) _opt = _nr,
|
||||
BCH_ERROR_ACTIONS()
|
||||
#undef x
|
||||
};
|
||||
#endif
|
||||
|
||||
enum bch_error_actions {
|
||||
BCH_ON_ERROR_CONTINUE = 0,
|
||||
BCH_ON_ERROR_RO = 1,
|
||||
|
@ -46,7 +46,6 @@ struct bch_ioctl_incremental {
|
||||
#define BCH_IOCTL_DISK_ONLINE _IOW(0xbc, 6, struct bch_ioctl_disk)
|
||||
#define BCH_IOCTL_DISK_OFFLINE _IOW(0xbc, 7, struct bch_ioctl_disk)
|
||||
#define BCH_IOCTL_DISK_SET_STATE _IOW(0xbc, 8, struct bch_ioctl_disk_set_state)
|
||||
#define BCH_IOCTL_DISK_EVACUATE _IOW(0xbc, 9, struct bch_ioctl_disk)
|
||||
#define BCH_IOCTL_DATA _IOW(0xbc, 10, struct bch_ioctl_data)
|
||||
#define BCH_IOCTL_USAGE _IOWR(0xbc, 11, struct bch_ioctl_usage)
|
||||
#define BCH_IOCTL_READ_SUPER _IOW(0xbc, 12, struct bch_ioctl_read_super)
|
||||
@ -75,30 +74,37 @@ struct bch_ioctl_disk_set_state {
|
||||
__u64 dev;
|
||||
};
|
||||
|
||||
#define BCH_REWRITE_INCREASE_REPLICAS (1 << 0)
|
||||
#define BCH_REWRITE_DECREASE_REPLICAS (1 << 1)
|
||||
|
||||
#define BCH_REWRITE_RECOMPRESS (1 << 0)
|
||||
#define BCH_REWRITE_DECREASE_REPLICAS (1 << 1)
|
||||
|
||||
enum bch_data_ops {
|
||||
BCH_DATA_SCRUB,
|
||||
};
|
||||
|
||||
struct bch_data_op {
|
||||
__u8 type;
|
||||
BCH_DATA_OP_SCRUB = 0,
|
||||
BCH_DATA_OP_REREPLICATE = 1,
|
||||
BCH_DATA_OP_MIGRATE = 2,
|
||||
BCH_DATA_OP_NR = 3,
|
||||
};
|
||||
|
||||
struct bch_ioctl_data {
|
||||
__u32 op;
|
||||
__u32 flags;
|
||||
__u32 pad;
|
||||
|
||||
__u64 start_inode;
|
||||
__u64 start_offset;
|
||||
struct bpos start;
|
||||
struct bpos end;
|
||||
|
||||
__u64 end_inode;
|
||||
__u64 end_offset;
|
||||
};
|
||||
union {
|
||||
struct {
|
||||
__u32 dev;
|
||||
__u32 pad;
|
||||
} migrate;
|
||||
};
|
||||
} __attribute__((packed, aligned(8)));
|
||||
|
||||
struct bch_ioctl_data_progress {
|
||||
__u8 data_type;
|
||||
__u8 btree_id;
|
||||
__u8 pad[2];
|
||||
struct bpos pos;
|
||||
|
||||
__u64 sectors_done;
|
||||
__u64 sectors_total;
|
||||
} __attribute__((packed, aligned(8)));
|
||||
|
||||
struct bch_ioctl_dev_usage {
|
||||
__u8 state;
|
||||
|
@ -1,20 +1,25 @@
|
||||
#ifndef NO_BCACHEFS_CHARDEV
|
||||
|
||||
#include "bcachefs.h"
|
||||
#include "alloc.h"
|
||||
#include "bcachefs_ioctl.h"
|
||||
#include "buckets.h"
|
||||
#include "chardev.h"
|
||||
#include "move.h"
|
||||
#include "super.h"
|
||||
#include "super-io.h"
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/major.h>
|
||||
#include <linux/anon_inodes.h>
|
||||
#include <linux/cdev.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/file.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/ioctl.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/kthread.h>
|
||||
#include <linux/major.h>
|
||||
#include <linux/sched/task.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/uaccess.h>
|
||||
|
||||
/* returns with ref on ca->ref */
|
||||
static struct bch_dev *bch2_device_lookup(struct bch_fs *c, u64 dev,
|
||||
@ -266,23 +271,108 @@ static long bch2_ioctl_disk_set_state(struct bch_fs *c,
|
||||
return ret;
|
||||
}
|
||||
|
||||
static long bch2_ioctl_disk_evacuate(struct bch_fs *c,
|
||||
struct bch_ioctl_disk arg)
|
||||
{
|
||||
struct bch_dev *ca;
|
||||
int ret;
|
||||
struct bch_data_ctx {
|
||||
struct bch_fs *c;
|
||||
struct bch_ioctl_data arg;
|
||||
struct bch_move_stats stats;
|
||||
|
||||
if ((arg.flags & ~BCH_BY_INDEX) ||
|
||||
arg.pad)
|
||||
int ret;
|
||||
|
||||
struct task_struct *thread;
|
||||
};
|
||||
|
||||
static int bch2_data_thread(void *arg)
|
||||
{
|
||||
struct bch_data_ctx *ctx = arg;
|
||||
|
||||
ctx->ret = bch2_data_job(ctx->c, &ctx->stats, ctx->arg);
|
||||
|
||||
ctx->stats.data_type = U8_MAX;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bch2_data_job_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct bch_data_ctx *ctx = file->private_data;
|
||||
|
||||
kthread_stop(ctx->thread);
|
||||
put_task_struct(ctx->thread);
|
||||
kfree(ctx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t bch2_data_job_read(struct file *file, char __user *buf,
|
||||
size_t len, loff_t *ppos)
|
||||
{
|
||||
struct bch_data_ctx *ctx = file->private_data;
|
||||
struct bch_fs *c = ctx->c;
|
||||
struct bch_ioctl_data_progress p = {
|
||||
.data_type = ctx->stats.data_type,
|
||||
.btree_id = ctx->stats.iter.btree_id,
|
||||
.pos = ctx->stats.iter.pos,
|
||||
.sectors_done = atomic64_read(&ctx->stats.sectors_seen),
|
||||
.sectors_total = bch2_fs_sectors_used(c, bch2_fs_usage_read(c)),
|
||||
};
|
||||
|
||||
if (len != sizeof(p))
|
||||
return -EINVAL;
|
||||
|
||||
ca = bch2_device_lookup(c, arg.dev, arg.flags);
|
||||
if (IS_ERR(ca))
|
||||
return PTR_ERR(ca);
|
||||
return copy_to_user(buf, &p, sizeof(p)) ?: sizeof(p);
|
||||
}
|
||||
|
||||
ret = bch2_dev_evacuate(c, ca);
|
||||
static const struct file_operations bcachefs_data_ops = {
|
||||
.release = bch2_data_job_release,
|
||||
.read = bch2_data_job_read,
|
||||
.llseek = no_llseek,
|
||||
};
|
||||
|
||||
percpu_ref_put(&ca->ref);
|
||||
static long bch2_ioctl_data(struct bch_fs *c,
|
||||
struct bch_ioctl_data arg)
|
||||
{
|
||||
struct bch_data_ctx *ctx = NULL;
|
||||
struct file *file = NULL;
|
||||
unsigned flags = O_RDONLY|O_CLOEXEC|O_NONBLOCK;
|
||||
int ret, fd = -1;
|
||||
|
||||
if (arg.op >= BCH_DATA_OP_NR || arg.flags)
|
||||
return -EINVAL;
|
||||
|
||||
ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
|
||||
if (!ctx)
|
||||
return -ENOMEM;
|
||||
|
||||
ctx->c = c;
|
||||
ctx->arg = arg;
|
||||
|
||||
ctx->thread = kthread_create(bch2_data_thread, ctx, "[bcachefs]");
|
||||
if (IS_ERR(ctx->thread)) {
|
||||
ret = PTR_ERR(ctx->thread);
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = get_unused_fd_flags(flags);
|
||||
if (ret < 0)
|
||||
goto err;
|
||||
fd = ret;
|
||||
|
||||
file = anon_inode_getfile("[bcachefs]", &bcachefs_data_ops, ctx, flags);
|
||||
if (IS_ERR(file)) {
|
||||
ret = PTR_ERR(file);
|
||||
goto err;
|
||||
}
|
||||
|
||||
fd_install(fd, file);
|
||||
|
||||
get_task_struct(ctx->thread);
|
||||
wake_up_process(ctx->thread);
|
||||
|
||||
return fd;
|
||||
err:
|
||||
if (fd >= 0)
|
||||
put_unused_fd(fd);
|
||||
if (!IS_ERR_OR_NULL(ctx->thread))
|
||||
kthread_stop(ctx->thread);
|
||||
kfree(ctx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -474,8 +564,8 @@ long bch2_fs_ioctl(struct bch_fs *c, unsigned cmd, void __user *arg)
|
||||
BCH_IOCTL(disk_offline, struct bch_ioctl_disk);
|
||||
case BCH_IOCTL_DISK_SET_STATE:
|
||||
BCH_IOCTL(disk_set_state, struct bch_ioctl_disk_set_state);
|
||||
case BCH_IOCTL_DISK_EVACUATE:
|
||||
BCH_IOCTL(disk_evacuate, struct bch_ioctl_disk);
|
||||
case BCH_IOCTL_DATA:
|
||||
BCH_IOCTL(data, struct bch_ioctl_data);
|
||||
case BCH_IOCTL_READ_SUPER:
|
||||
BCH_IOCTL(read_super, struct bch_ioctl_read_super);
|
||||
case BCH_IOCTL_DISK_GET_IDX:
|
||||
@ -488,9 +578,12 @@ long bch2_fs_ioctl(struct bch_fs *c, unsigned cmd, void __user *arg)
|
||||
}
|
||||
}
|
||||
|
||||
static DEFINE_IDR(bch_chardev_minor);
|
||||
|
||||
static long bch2_chardev_ioctl(struct file *filp, unsigned cmd, unsigned long v)
|
||||
{
|
||||
struct bch_fs *c = filp->private_data;
|
||||
unsigned minor = iminor(file_inode(filp));
|
||||
struct bch_fs *c = minor < U8_MAX ? idr_find(&bch_chardev_minor, minor) : NULL;
|
||||
void __user *arg = (void __user *) v;
|
||||
|
||||
return c
|
||||
@ -507,7 +600,6 @@ static const struct file_operations bch_chardev_fops = {
|
||||
static int bch_chardev_major;
|
||||
static struct class *bch_chardev_class;
|
||||
static struct device *bch_chardev;
|
||||
static DEFINE_IDR(bch_chardev_minor);
|
||||
|
||||
void bch2_fs_chardev_exit(struct bch_fs *c)
|
||||
{
|
||||
@ -524,7 +616,7 @@ int bch2_fs_chardev_init(struct bch_fs *c)
|
||||
return c->minor;
|
||||
|
||||
c->chardev = device_create(bch_chardev_class, NULL,
|
||||
MKDEV(bch_chardev_major, c->minor), NULL,
|
||||
MKDEV(bch_chardev_major, c->minor), c,
|
||||
"bcachefs%u-ctl", c->minor);
|
||||
if (IS_ERR(c->chardev))
|
||||
return PTR_ERR(c->chardev);
|
||||
@ -536,7 +628,7 @@ void bch2_chardev_exit(void)
|
||||
{
|
||||
if (!IS_ERR_OR_NULL(bch_chardev_class))
|
||||
device_destroy(bch_chardev_class,
|
||||
MKDEV(bch_chardev_major, 255));
|
||||
MKDEV(bch_chardev_major, U8_MAX));
|
||||
if (!IS_ERR_OR_NULL(bch_chardev_class))
|
||||
class_destroy(bch_chardev_class);
|
||||
if (bch_chardev_major > 0)
|
||||
@ -554,7 +646,7 @@ int __init bch2_chardev_init(void)
|
||||
return PTR_ERR(bch_chardev_class);
|
||||
|
||||
bch_chardev = device_create(bch_chardev_class, NULL,
|
||||
MKDEV(bch_chardev_major, 255),
|
||||
MKDEV(bch_chardev_major, U8_MAX),
|
||||
NULL, "bcachefs-ctl");
|
||||
if (IS_ERR(bch_chardev))
|
||||
return PTR_ERR(bch_chardev);
|
||||
|
@ -139,6 +139,34 @@ bool bch2_extent_drop_device(struct bkey_s_extent e, unsigned dev)
|
||||
return dropped;
|
||||
}
|
||||
|
||||
const struct bch_extent_ptr *
|
||||
bch2_extent_has_group(struct bch_fs *c, struct bkey_s_c_extent e, unsigned group)
|
||||
{
|
||||
const struct bch_extent_ptr *ptr;
|
||||
|
||||
extent_for_each_ptr(e, ptr) {
|
||||
struct bch_dev *ca = c->devs[ptr->dev];
|
||||
|
||||
if (ca->mi.group &&
|
||||
ca->mi.group == group)
|
||||
return ptr;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const struct bch_extent_ptr *
|
||||
bch2_extent_has_target(struct bch_fs *c, struct bkey_s_c_extent e, unsigned target)
|
||||
{
|
||||
const struct bch_extent_ptr *ptr;
|
||||
|
||||
extent_for_each_ptr(e, ptr)
|
||||
if (dev_in_target(c->devs[ptr->dev], target))
|
||||
return ptr;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
unsigned bch2_extent_nr_ptrs(struct bkey_s_c_extent e)
|
||||
{
|
||||
const struct bch_extent_ptr *ptr;
|
||||
|
@ -43,6 +43,10 @@ void bch2_extent_mark_replicas_cached(struct bch_fs *, struct bkey_s_extent);
|
||||
const struct bch_extent_ptr *
|
||||
bch2_extent_has_device(struct bkey_s_c_extent, unsigned);
|
||||
bool bch2_extent_drop_device(struct bkey_s_extent, unsigned);
|
||||
const struct bch_extent_ptr *
|
||||
bch2_extent_has_group(struct bch_fs *, struct bkey_s_c_extent, unsigned);
|
||||
const struct bch_extent_ptr *
|
||||
bch2_extent_has_target(struct bch_fs *, struct bkey_s_c_extent, unsigned);
|
||||
|
||||
unsigned bch2_extent_nr_ptrs(struct bkey_s_c_extent);
|
||||
unsigned bch2_extent_nr_dirty_ptrs(struct bkey_s_c);
|
||||
|
@ -54,6 +54,13 @@ static inline u64 journal_last_seq(struct journal *j)
|
||||
return j->pin.front;
|
||||
}
|
||||
|
||||
static inline u64 journal_cur_seq(struct journal *j)
|
||||
{
|
||||
BUG_ON(j->pin.back - 1 != atomic64_read(&j->seq));
|
||||
|
||||
return j->pin.back - 1;
|
||||
}
|
||||
|
||||
static inline u64 journal_pin_seq(struct journal *j,
|
||||
struct journal_entry_pin_list *pin_list)
|
||||
{
|
||||
@ -264,7 +271,9 @@ int bch2_journal_seq_should_ignore(struct bch_fs *c, u64 seq, struct btree *b)
|
||||
if (!seq)
|
||||
return 0;
|
||||
|
||||
journal_seq = atomic64_read(&j->seq);
|
||||
spin_lock(&j->lock);
|
||||
journal_seq = journal_cur_seq(j);
|
||||
spin_unlock(&j->lock);
|
||||
|
||||
/* Interier updates aren't journalled: */
|
||||
BUG_ON(b->level);
|
||||
@ -989,6 +998,7 @@ int bch2_journal_read(struct bch_fs *c, struct list_head *list)
|
||||
u64 cur_seq, end_seq, seq;
|
||||
unsigned iter, keys = 0, entries = 0;
|
||||
size_t nr;
|
||||
bool degraded = false;
|
||||
int ret = 0;
|
||||
|
||||
closure_init_stack(&jlist.cl);
|
||||
@ -996,12 +1006,19 @@ int bch2_journal_read(struct bch_fs *c, struct list_head *list)
|
||||
jlist.head = list;
|
||||
jlist.ret = 0;
|
||||
|
||||
for_each_readable_member(ca, c, iter) {
|
||||
percpu_ref_get(&ca->io_ref);
|
||||
closure_call(&ca->journal.read,
|
||||
bch2_journal_read_device,
|
||||
system_unbound_wq,
|
||||
&jlist.cl);
|
||||
for_each_member_device(ca, c, iter) {
|
||||
if (!(bch2_dev_has_data(c, ca) & (1 << BCH_DATA_JOURNAL)))
|
||||
continue;
|
||||
|
||||
if ((ca->mi.state == BCH_MEMBER_STATE_RW ||
|
||||
ca->mi.state == BCH_MEMBER_STATE_RO) &&
|
||||
percpu_ref_tryget(&ca->io_ref))
|
||||
closure_call(&ca->journal.read,
|
||||
bch2_journal_read_device,
|
||||
system_unbound_wq,
|
||||
&jlist.cl);
|
||||
else
|
||||
degraded = true;
|
||||
}
|
||||
|
||||
closure_sync(&jlist.cl);
|
||||
@ -1022,11 +1039,17 @@ int bch2_journal_read(struct bch_fs *c, struct list_head *list)
|
||||
if (ret)
|
||||
goto fsck_err;
|
||||
|
||||
if (test_bit(BCH_FS_REBUILD_REPLICAS, &c->flags) ||
|
||||
fsck_err_on(!bch2_sb_has_replicas(c, BCH_DATA_JOURNAL,
|
||||
i->devs), c,
|
||||
"superblock not marked as containing replicas (type %u)",
|
||||
BCH_DATA_JOURNAL)) {
|
||||
/*
|
||||
* If we're mounting in degraded mode - if we didn't read all
|
||||
* the devices - this is wrong:
|
||||
*/
|
||||
|
||||
if (!degraded &&
|
||||
(test_bit(BCH_FS_REBUILD_REPLICAS, &c->flags) ||
|
||||
fsck_err_on(!bch2_sb_has_replicas(c, BCH_DATA_JOURNAL,
|
||||
i->devs), c,
|
||||
"superblock not marked as containing replicas (type %u)",
|
||||
BCH_DATA_JOURNAL))) {
|
||||
ret = bch2_check_mark_super(c, BCH_DATA_JOURNAL,
|
||||
i->devs);
|
||||
if (ret)
|
||||
@ -1111,7 +1134,7 @@ int bch2_journal_read(struct bch_fs *c, struct list_head *list)
|
||||
}
|
||||
|
||||
bch_info(c, "journal read done, %i keys in %i entries, seq %llu",
|
||||
keys, entries, (u64) atomic64_read(&j->seq));
|
||||
keys, entries, journal_cur_seq(j));
|
||||
fsck_err:
|
||||
return ret;
|
||||
}
|
||||
@ -1174,9 +1197,6 @@ static void journal_pin_new_entry(struct journal *j, int count)
|
||||
atomic64_inc(&j->seq);
|
||||
p = fifo_push_ref(&j->pin);
|
||||
|
||||
EBUG_ON(journal_seq_pin(j, atomic64_read(&j->seq)) !=
|
||||
&fifo_peek_back(&j->pin));
|
||||
|
||||
INIT_LIST_HEAD(&p->list);
|
||||
INIT_LIST_HEAD(&p->flushed);
|
||||
atomic_set(&p->count, count);
|
||||
@ -1190,7 +1210,7 @@ static void bch2_journal_buf_init(struct journal *j)
|
||||
memset(buf->has_inode, 0, sizeof(buf->has_inode));
|
||||
|
||||
memset(buf->data, 0, sizeof(*buf->data));
|
||||
buf->data->seq = cpu_to_le64(atomic64_read(&j->seq));
|
||||
buf->data->seq = cpu_to_le64(journal_cur_seq(j));
|
||||
buf->data->u64s = 0;
|
||||
}
|
||||
|
||||
@ -1472,7 +1492,7 @@ void bch2_journal_start(struct bch_fs *c)
|
||||
|
||||
set_bit(JOURNAL_STARTED, &j->flags);
|
||||
|
||||
while (atomic64_read(&j->seq) < new_seq)
|
||||
while (journal_cur_seq(j) < new_seq)
|
||||
journal_pin_new_entry(j, 0);
|
||||
|
||||
/*
|
||||
@ -2015,9 +2035,11 @@ static void journal_reclaim_work(struct work_struct *work)
|
||||
mutex_unlock(&j->reclaim_lock);
|
||||
|
||||
/* Also flush if the pin fifo is more than half full */
|
||||
spin_lock(&j->lock);
|
||||
seq_to_flush = max_t(s64, seq_to_flush,
|
||||
(s64) atomic64_read(&j->seq) -
|
||||
(s64) journal_cur_seq(j) -
|
||||
(j->pin.size >> 1));
|
||||
spin_unlock(&j->lock);
|
||||
|
||||
/*
|
||||
* If it's been longer than j->reclaim_delay_ms since we last flushed,
|
||||
@ -2110,7 +2132,7 @@ static int journal_write_alloc(struct journal *j, struct journal_buf *w,
|
||||
|
||||
ja->sectors_free = ca->mi.bucket_size - sectors;
|
||||
ja->cur_idx = (ja->cur_idx + 1) % ja->nr;
|
||||
ja->bucket_seq[ja->cur_idx] = atomic64_read(&j->seq);
|
||||
ja->bucket_seq[ja->cur_idx] = le64_to_cpu(w->data->seq);
|
||||
|
||||
extent_ptr_append(bkey_i_to_extent(&j->key),
|
||||
(struct bch_extent_ptr) {
|
||||
@ -2436,9 +2458,9 @@ u64 bch2_inode_journal_seq(struct journal *j, u64 inode)
|
||||
|
||||
spin_lock(&j->lock);
|
||||
if (test_bit(h, journal_cur_buf(j)->has_inode))
|
||||
seq = atomic64_read(&j->seq);
|
||||
seq = journal_cur_seq(j);
|
||||
else if (test_bit(h, journal_prev_buf(j)->has_inode))
|
||||
seq = atomic64_read(&j->seq) - 1;
|
||||
seq = journal_cur_seq(j) - 1;
|
||||
spin_unlock(&j->lock);
|
||||
|
||||
return seq;
|
||||
@ -2547,7 +2569,7 @@ u64 bch2_journal_last_unwritten_seq(struct journal *j)
|
||||
u64 seq;
|
||||
|
||||
spin_lock(&j->lock);
|
||||
seq = atomic64_read(&j->seq);
|
||||
seq = journal_cur_seq(j);
|
||||
if (j->reservations.prev_buf_unwritten)
|
||||
seq--;
|
||||
spin_unlock(&j->lock);
|
||||
@ -2560,9 +2582,9 @@ int bch2_journal_open_seq_async(struct journal *j, u64 seq, struct closure *pare
|
||||
int ret;
|
||||
|
||||
spin_lock(&j->lock);
|
||||
BUG_ON(seq > atomic64_read(&j->seq));
|
||||
BUG_ON(seq > journal_cur_seq(j));
|
||||
|
||||
if (seq < atomic64_read(&j->seq) ||
|
||||
if (seq < journal_cur_seq(j) ||
|
||||
journal_entry_is_open(j)) {
|
||||
spin_unlock(&j->lock);
|
||||
return 1;
|
||||
@ -2583,17 +2605,17 @@ void bch2_journal_wait_on_seq(struct journal *j, u64 seq, struct closure *parent
|
||||
{
|
||||
spin_lock(&j->lock);
|
||||
|
||||
BUG_ON(seq > atomic64_read(&j->seq));
|
||||
BUG_ON(seq > journal_cur_seq(j));
|
||||
|
||||
if (bch2_journal_error(j)) {
|
||||
spin_unlock(&j->lock);
|
||||
return;
|
||||
}
|
||||
|
||||
if (seq == atomic64_read(&j->seq)) {
|
||||
if (seq == journal_cur_seq(j)) {
|
||||
if (!closure_wait(&journal_cur_buf(j)->wait, parent))
|
||||
BUG();
|
||||
} else if (seq + 1 == atomic64_read(&j->seq) &&
|
||||
} else if (seq + 1 == journal_cur_seq(j) &&
|
||||
j->reservations.prev_buf_unwritten) {
|
||||
if (!closure_wait(&journal_prev_buf(j)->wait, parent))
|
||||
BUG();
|
||||
@ -2615,14 +2637,14 @@ void bch2_journal_flush_seq_async(struct journal *j, u64 seq, struct closure *pa
|
||||
|
||||
spin_lock(&j->lock);
|
||||
|
||||
BUG_ON(seq > atomic64_read(&j->seq));
|
||||
BUG_ON(seq > journal_cur_seq(j));
|
||||
|
||||
if (bch2_journal_error(j)) {
|
||||
spin_unlock(&j->lock);
|
||||
return;
|
||||
}
|
||||
|
||||
if (seq == atomic64_read(&j->seq)) {
|
||||
if (seq == journal_cur_seq(j)) {
|
||||
bool set_need_write = false;
|
||||
|
||||
buf = journal_cur_buf(j);
|
||||
@ -2643,7 +2665,7 @@ void bch2_journal_flush_seq_async(struct journal *j, u64 seq, struct closure *pa
|
||||
case JOURNAL_ENTRY_CLOSED:
|
||||
/*
|
||||
* Journal entry hasn't been opened yet, but caller
|
||||
* claims it has something (seq == j->seq):
|
||||
* claims it has something
|
||||
*/
|
||||
BUG();
|
||||
case JOURNAL_ENTRY_INUSE:
|
||||
@ -2652,7 +2674,7 @@ void bch2_journal_flush_seq_async(struct journal *j, u64 seq, struct closure *pa
|
||||
return;
|
||||
}
|
||||
} else if (parent &&
|
||||
seq + 1 == atomic64_read(&j->seq) &&
|
||||
seq + 1 == journal_cur_seq(j) &&
|
||||
j->reservations.prev_buf_unwritten) {
|
||||
buf = journal_prev_buf(j);
|
||||
|
||||
@ -2676,9 +2698,9 @@ static int journal_seq_flushed(struct journal *j, u64 seq)
|
||||
int ret = 1;
|
||||
|
||||
spin_lock(&j->lock);
|
||||
BUG_ON(seq > atomic64_read(&j->seq));
|
||||
BUG_ON(seq > journal_cur_seq(j));
|
||||
|
||||
if (seq == atomic64_read(&j->seq)) {
|
||||
if (seq == journal_cur_seq(j)) {
|
||||
bool set_need_write = false;
|
||||
|
||||
ret = 0;
|
||||
@ -2697,7 +2719,7 @@ static int journal_seq_flushed(struct journal *j, u64 seq)
|
||||
case JOURNAL_ENTRY_CLOSED:
|
||||
/*
|
||||
* Journal entry hasn't been opened yet, but caller
|
||||
* claims it has something (seq == j->seq):
|
||||
* claims it has something
|
||||
*/
|
||||
BUG();
|
||||
case JOURNAL_ENTRY_INUSE:
|
||||
@ -2705,7 +2727,7 @@ static int journal_seq_flushed(struct journal *j, u64 seq)
|
||||
case JOURNAL_UNLOCKED:
|
||||
return 0;
|
||||
}
|
||||
} else if (seq + 1 == atomic64_read(&j->seq) &&
|
||||
} else if (seq + 1 == journal_cur_seq(j) &&
|
||||
j->reservations.prev_buf_unwritten) {
|
||||
ret = bch2_journal_error(j);
|
||||
}
|
||||
@ -2762,7 +2784,7 @@ void bch2_journal_flush_async(struct journal *j, struct closure *parent)
|
||||
u64 seq, journal_seq;
|
||||
|
||||
spin_lock(&j->lock);
|
||||
journal_seq = atomic64_read(&j->seq);
|
||||
journal_seq = journal_cur_seq(j);
|
||||
|
||||
if (journal_entry_is_open(j)) {
|
||||
seq = journal_seq;
|
||||
@ -2782,7 +2804,7 @@ int bch2_journal_flush(struct journal *j)
|
||||
u64 seq, journal_seq;
|
||||
|
||||
spin_lock(&j->lock);
|
||||
journal_seq = atomic64_read(&j->seq);
|
||||
journal_seq = journal_cur_seq(j);
|
||||
|
||||
if (journal_entry_is_open(j)) {
|
||||
seq = journal_seq;
|
||||
@ -2797,7 +2819,7 @@ int bch2_journal_flush(struct journal *j)
|
||||
return bch2_journal_flush_seq(j, seq);
|
||||
}
|
||||
|
||||
int bch2_journal_flush_device(struct journal *j, unsigned dev_idx)
|
||||
int bch2_journal_flush_device(struct journal *j, int dev_idx)
|
||||
{
|
||||
struct bch_fs *c = container_of(j, struct bch_fs, journal);
|
||||
struct journal_entry_pin_list *p;
|
||||
@ -2807,7 +2829,9 @@ int bch2_journal_flush_device(struct journal *j, unsigned dev_idx)
|
||||
|
||||
spin_lock(&j->lock);
|
||||
fifo_for_each_entry_ptr(p, &j->pin, iter)
|
||||
if (bch2_dev_list_has_dev(p->devs, dev_idx))
|
||||
if (dev_idx >= 0
|
||||
? bch2_dev_list_has_dev(p->devs, dev_idx)
|
||||
: p->devs.nr < c->opts.metadata_replicas)
|
||||
seq = iter;
|
||||
spin_unlock(&j->lock);
|
||||
|
||||
@ -2821,7 +2845,7 @@ int bch2_journal_flush_device(struct journal *j, unsigned dev_idx)
|
||||
seq = 0;
|
||||
|
||||
spin_lock(&j->lock);
|
||||
while (!ret && seq < atomic64_read(&j->seq)) {
|
||||
while (!ret && seq < j->pin.back) {
|
||||
seq = max(seq, journal_last_seq(j));
|
||||
devs = journal_seq_pin(j, seq)->devs;
|
||||
seq++;
|
||||
@ -2982,7 +3006,7 @@ ssize_t bch2_journal_print_debug(struct journal *j, char *buf)
|
||||
"dirty:\t\t\t%i\n"
|
||||
"replay done:\t\t%i\n",
|
||||
fifo_used(&j->pin),
|
||||
(u64) atomic64_read(&j->seq),
|
||||
journal_cur_seq(j),
|
||||
journal_last_seq(j),
|
||||
j->last_seq_ondisk,
|
||||
journal_state_count(*s, s->idx),
|
||||
|
@ -368,7 +368,7 @@ void bch2_journal_meta_async(struct journal *, struct closure *);
|
||||
int bch2_journal_flush_seq(struct journal *, u64);
|
||||
int bch2_journal_flush(struct journal *);
|
||||
int bch2_journal_meta(struct journal *);
|
||||
int bch2_journal_flush_device(struct journal *, unsigned);
|
||||
int bch2_journal_flush_device(struct journal *, int);
|
||||
|
||||
void bch2_journal_halt(struct journal *);
|
||||
|
||||
|
@ -40,12 +40,15 @@ static int bch2_dev_usrdata_migrate(struct bch_fs *c, struct bch_dev *ca,
|
||||
* operations
|
||||
*/
|
||||
do {
|
||||
memset(&stats, 0, sizeof(stats));
|
||||
|
||||
ret = bch2_move_data(c, NULL,
|
||||
SECTORS_IN_FLIGHT_PER_DEVICE,
|
||||
NULL,
|
||||
writepoint_hashed((unsigned long) current),
|
||||
0,
|
||||
ca->dev_idx,
|
||||
POS_MIN, POS_MAX,
|
||||
migrate_pred, ca,
|
||||
&stats);
|
||||
if (ret) {
|
||||
|
@ -87,7 +87,6 @@ static int bch2_migrate_index_update(struct bch_write_op *op)
|
||||
m->move_dev)))
|
||||
bch2_extent_drop_ptr(extent_i_to_s(insert), ptr);
|
||||
|
||||
|
||||
extent_for_each_ptr_crc(extent_i_to_s(new), ptr, crc) {
|
||||
if (bch2_extent_has_device(extent_i_to_s_c(insert), ptr->dev)) {
|
||||
/*
|
||||
@ -194,6 +193,8 @@ static void move_free(struct closure *cl)
|
||||
struct bio_vec *bv;
|
||||
int i;
|
||||
|
||||
bch2_disk_reservation_put(io->write.op.c, &io->write.op.res);
|
||||
|
||||
bio_for_each_segment_all(bv, &io->write.op.wbio.bio, i)
|
||||
if (bv->bv_page)
|
||||
__free_page(bv->bv_page);
|
||||
@ -243,20 +244,21 @@ static int bch2_move_extent(struct bch_fs *c,
|
||||
int btree_insert_flags,
|
||||
int move_device,
|
||||
struct bch_io_opts opts,
|
||||
struct bkey_s_c k)
|
||||
struct bkey_s_c_extent e)
|
||||
{
|
||||
struct extent_pick_ptr pick;
|
||||
struct moving_io *io;
|
||||
const struct bch_extent_ptr *ptr;
|
||||
struct bch_extent_crc_unpacked crc;
|
||||
unsigned sectors = k.k->size, pages;
|
||||
unsigned sectors = e.k->size, pages, nr_good;
|
||||
int ret = -ENOMEM;
|
||||
|
||||
bch2_extent_pick_ptr(c, k, NULL, &pick);
|
||||
bch2_extent_pick_ptr(c, e.s_c, NULL, &pick);
|
||||
if (IS_ERR_OR_NULL(pick.ca))
|
||||
return pick.ca ? PTR_ERR(pick.ca) : 0;
|
||||
|
||||
/* write path might have to decompress data: */
|
||||
extent_for_each_ptr_crc(bkey_s_c_to_extent(k), ptr, crc)
|
||||
extent_for_each_ptr_crc(e, ptr, crc)
|
||||
sectors = max_t(unsigned, sectors, crc.uncompressed_size);
|
||||
|
||||
pages = DIV_ROUND_UP(sectors, PAGE_SECTORS);
|
||||
@ -266,7 +268,7 @@ static int bch2_move_extent(struct bch_fs *c,
|
||||
goto err;
|
||||
|
||||
io->write.ctxt = ctxt;
|
||||
io->sectors = k.k->size;
|
||||
io->sectors = e.k->size;
|
||||
|
||||
bio_init(&io->write.op.wbio.bio, io->bi_inline_vecs, pages);
|
||||
bio_set_prio(&io->write.op.wbio.bio,
|
||||
@ -274,10 +276,8 @@ static int bch2_move_extent(struct bch_fs *c,
|
||||
io->write.op.wbio.bio.bi_iter.bi_size = sectors << 9;
|
||||
|
||||
bch2_bio_map(&io->write.op.wbio.bio, NULL);
|
||||
if (bio_alloc_pages(&io->write.op.wbio.bio, GFP_KERNEL)) {
|
||||
kfree(io);
|
||||
goto err;
|
||||
}
|
||||
if (bio_alloc_pages(&io->write.op.wbio.bio, GFP_KERNEL))
|
||||
goto err_free;
|
||||
|
||||
io->rbio.opts = opts;
|
||||
bio_init(&io->rbio.bio, io->bi_inline_vecs, pages);
|
||||
@ -285,7 +285,7 @@ static int bch2_move_extent(struct bch_fs *c,
|
||||
io->rbio.bio.bi_iter.bi_size = sectors << 9;
|
||||
|
||||
bio_set_op_attrs(&io->rbio.bio, REQ_OP_READ, 0);
|
||||
io->rbio.bio.bi_iter.bi_sector = bkey_start_offset(k.k);
|
||||
io->rbio.bio.bi_iter.bi_sector = bkey_start_offset(e.k);
|
||||
io->rbio.bio.bi_end_io = move_read_endio;
|
||||
|
||||
io->write.btree_insert_flags = btree_insert_flags;
|
||||
@ -298,10 +298,22 @@ static int bch2_move_extent(struct bch_fs *c,
|
||||
io->write.op.devs = devs;
|
||||
io->write.op.write_point = wp;
|
||||
|
||||
atomic64_inc(&ctxt->stats->keys_moved);
|
||||
atomic64_add(k.k->size, &ctxt->stats->sectors_moved);
|
||||
if (move_device < 0 &&
|
||||
((nr_good = bch2_extent_nr_good_ptrs(c, e)) <
|
||||
c->opts.data_replicas)) {
|
||||
io->write.op.nr_replicas = c->opts.data_replicas - nr_good;
|
||||
|
||||
trace_move_extent(k.k);
|
||||
ret = bch2_disk_reservation_get(c, &io->write.op.res,
|
||||
e.k->size,
|
||||
io->write.op.nr_replicas, 0);
|
||||
if (ret)
|
||||
goto err_free_pages;
|
||||
}
|
||||
|
||||
atomic64_inc(&ctxt->stats->keys_moved);
|
||||
atomic64_add(e.k->size, &ctxt->stats->sectors_moved);
|
||||
|
||||
trace_move_extent(e.k);
|
||||
|
||||
atomic_add(io->sectors, &ctxt->sectors_in_flight);
|
||||
list_add_tail(&io->list, &ctxt->reads);
|
||||
@ -311,12 +323,16 @@ static int bch2_move_extent(struct bch_fs *c,
|
||||
* ctxt when doing wakeup
|
||||
*/
|
||||
closure_get(&ctxt->cl);
|
||||
bch2_read_extent(c, &io->rbio, bkey_s_c_to_extent(k),
|
||||
&pick, BCH_READ_NODECODE);
|
||||
bch2_read_extent(c, &io->rbio, e, &pick, BCH_READ_NODECODE);
|
||||
return 0;
|
||||
err_free_pages:
|
||||
bio_free_pages(&io->write.op.wbio.bio);
|
||||
err_free:
|
||||
kfree(io);
|
||||
err:
|
||||
trace_move_alloc_fail(k.k);
|
||||
return -ENOMEM;
|
||||
percpu_ref_put(&pick.ca->io_ref);
|
||||
trace_move_alloc_fail(e.k);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void do_pending_writes(struct moving_context *ctxt)
|
||||
@ -355,6 +371,8 @@ int bch2_move_data(struct bch_fs *c,
|
||||
struct write_point_specifier wp,
|
||||
int btree_insert_flags,
|
||||
int move_device,
|
||||
struct bpos start,
|
||||
struct bpos end,
|
||||
move_pred_fn pred, void *arg,
|
||||
struct bch_move_stats *stats)
|
||||
{
|
||||
@ -363,14 +381,16 @@ int bch2_move_data(struct bch_fs *c,
|
||||
struct bch_io_opts opts = bch2_opts_to_inode_opts(c->opts);
|
||||
BKEY_PADDED(k) tmp;
|
||||
struct bkey_s_c k;
|
||||
struct bkey_s_c_extent e;
|
||||
u64 cur_inum = U64_MAX;
|
||||
int ret = 0;
|
||||
|
||||
memset(stats, 0, sizeof(*stats));
|
||||
closure_init_stack(&ctxt.cl);
|
||||
INIT_LIST_HEAD(&ctxt.reads);
|
||||
init_waitqueue_head(&ctxt.wait);
|
||||
bch2_btree_iter_init(&stats->iter, c, BTREE_ID_EXTENTS, POS_MIN,
|
||||
|
||||
stats->data_type = BCH_DATA_USER;
|
||||
bch2_btree_iter_init(&stats->iter, c, BTREE_ID_EXTENTS, start,
|
||||
BTREE_ITER_PREFETCH);
|
||||
|
||||
if (rate)
|
||||
@ -396,10 +416,14 @@ peek:
|
||||
ret = btree_iter_err(k);
|
||||
if (ret)
|
||||
break;
|
||||
if (bkey_cmp(bkey_start_pos(k.k), end) >= 0)
|
||||
break;
|
||||
|
||||
if (!bkey_extent_is_data(k.k))
|
||||
goto next_nondata;
|
||||
|
||||
e = bkey_s_c_to_extent(k);
|
||||
|
||||
if (cur_inum != k.k->p.inode) {
|
||||
struct bch_inode_unpacked inode;
|
||||
|
||||
@ -413,7 +437,7 @@ peek:
|
||||
goto peek;
|
||||
}
|
||||
|
||||
if (!pred(arg, bkey_s_c_to_extent(k)))
|
||||
if (!pred(arg, e))
|
||||
goto next;
|
||||
|
||||
/* unlock before doing IO: */
|
||||
@ -423,7 +447,8 @@ peek:
|
||||
|
||||
if (bch2_move_extent(c, &ctxt, devs, wp,
|
||||
btree_insert_flags,
|
||||
move_device, opts, k)) {
|
||||
move_device, opts,
|
||||
bkey_s_c_to_extent(k))) {
|
||||
/* memory allocation failure, wait for some IO to finish */
|
||||
bch2_move_ctxt_wait_for_io(&ctxt);
|
||||
continue;
|
||||
@ -453,3 +478,157 @@ next_nondata:
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int bch2_gc_data_replicas(struct bch_fs *c)
|
||||
{
|
||||
struct btree_iter iter;
|
||||
struct bkey_s_c k;
|
||||
int ret;
|
||||
|
||||
mutex_lock(&c->replicas_gc_lock);
|
||||
bch2_replicas_gc_start(c, 1 << BCH_DATA_USER);
|
||||
|
||||
for_each_btree_key(&iter, c, BTREE_ID_EXTENTS, POS_MIN,
|
||||
BTREE_ITER_PREFETCH, k) {
|
||||
ret = bch2_check_mark_super(c, BCH_DATA_USER, bch2_bkey_devs(k));
|
||||
if (ret)
|
||||
break;
|
||||
}
|
||||
ret = bch2_btree_iter_unlock(&iter) ?: ret;
|
||||
|
||||
bch2_replicas_gc_end(c, ret);
|
||||
mutex_unlock(&c->replicas_gc_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int bch2_gc_btree_replicas(struct bch_fs *c)
|
||||
{
|
||||
struct btree_iter iter;
|
||||
struct btree *b;
|
||||
unsigned id;
|
||||
int ret = 0;
|
||||
|
||||
mutex_lock(&c->replicas_gc_lock);
|
||||
bch2_replicas_gc_start(c, 1 << BCH_DATA_BTREE);
|
||||
|
||||
for (id = 0; id < BTREE_ID_NR; id++) {
|
||||
for_each_btree_node(&iter, c, id, POS_MIN, BTREE_ITER_PREFETCH, b) {
|
||||
ret = bch2_check_mark_super(c, BCH_DATA_BTREE,
|
||||
bch2_bkey_devs(bkey_i_to_s_c(&b->key)));
|
||||
|
||||
bch2_btree_iter_cond_resched(&iter);
|
||||
}
|
||||
|
||||
ret = bch2_btree_iter_unlock(&iter) ?: ret;
|
||||
}
|
||||
|
||||
bch2_replicas_gc_end(c, ret);
|
||||
mutex_unlock(&c->replicas_gc_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int bch2_move_btree(struct bch_fs *c,
|
||||
move_pred_fn pred,
|
||||
void *arg,
|
||||
struct bch_move_stats *stats)
|
||||
{
|
||||
struct btree *b;
|
||||
unsigned id;
|
||||
int ret = 0;
|
||||
|
||||
stats->data_type = BCH_DATA_BTREE;
|
||||
|
||||
for (id = 0; id < BTREE_ID_NR; id++) {
|
||||
for_each_btree_node(&stats->iter, c, id, POS_MIN, BTREE_ITER_PREFETCH, b) {
|
||||
if (pred(arg, bkey_i_to_s_c_extent(&b->key)))
|
||||
ret = bch2_btree_node_rewrite(c, &stats->iter,
|
||||
b->data->keys.seq, 0) ?: ret;
|
||||
|
||||
bch2_btree_iter_cond_resched(&stats->iter);
|
||||
}
|
||||
|
||||
ret = bch2_btree_iter_unlock(&stats->iter) ?: ret;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#if 0
|
||||
static bool scrub_data_pred(void *arg, struct bkey_s_c_extent e)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
static bool rereplicate_metadata_pred(void *arg, struct bkey_s_c_extent e)
|
||||
{
|
||||
struct bch_fs *c = arg;
|
||||
unsigned nr_good = bch2_extent_nr_good_ptrs(c, e);
|
||||
|
||||
return nr_good && nr_good < c->opts.metadata_replicas;
|
||||
}
|
||||
|
||||
static bool rereplicate_data_pred(void *arg, struct bkey_s_c_extent e)
|
||||
{
|
||||
struct bch_fs *c = arg;
|
||||
unsigned nr_good = bch2_extent_nr_good_ptrs(c, e);
|
||||
|
||||
return nr_good && nr_good < c->opts.data_replicas;
|
||||
}
|
||||
|
||||
static bool migrate_pred(void *arg, struct bkey_s_c_extent e)
|
||||
{
|
||||
struct bch_ioctl_data *op = arg;
|
||||
|
||||
return bch2_extent_has_device(e, op->migrate.dev);
|
||||
}
|
||||
|
||||
int bch2_data_job(struct bch_fs *c,
|
||||
struct bch_move_stats *stats,
|
||||
struct bch_ioctl_data op)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
switch (op.op) {
|
||||
case BCH_DATA_OP_REREPLICATE:
|
||||
stats->data_type = BCH_DATA_JOURNAL;
|
||||
ret = bch2_journal_flush_device(&c->journal, -1);
|
||||
|
||||
ret = bch2_move_btree(c, rereplicate_metadata_pred, c, stats) ?: ret;
|
||||
ret = bch2_gc_btree_replicas(c) ?: ret;
|
||||
|
||||
ret = bch2_move_data(c, NULL, SECTORS_IN_FLIGHT_PER_DEVICE,
|
||||
NULL,
|
||||
writepoint_hashed((unsigned long) current),
|
||||
0, -1,
|
||||
op.start,
|
||||
op.end,
|
||||
rereplicate_data_pred, c, stats) ?: ret;
|
||||
ret = bch2_gc_data_replicas(c) ?: ret;
|
||||
break;
|
||||
case BCH_DATA_OP_MIGRATE:
|
||||
if (op.migrate.dev >= c->sb.nr_devices)
|
||||
return -EINVAL;
|
||||
|
||||
stats->data_type = BCH_DATA_JOURNAL;
|
||||
ret = bch2_journal_flush_device(&c->journal, op.migrate.dev);
|
||||
|
||||
ret = bch2_move_btree(c, migrate_pred, &op, stats) ?: ret;
|
||||
ret = bch2_gc_btree_replicas(c) ?: ret;
|
||||
|
||||
ret = bch2_move_data(c, NULL, SECTORS_IN_FLIGHT_PER_DEVICE,
|
||||
NULL,
|
||||
writepoint_hashed((unsigned long) current),
|
||||
0, -1,
|
||||
op.start,
|
||||
op.end,
|
||||
migrate_pred, &op, stats) ?: ret;
|
||||
ret = bch2_gc_data_replicas(c) ?: ret;
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ void bch2_migrate_write_init(struct migrate_write *, struct bch_read_bio *);
|
||||
typedef bool (*move_pred_fn)(void *, struct bkey_s_c_extent);
|
||||
|
||||
struct bch_move_stats {
|
||||
enum bch_data_type data_type;
|
||||
struct btree_iter iter;
|
||||
|
||||
atomic64_t keys_moved;
|
||||
@ -38,7 +39,12 @@ struct bch_move_stats {
|
||||
int bch2_move_data(struct bch_fs *, struct bch_ratelimit *,
|
||||
unsigned, struct bch_devs_mask *,
|
||||
struct write_point_specifier,
|
||||
int, int, move_pred_fn, void *,
|
||||
int, int, struct bpos, struct bpos,
|
||||
move_pred_fn, void *,
|
||||
struct bch_move_stats *);
|
||||
|
||||
int bch2_data_job(struct bch_fs *,
|
||||
struct bch_move_stats *,
|
||||
struct bch_ioctl_data);
|
||||
|
||||
#endif /* _BCACHEFS_MOVE_H */
|
||||
|
@ -106,6 +106,7 @@ static void bch2_copygc(struct bch_fs *c, struct bch_dev *ca)
|
||||
size_t b;
|
||||
int ret;
|
||||
|
||||
memset(&move_stats, 0, sizeof(move_stats));
|
||||
closure_wait_event(&c->freelist_wait, have_copygc_reserve(ca));
|
||||
|
||||
/*
|
||||
@ -166,6 +167,7 @@ static void bch2_copygc(struct bch_fs *c, struct bch_dev *ca)
|
||||
writepoint_ptr(&ca->copygc_write_point),
|
||||
BTREE_INSERT_USE_RESERVE,
|
||||
ca->dev_idx,
|
||||
POS_MIN, POS_MAX,
|
||||
copygc_pred, ca,
|
||||
&move_stats);
|
||||
|
||||
|
@ -13,6 +13,7 @@
|
||||
static int bch2_sb_replicas_to_cpu_replicas(struct bch_fs *);
|
||||
static int bch2_cpu_replicas_to_sb_replicas(struct bch_fs *,
|
||||
struct bch_replicas_cpu *);
|
||||
static int bch2_sb_disk_groups_to_cpu(struct bch_fs *);
|
||||
|
||||
/* superblock fields (optional/variable size sections: */
|
||||
|
||||
@ -43,6 +44,7 @@ static const struct bch_sb_field_ops bch2_sb_field_ops[] = {
|
||||
|
||||
static const char *bch2_sb_field_validate(struct bch_sb *sb,
|
||||
struct bch_sb_field *f)
|
||||
|
||||
{
|
||||
unsigned type = le32_to_cpu(f->type);
|
||||
|
||||
@ -297,7 +299,7 @@ const char *bch2_sb_validate(struct bch_sb_handle *disk_sb)
|
||||
if (!sb->nr_devices ||
|
||||
sb->nr_devices <= sb->dev_idx ||
|
||||
sb->nr_devices > BCH_SB_MEMBERS_MAX)
|
||||
return "Bad cache device number in set";
|
||||
return "Bad number of member devices";
|
||||
|
||||
if (!BCH_SB_META_REPLICAS_WANT(sb) ||
|
||||
BCH_SB_META_REPLICAS_WANT(sb) >= BCH_REPLICAS_MAX)
|
||||
@ -458,6 +460,10 @@ int bch2_sb_to_fs(struct bch_fs *c, struct bch_sb *src)
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = bch2_sb_disk_groups_to_cpu(c);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
bch2_sb_update(c);
|
||||
return 0;
|
||||
}
|
||||
@ -1557,3 +1563,129 @@ static const char *bch2_sb_validate_quota(struct bch_sb *sb,
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Disk groups: */
|
||||
|
||||
#if 0
|
||||
static size_t trim_nulls(const char *str, size_t len)
|
||||
{
|
||||
while (len && !str[len - 1])
|
||||
--len;
|
||||
return len;
|
||||
}
|
||||
#endif
|
||||
|
||||
static const char *bch2_sb_validate_disk_groups(struct bch_sb *sb,
|
||||
struct bch_sb_field *f)
|
||||
{
|
||||
struct bch_sb_field_disk_groups *groups =
|
||||
field_to_type(f, disk_groups);
|
||||
struct bch_sb_field_members *mi;
|
||||
struct bch_member *m;
|
||||
struct bch_disk_group *g;
|
||||
unsigned nr_groups;
|
||||
|
||||
mi = bch2_sb_get_members(sb);
|
||||
groups = bch2_sb_get_disk_groups(sb);
|
||||
nr_groups = disk_groups_nr(groups);
|
||||
|
||||
for (m = mi->members;
|
||||
m < mi->members + sb->nr_devices;
|
||||
m++) {
|
||||
if (!BCH_MEMBER_GROUP(m))
|
||||
continue;
|
||||
|
||||
if (BCH_MEMBER_GROUP(m) >= nr_groups)
|
||||
return "disk has invalid group";
|
||||
|
||||
g = &groups->entries[BCH_MEMBER_GROUP(m)];
|
||||
if (BCH_GROUP_DELETED(g))
|
||||
return "disk has invalid group";
|
||||
}
|
||||
#if 0
|
||||
if (!groups)
|
||||
return NULL;
|
||||
|
||||
char **labels;
|
||||
labels = kcalloc(nr_groups, sizeof(char *), GFP_KERNEL);
|
||||
if (!labels)
|
||||
return "cannot allocate memory";
|
||||
|
||||
for (g = groups->groups;
|
||||
g < groups->groups + nr_groups;
|
||||
g++) {
|
||||
|
||||
}
|
||||
#endif
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int bch2_sb_disk_groups_to_cpu(struct bch_fs *c)
|
||||
{
|
||||
struct bch_sb_field_members *mi;
|
||||
struct bch_sb_field_disk_groups *groups;
|
||||
struct bch_disk_groups_cpu *cpu_g, *old_g;
|
||||
unsigned i, nr_groups;
|
||||
|
||||
lockdep_assert_held(&c->sb_lock);
|
||||
|
||||
mi = bch2_sb_get_members(c->disk_sb);
|
||||
groups = bch2_sb_get_disk_groups(c->disk_sb);
|
||||
nr_groups = disk_groups_nr(groups);
|
||||
|
||||
if (!groups)
|
||||
return 0;
|
||||
|
||||
cpu_g = kzalloc(sizeof(*cpu_g) +
|
||||
sizeof(cpu_g->entries[0]) * nr_groups, GFP_KERNEL);
|
||||
if (!cpu_g)
|
||||
return -ENOMEM;
|
||||
|
||||
cpu_g->nr = nr_groups;
|
||||
|
||||
for (i = 0; i < nr_groups; i++) {
|
||||
struct bch_disk_group *src = &groups->entries[i];
|
||||
struct bch_disk_group_cpu *dst = &cpu_g->entries[i];
|
||||
|
||||
dst->deleted = BCH_GROUP_DELETED(src);
|
||||
}
|
||||
|
||||
for (i = 0; i < c->disk_sb->nr_devices; i++) {
|
||||
struct bch_member *m = mi->members + i;
|
||||
struct bch_disk_group_cpu *dst =
|
||||
&cpu_g->entries[BCH_MEMBER_GROUP(m)];
|
||||
|
||||
if (!bch2_member_exists(m))
|
||||
continue;
|
||||
|
||||
__set_bit(i, dst->devs.d);
|
||||
}
|
||||
|
||||
old_g = c->disk_groups;
|
||||
rcu_assign_pointer(c->disk_groups, cpu_g);
|
||||
if (old_g)
|
||||
kfree_rcu(old_g, rcu);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const struct bch_devs_mask *bch2_target_to_mask(struct bch_fs *c, unsigned target)
|
||||
{
|
||||
struct target t = target_decode(target);
|
||||
|
||||
switch (t.type) {
|
||||
case TARGET_DEV:
|
||||
BUG_ON(t.dev >= c->sb.nr_devices && !c->devs[t.dev]);
|
||||
return &c->devs[t.dev]->self;
|
||||
case TARGET_GROUP: {
|
||||
struct bch_disk_groups_cpu *g =
|
||||
rcu_dereference(c->disk_groups);
|
||||
|
||||
/* XXX: what to do here? */
|
||||
BUG_ON(t.group >= g->nr || g->entries[t.group].deleted);
|
||||
return &g->entries[t.group].devs;
|
||||
}
|
||||
default:
|
||||
BUG();
|
||||
}
|
||||
}
|
||||
|
@ -127,6 +127,7 @@ static inline struct bch_member_cpu bch2_mi_to_cpu(struct bch_member *mi)
|
||||
.nbuckets = le64_to_cpu(mi->nbuckets),
|
||||
.first_bucket = le16_to_cpu(mi->first_bucket),
|
||||
.bucket_size = le16_to_cpu(mi->bucket_size),
|
||||
.group = BCH_MEMBER_GROUP(mi),
|
||||
.state = BCH_MEMBER_STATE(mi),
|
||||
.tier = BCH_MEMBER_TIER(mi),
|
||||
.replacement = BCH_MEMBER_REPLACEMENT(mi),
|
||||
@ -177,4 +178,65 @@ replicas_entry_next(struct bch_replicas_entry *i)
|
||||
(void *) (_i) < vstruct_end(&(_r)->field) && (_i)->data_type;\
|
||||
(_i) = replicas_entry_next(_i))
|
||||
|
||||
/* disk groups: */
|
||||
|
||||
static inline unsigned disk_groups_nr(struct bch_sb_field_disk_groups *groups)
|
||||
{
|
||||
return groups
|
||||
? (vstruct_end(&groups->field) -
|
||||
(void *) &groups->entries[0]) / sizeof(struct bch_disk_group)
|
||||
: 0;
|
||||
}
|
||||
|
||||
struct target {
|
||||
enum {
|
||||
TARGET_NULL,
|
||||
TARGET_DEV,
|
||||
TARGET_GROUP,
|
||||
} type;
|
||||
union {
|
||||
unsigned dev;
|
||||
unsigned group;
|
||||
};
|
||||
};
|
||||
|
||||
static inline u16 dev_to_target(unsigned dev)
|
||||
{
|
||||
return 1 + dev;
|
||||
}
|
||||
|
||||
static inline u16 group_to_target(unsigned group)
|
||||
{
|
||||
return 1 + U8_MAX + group;
|
||||
}
|
||||
|
||||
static inline struct target target_decode(unsigned target)
|
||||
{
|
||||
if (!target)
|
||||
return (struct target) { .type = TARGET_NULL };
|
||||
|
||||
--target;
|
||||
if (target <= U8_MAX)
|
||||
return (struct target) { .type = TARGET_DEV, .dev = target };
|
||||
|
||||
target -= U8_MAX;
|
||||
return (struct target) { .type = TARGET_GROUP, .group = target };
|
||||
}
|
||||
|
||||
static inline bool dev_in_target(struct bch_dev *ca, unsigned target)
|
||||
{
|
||||
struct target t = target_decode(target);
|
||||
|
||||
switch (t.type) {
|
||||
case TARGET_DEV:
|
||||
return ca->dev_idx == t.dev;
|
||||
case TARGET_GROUP:
|
||||
return ca->mi.group && ca->mi.group == t.group;
|
||||
default:
|
||||
BUG();
|
||||
}
|
||||
}
|
||||
|
||||
const struct bch_devs_mask *bch2_target_to_mask(struct bch_fs *, unsigned);
|
||||
|
||||
#endif /* _BCACHEFS_SUPER_IO_H */
|
||||
|
@ -426,6 +426,7 @@ static void bch2_fs_free(struct bch_fs *c)
|
||||
mempool_exit(&c->fill_iter);
|
||||
percpu_ref_exit(&c->writes);
|
||||
kfree(rcu_dereference_protected(c->replicas, 1));
|
||||
kfree(rcu_dereference_protected(c->disk_groups, 1));
|
||||
|
||||
if (c->copygc_wq)
|
||||
destroy_workqueue(c->copygc_wq);
|
||||
@ -1169,6 +1170,12 @@ static int __bch2_dev_online(struct bch_fs *c, struct bch_sb_handle *sb)
|
||||
|
||||
BUG_ON(!percpu_ref_is_zero(&ca->io_ref));
|
||||
|
||||
if (get_capacity(sb->bdev->bd_disk) <
|
||||
ca->mi.bucket_size * ca->mi.nbuckets) {
|
||||
bch_err(c, "device too small");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = bch2_dev_journal_init(ca, sb->sb);
|
||||
if (ret)
|
||||
return ret;
|
||||
@ -1495,10 +1502,7 @@ int bch2_dev_add(struct bch_fs *c, const char *path)
|
||||
mutex_lock(&c->state_lock);
|
||||
mutex_lock(&c->sb_lock);
|
||||
|
||||
/*
|
||||
* Preserve the old cache member information (esp. tier)
|
||||
* before we start bashing the disk stuff.
|
||||
*/
|
||||
/* Grab member info for new disk: */
|
||||
dev_mi = bch2_sb_get_members(sb.sb);
|
||||
saved_mi = dev_mi->members[sb.sb->dev_idx];
|
||||
saved_mi.last_mount = cpu_to_le64(ktime_get_seconds());
|
||||
@ -1646,47 +1650,6 @@ int bch2_dev_offline(struct bch_fs *c, struct bch_dev *ca, int flags)
|
||||
return 0;
|
||||
}
|
||||
|
||||
int bch2_dev_evacuate(struct bch_fs *c, struct bch_dev *ca)
|
||||
{
|
||||
unsigned data;
|
||||
int ret = 0;
|
||||
|
||||
mutex_lock(&c->state_lock);
|
||||
|
||||
if (ca->mi.state == BCH_MEMBER_STATE_RW &&
|
||||
bch2_dev_is_online(ca)) {
|
||||
bch_err(ca, "Cannot migrate data off RW device");
|
||||
ret = -EINVAL;
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = bch2_dev_data_migrate(c, ca, 0);
|
||||
if (ret) {
|
||||
bch_err(ca, "Error migrating data: %i", ret);
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = bch2_journal_flush_device(&c->journal, ca->dev_idx);
|
||||
if (ret) {
|
||||
bch_err(ca, "Migrate failed: error %i flushing journal", ret);
|
||||
goto err;
|
||||
}
|
||||
|
||||
data = bch2_dev_has_data(c, ca);
|
||||
if (data) {
|
||||
char buf[100];
|
||||
|
||||
bch2_scnprint_flag_list(buf, sizeof(buf),
|
||||
bch2_data_types, data);
|
||||
bch_err(ca, "Migrate failed, still has data (%s)", buf);
|
||||
ret = -EINVAL;
|
||||
goto err;
|
||||
}
|
||||
err:
|
||||
mutex_unlock(&c->state_lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int bch2_dev_resize(struct bch_fs *c, struct bch_dev *ca, u64 nbuckets)
|
||||
{
|
||||
struct bch_member *mi;
|
||||
|
@ -30,7 +30,7 @@ static inline bool bch2_dev_is_online(struct bch_dev *ca)
|
||||
return ca->disk_sb.bdev != NULL;
|
||||
}
|
||||
|
||||
static inline unsigned dev_mask_nr(struct bch_devs_mask *devs)
|
||||
static inline unsigned dev_mask_nr(const struct bch_devs_mask *devs)
|
||||
{
|
||||
return bitmap_weight(devs->d, BCH_SB_MEMBERS_MAX);
|
||||
}
|
||||
@ -68,7 +68,7 @@ static inline void bch2_dev_list_add_dev(struct bch_devs_list *devs,
|
||||
}
|
||||
|
||||
static inline struct bch_dev *__bch2_next_dev(struct bch_fs *c, unsigned *iter,
|
||||
struct bch_devs_mask *mask)
|
||||
const struct bch_devs_mask *mask)
|
||||
{
|
||||
struct bch_dev *ca = NULL;
|
||||
|
||||
@ -188,7 +188,6 @@ int bch2_dev_remove(struct bch_fs *, struct bch_dev *, int);
|
||||
int bch2_dev_add(struct bch_fs *, const char *);
|
||||
int bch2_dev_online(struct bch_fs *, const char *);
|
||||
int bch2_dev_offline(struct bch_fs *, struct bch_dev *, int);
|
||||
int bch2_dev_evacuate(struct bch_fs *, struct bch_dev *);
|
||||
int bch2_dev_resize(struct bch_fs *, struct bch_dev *, u64);
|
||||
|
||||
bool bch2_fs_emergency_read_only(struct bch_fs *);
|
||||
|
@ -22,6 +22,7 @@ struct bch_member_cpu {
|
||||
u64 nbuckets; /* device size */
|
||||
u16 first_bucket; /* index of first bucket used */
|
||||
u16 bucket_size; /* sectors */
|
||||
u16 group;
|
||||
u8 state;
|
||||
u8 tier;
|
||||
u8 replacement;
|
||||
@ -42,4 +43,15 @@ struct bch_replicas_cpu {
|
||||
struct bch_replicas_cpu_entry entries[];
|
||||
};
|
||||
|
||||
struct bch_disk_group_cpu {
|
||||
struct bch_devs_mask devs;
|
||||
bool deleted;
|
||||
};
|
||||
|
||||
struct bch_disk_groups_cpu {
|
||||
struct rcu_head rcu;
|
||||
unsigned nr;
|
||||
struct bch_disk_group_cpu entries[];
|
||||
};
|
||||
|
||||
#endif /* _BCACHEFS_SUPER_TYPES_H */
|
||||
|
@ -44,6 +44,7 @@ static int bch2_tiering_thread(void *arg)
|
||||
unsigned long last;
|
||||
unsigned i, nr_devices;
|
||||
|
||||
memset(&move_stats, 0, sizeof(move_stats));
|
||||
set_freezable();
|
||||
|
||||
while (!kthread_should_stop()) {
|
||||
@ -91,6 +92,7 @@ static int bch2_tiering_thread(void *arg)
|
||||
writepoint_ptr(&tier->wp),
|
||||
0,
|
||||
-1,
|
||||
POS_MIN, POS_MAX,
|
||||
tiering_pred, tier,
|
||||
&move_stats);
|
||||
}
|
||||
|
@ -163,6 +163,15 @@ struct bio *bio_split(struct bio *bio, int sectors,
|
||||
return split;
|
||||
}
|
||||
|
||||
void bio_free_pages(struct bio *bio)
|
||||
{
|
||||
struct bio_vec *bvec;
|
||||
int i;
|
||||
|
||||
bio_for_each_segment_all(bvec, bio, i)
|
||||
__free_page(bvec->bv_page);
|
||||
}
|
||||
|
||||
int bio_alloc_pages(struct bio *bio, gfp_t gfp_mask)
|
||||
{
|
||||
int i;
|
||||
|
Loading…
Reference in New Issue
Block a user