-BCACHE_REVISION=c1f1a9e1d9b9664db9c9c03cbac455c2750335bc
+BCACHE_REVISION=206668e86912eea889b3f2aaeaac7433da6f9245
"Commands for managing a specific device in a filesystem:\n"
" device_show Show information about a formatted device\n"
" device_add Add a device to an existing (running) filesystem\n"
+ " device_fail Mark a device as failed\n"
" device_remove Remove a device from an existing (running) filesystem\n"
"\n"
"Repair:\n"
return cmd_device_show(argc, argv);
if (!strcmp(cmd, "device_add"))
return cmd_device_add(argc, argv);
+ if (!strcmp(cmd, "device_fail"))
+ return cmd_device_fail(argc, argv);
if (!strcmp(cmd, "device_remove"))
return cmd_device_remove(argc, argv);
#include "cmds.h"
#include "libbcache.h"
#include "linux/bcache-ioctl.h"
+#include "tools-util.h"
/* This code belongs under show_fs */
#if 0
.dev = (__u64) argv[i],
};
- if (ioctl(fs.ioctl_fd, BCH_IOCTL_DISK_ADD, &ia))
- die("BCH_IOCTL_DISK_ADD error: %s", strerror(errno));
+ xioctl(fs.ioctl_fd, BCH_IOCTL_DISK_ADD, &ia);
}
return 0;
}
-static void usage(void)
+static void device_fail_usage(void)
+{
+ puts("bcache device_fail - mark a device as failed\n"
+ "Usage: bcache device_fail filesystem [devices]\n"
+ "\n"
+ "Options:\n"
+ " -f, --force Force removal, even if some data\n"
+ " couldn't be migrated\n"
+ " --force-metadata Force removal, even if some metadata\n"
+ " couldn't be migrated\n"
+ " -h, --help display this help and exit\n"
+ "Report bugs to <linux-bcache@vger.kernel.org>");
+ exit(EXIT_SUCCESS);
+}
+
+int cmd_device_fail(int argc, char *argv[])
+{
+ static const struct option longopts[] = {
+ { "force-degraded", 0, NULL, 'f' },
+ //{ "force-data-lost", 0, NULL, 'F' },
+ //{ "force-metadata-lost", 0, NULL, 'F' },
+ { "help", 0, NULL, 'h' },
+ { NULL }
+ };
+ int opt, force_degraded = 0, force_data = 0, force_metadata = 0;
+
+ while ((opt = getopt_long(argc, argv, "fh", longopts, NULL)) != -1)
+ switch (opt) {
+ case 'f':
+ force_degraded = 1;
+ break;
+ case 'h':
+ device_fail_usage();
+ }
+
+ if (argc - optind < 2)
+ die("Please supply a filesystem and at least one device to fail");
+
+ struct bcache_handle fs = bcache_fs_open(argv[optind]);
+
+ for (unsigned i = optind + 1; i < argc; i++) {
+ struct bch_ioctl_disk_set_state ir = {
+ .dev = (__u64) argv[i],
+ .new_state = BCH_MEMBER_STATE_FAILED,
+ };
+
+ if (force_degraded)
+ ir.flags |= BCH_FORCE_IF_DEGRADED;
+ if (force_data)
+ ir.flags |= BCH_FORCE_IF_DATA_LOST;
+ if (force_metadata)
+ ir.flags |= BCH_FORCE_IF_METADATA_LOST;
+
+ xioctl(fs.ioctl_fd, BCH_IOCTL_DISK_SET_STATE, &ir);
+ }
+
+ return 0;
+}
+
+static void device_remove_usage(void)
{
puts("bcache device_remove - remove one or more devices from a filesystem\n"
"Usage: bcache device_remove filesystem [devices]\n"
force_metadata = 1;
break;
case 'h':
- usage();
+ device_remove_usage();
}
- if (argc < 3)
- die("Please supply a filesystem and at least one device to add");
+ if (argc - optind < 2)
+ die("Please supply a filesystem and at least one device to remove");
- struct bcache_handle fs = bcache_fs_open(argv[1]);
+ struct bcache_handle fs = bcache_fs_open(argv[optind]);
- for (unsigned i = 2; i < argc; i++) {
+ for (unsigned i = optind + 1; i < argc; i++) {
struct bch_ioctl_disk_remove ir = {
.dev = (__u64) argv[i],
};
if (force_data)
- ir.flags |= BCH_FORCE_IF_DATA_MISSING;
+ ir.flags |= BCH_FORCE_IF_DATA_LOST;
if (force_metadata)
- ir.flags |= BCH_FORCE_IF_METADATA_MISSING;
+ ir.flags |= BCH_FORCE_IF_METADATA_LOST;
- if (ioctl(fs.ioctl_fd, BCH_IOCTL_DISK_REMOVE, &ir))
- die("BCH_IOCTL_DISK_REMOVE error: %s\n", strerror(errno));
+ xioctl(fs.ioctl_fd, BCH_IOCTL_DISK_REMOVE, &ir);
}
return 0;
x(0, metadata_checksum_type, "(none|crc32c|crc64)", NULL) \
x(0, data_checksum_type, "(none|crc32c|crc64)", NULL) \
x(0, compression_type, "(none|lz4|gzip)", NULL) \
+x(0, data_replicas, "#", NULL) \
+x(0, metadata_replicas, "#", NULL) \
x(0, encrypted, NULL, "Enable whole filesystem encryption (chacha20/poly1305)")\
x(0, no_passphrase, NULL, "Don't encrypt master encryption key")\
x('e', error_action, "(continue|readonly|panic)", NULL) \
" --metadata_checksum_type=(none|crc32c|crc64)\n"
" --data_checksum_type=(none|crc32c|crc64)\n"
" --compression_type=(none|lz4|gzip)\n"
+ " --data_replicas=# Number of data replicas\n"
+ " --metadata_replicas=# Number of metadata replicas\n"
" --encrypted Enable whole filesystem encryption (chacha20/poly1305)\n"
" --no_passphrase Don't encrypt master encryption key\n"
" --error_action=(continue|readonly|panic)\n"
}
enum {
- Opt_no_opt = 1,
+ O_no_opt = 1,
#define t(text)
-#define x(shortopt, longopt, arg, help) Opt_##longopt,
+#define x(shortopt, longopt, arg, help) O_##longopt,
OPTS
#undef x
#undef t
.name = #longopt, \
.has_arg = arg ? required_argument : no_argument, \
.flag = NULL, \
- .val = Opt_##longopt, \
+ .val = O_##longopt, \
},
OPTS
#undef x
format_opts,
NULL)) != -1)
switch (opt) {
- case Opt_block_size:
+ case O_block_size:
case 'b':
opts.block_size =
hatoi_validate(optarg, "block size");
break;
- case Opt_btree_node_size:
+ case O_btree_node_size:
opts.btree_node_size =
hatoi_validate(optarg, "btree node size");
break;
- case Opt_metadata_checksum_type:
+ case O_metadata_checksum_type:
opts.meta_csum_type =
read_string_list_or_die(optarg,
bch_csum_types, "checksum type");
break;
- case Opt_data_checksum_type:
+ case O_data_checksum_type:
opts.data_csum_type =
read_string_list_or_die(optarg,
bch_csum_types, "checksum type");
break;
- case Opt_compression_type:
+ case O_compression_type:
opts.compression_type =
read_string_list_or_die(optarg,
bch_compression_types,
"compression type");
break;
- case Opt_encrypted:
+ case O_data_replicas:
+ if (kstrtouint(optarg, 10, &opts.data_replicas) ||
+ dev_opts.tier >= BCH_REPLICAS_MAX)
+ die("invalid replicas");
+ break;
+ case O_metadata_replicas:
+ if (kstrtouint(optarg, 10, &opts.meta_replicas) ||
+ dev_opts.tier >= BCH_REPLICAS_MAX)
+ die("invalid replicas");
+ break;
+ case O_encrypted:
opts.encrypted = true;
break;
- case Opt_no_passphrase:
+ case O_no_passphrase:
no_passphrase = true;
break;
- case Opt_error_action:
+ case O_error_action:
case 'e':
opts.on_error_action =
read_string_list_or_die(optarg,
bch_error_actions, "error action");
break;
- case Opt_max_journal_entry_size:
+ case O_max_journal_entry_size:
opts.max_journal_entry_size =
hatoi_validate(optarg, "journal entry size");
break;
- case Opt_label:
+ case O_label:
case 'L':
opts.label = strdup(optarg);
break;
- case Opt_uuid:
+ case O_uuid:
case 'U':
if (uuid_parse(optarg, opts.uuid.b))
die("Bad uuid");
break;
- case Opt_force:
+ case O_force:
case 'f':
force = true;
break;
- case Opt_fs_size:
+ case O_fs_size:
if (bch_strtoull_h(optarg, &dev_opts.size))
die("invalid filesystem size");
dev_opts.size >>= 9;
break;
- case Opt_bucket_size:
+ case O_bucket_size:
dev_opts.bucket_size =
hatoi_validate(optarg, "bucket size");
break;
- case Opt_tier:
+ case O_tier:
case 't':
if (kstrtouint(optarg, 10, &dev_opts.tier) ||
dev_opts.tier >= BCH_TIER_MAX)
die("invalid tier");
break;
- case Opt_discard:
+ case O_discard:
dev_opts.discard = true;
break;
- case Opt_no_opt:
+ case O_no_opt:
dev_opts.path = strdup(optarg);
darray_append(devices, dev_opts);
dev_opts.size = 0;
break;
- case Opt_help:
+ case O_help:
case 'h':
usage();
exit(EXIT_SUCCESS);
int cmd_device_show(int argc, char *argv[]);
int cmd_device_add(int argc, char *argv[]);
+int cmd_device_fail(int argc, char *argv[]);
int cmd_device_remove(int argc, char *argv[]);
int cmd_fsck(int argc, char *argv[]);
/* global control dev: */
-#define BCH_FORCE_IF_DATA_MISSING (1 << 0)
-#define BCH_FORCE_IF_METADATA_MISSING (1 << 1)
+#define BCH_FORCE_IF_DATA_LOST (1 << 0)
+#define BCH_FORCE_IF_METADATA_LOST (1 << 1)
+#define BCH_FORCE_IF_DATA_DEGRADED (1 << 2)
+#define BCH_FORCE_IF_METADATA_DEGRADED (1 << 3)
+
+#define BCH_FORCE_IF_DEGRADED \
+ (BCH_FORCE_IF_DATA_DEGRADED| \
+ BCH_FORCE_IF_METADATA_DEGRADED)
#define BCH_IOCTL_ASSEMBLE _IOW('r', 1, struct bch_ioctl_assemble)
#define BCH_IOCTL_INCREMENTAL _IOW('r', 1, struct bch_ioctl_incremental)
#define BCH_IOCTL_DISK_ADD _IOW('r', 4, struct bch_ioctl_disk_add)
#define BCH_IOCTL_DISK_REMOVE _IOW('r', 5, struct bch_ioctl_disk_remove)
-#define BCH_IOCTL_DISK_FAIL _IOW('r', 6, struct bch_ioctl_disk_fail)
+#define BCH_IOCTL_DISK_SET_STATE _IOW('r', 6, struct bch_ioctl_disk_set_state)
#define BCH_IOCTL_DISK_REMOVE_BY_UUID \
_IOW('r', 5, struct bch_ioctl_disk_remove_by_uuid)
__u64 dev;
};
-struct bch_ioctl_disk_fail {
+struct bch_ioctl_disk_set_state {
__u32 flags;
- __u32 pad;
+ __u8 new_state;
+ __u8 pad[3];
__u64 dev;
};
LE64_BITMASK(BCH_SB_ENCRYPTION_TYPE, struct bch_sb, flags[1], 10, 14);
LE64_BITMASK(BCH_SB_JOURNAL_ENTRY_SIZE, struct bch_sb, flags[1], 14, 20);
+LE64_BITMASK(BCH_SB_META_REPLICAS_REQ, struct bch_sb, flags[1], 20, 24);
+LE64_BITMASK(BCH_SB_DATA_REPLICAS_REQ, struct bch_sb, flags[1], 24, 28);
+
/* Features: */
enum bch_sb_features {
BCH_FEATURE_LZ4 = 0,
SET_BCH_SB_GC_RESERVE(sb, 8);
SET_BCH_SB_META_REPLICAS_WANT(sb, opts.meta_replicas);
SET_BCH_SB_META_REPLICAS_HAVE(sb, opts.meta_replicas);
+ SET_BCH_SB_META_REPLICAS_REQ(sb, opts.meta_replicas_required);
SET_BCH_SB_DATA_REPLICAS_WANT(sb, opts.data_replicas);
SET_BCH_SB_DATA_REPLICAS_HAVE(sb, opts.data_replicas);
+ SET_BCH_SB_DATA_REPLICAS_REQ(sb, opts.data_replicas_required);
SET_BCH_SB_ERROR_ACTION(sb, opts.on_error_action);
SET_BCH_SB_STR_HASH_TYPE(sb, BCH_STR_HASH_SIPHASH);
SET_BCH_SB_JOURNAL_ENTRY_SIZE(sb, ilog2(opts.max_journal_entry_size));
unsigned meta_replicas;
unsigned data_replicas;
+ unsigned meta_replicas_required;
+ unsigned data_replicas_required;
+
unsigned meta_csum_type;
unsigned data_csum_type;
unsigned compression_type;
.data_csum_type = BCH_CSUM_CRC32C,
.meta_replicas = 1,
.data_replicas = 1,
+ .meta_replicas_required = 1,
+ .data_replicas_required = 1,
};
}
-1);
group_for_each_cache_rcu(ca, &c->tiers[i].devs, iter) {
- struct bucket_stats_cache stats = bch_bucket_stats_read_cache(ca);
+ struct bch_dev_usage stats = bch_dev_usage_read(ca);
unsigned bucket_bits = ca->bucket_bits + 9;
u64 size = (ca->mi.nbuckets -
struct cache_member_rcu *mi = cache_member_info_get(c);
unsigned i, sectors_free = UINT_MAX;
- BUG_ON(nr_replicas > ob->nr_ptrs);
-
- for (i = 0; i < nr_replicas; i++)
+ for (i = 0; i < min(nr_replicas, ob->nr_ptrs); i++)
sectors_free = min(sectors_free,
ob_ptr_sectors_free(ob, mi, &ob->ptrs[i]));
struct write_point *wp,
struct open_bucket *ob,
unsigned nr_replicas,
+ unsigned nr_replicas_required,
enum alloc_reserve reserve,
struct closure *cl)
{
long caches_used[BITS_TO_LONGS(BCH_SB_MEMBERS_MAX)];
- int i, dst;
+ unsigned i;
+ int ret;
/*
* We might be allocating pointers to add to an existing extent
memset(caches_used, 0, sizeof(caches_used));
- /*
- * Shuffle pointers to devices we already have to the end:
- * bch_bucket_alloc_set() will add new pointers to the statr of @b, and
- * bch_alloc_sectors_done() will add the first nr_replicas ptrs to @e:
- */
- for (i = dst = ob->nr_ptrs - 1; i >= 0; --i)
- if (__test_and_set_bit(ob->ptrs[i].dev, caches_used)) {
- if (i != dst) {
- swap(ob->ptrs[i], ob->ptrs[dst]);
- swap(ob->ptr_offset[i], ob->ptr_offset[dst]);
- }
- --dst;
- nr_replicas++;
- }
+ for (i = 0; i < ob->nr_ptrs; i++)
+ __set_bit(ob->ptrs[i].dev, caches_used);
+
+ ret = bch_bucket_alloc_set(c, wp, ob, nr_replicas,
+ reserve, caches_used, cl);
+
+ if (ret == -EROFS &&
+ ob->nr_ptrs >= nr_replicas_required)
+ ret = 0;
- return bch_bucket_alloc_set(c, wp, ob, nr_replicas,
- reserve, caches_used, cl);
+ return ret;
}
/*
struct open_bucket *bch_alloc_sectors_start(struct cache_set *c,
struct write_point *wp,
unsigned nr_replicas,
+ unsigned nr_replicas_required,
enum alloc_reserve reserve,
struct closure *cl)
{
}
ret = open_bucket_add_buckets(c, wp, ob, nr_replicas,
+ nr_replicas_required,
reserve, cl);
if (ret) {
mutex_unlock(&ob->lock);
* __bch_write() will only write to the pointers we add here:
*/
- /*
- * XXX: don't add pointers to devices @e already has
- */
- BUG_ON(nr_replicas > ob->nr_ptrs);
BUG_ON(sectors > ob->sectors_free);
/* didn't use all the ptrs: */
rcu_read_lock();
- for (i = 0; i < nr_replicas; i++) {
+ for (i = 0; i < min(ob->nr_ptrs, nr_replicas); i++) {
EBUG_ON(bch_extent_has_device(extent_i_to_s_c(e), ob->ptrs[i].dev));
tmp = ob->ptrs[i];
struct write_point *wp,
struct bkey_i_extent *e,
unsigned nr_replicas,
+ unsigned nr_replicas_required,
enum alloc_reserve reserve,
struct closure *cl)
{
struct open_bucket *ob;
- ob = bch_alloc_sectors_start(c, wp, nr_replicas, reserve, cl);
+ ob = bch_alloc_sectors_start(c, wp, nr_replicas,
+ nr_replicas_required,
+ reserve, cl);
if (IS_ERR_OR_NULL(ob))
return ob;
struct open_bucket *bch_alloc_sectors_start(struct cache_set *,
struct write_point *,
- unsigned, enum alloc_reserve,
+ unsigned, unsigned,
+ enum alloc_reserve,
struct closure *);
void bch_alloc_sectors_append_ptrs(struct cache_set *, struct bkey_i_extent *,
struct open_bucket *);
struct open_bucket *bch_alloc_sectors(struct cache_set *, struct write_point *,
- struct bkey_i_extent *, unsigned,
+ struct bkey_i_extent *, unsigned, unsigned,
enum alloc_reserve, struct closure *);
static inline void bch_wake_allocator(struct cache *ca)
struct cache_member_cpu m[];
};
-/* cache->flags: */
-enum {
- BCH_DEV_REMOVING,
- BCH_DEV_FORCE_REMOVE,
-};
-
struct cache {
struct percpu_ref ref;
struct rcu_head free_rcu;
struct work_struct free_work;
- struct work_struct remove_work;
- unsigned long flags;
struct cache_set *set;
* second contains a saved copy of the stats from the beginning
* of GC.
*/
- struct bucket_stats_cache __percpu *bucket_stats_percpu;
- struct bucket_stats_cache bucket_stats_cached;
+ struct bch_dev_usage __percpu *bucket_stats_percpu;
+ struct bch_dev_usage bucket_stats_cached;
atomic_long_t saturated_count;
size_t inc_gen_needs_gc;
atomic64_t sectors_available;
- struct bucket_stats_cache_set __percpu *bucket_stats_percpu;
- struct bucket_stats_cache_set bucket_stats_cached;
+ struct bch_fs_usage __percpu *bucket_stats_percpu;
+ struct bch_fs_usage bucket_stats_cached;
struct lglock bucket_stats_lock;
struct mutex bucket_lock;
/* Also see bch_pending_btree_node_free_insert_done() */
static void bch_mark_pending_btree_node_frees(struct cache_set *c)
{
- struct bucket_stats_cache_set stats = { 0 };
+ struct bch_fs_usage stats = { 0 };
struct btree_interior_update *as;
struct pending_btree_node_free *d;
/* Save a copy of the existing bucket stats while we recompute them: */
for_each_cache(ca, c, i) {
- ca->bucket_stats_cached = __bch_bucket_stats_read_cache(ca);
+ ca->bucket_stats_cached = __bch_dev_usage_read(ca);
for_each_possible_cpu(cpu) {
- struct bucket_stats_cache *p =
+ struct bch_dev_usage *p =
per_cpu_ptr(ca->bucket_stats_percpu, cpu);
memset(p, 0, sizeof(*p));
}
}
- c->bucket_stats_cached = __bch_bucket_stats_read_cache_set(c);
+ c->bucket_stats_cached = __bch_fs_usage_read(c);
for_each_possible_cpu(cpu) {
- struct bucket_stats_cache_set *p =
+ struct bch_fs_usage *p =
per_cpu_ptr(c->bucket_stats_percpu, cpu);
memset(p->s, 0, sizeof(p->s));
*/
struct btree_iter;
-struct bucket_stats_cache_set;
struct btree_node_iter;
enum extent_insert_hook_ret {
*/
static void bch_btree_node_free_index(struct cache_set *c, struct btree *b,
enum btree_id id, struct bkey_s_c k,
- struct bucket_stats_cache_set *stats)
+ struct bch_fs_usage *stats)
{
struct btree_interior_update *as;
struct pending_btree_node_free *d;
* moving this reference from, hence one comparison here:
*/
if (gc_pos_cmp(c->gc_pos, gc_phase(GC_PHASE_PENDING_DELETE)) < 0) {
- struct bucket_stats_cache_set tmp = { 0 };
+ struct bch_fs_usage tmp = { 0 };
bch_mark_key(c, bkey_i_to_s_c(&d->key),
-c->sb.btree_node_size, true, b
static void bch_btree_node_free_ondisk(struct cache_set *c,
struct pending_btree_node_free *pending)
{
- struct bucket_stats_cache_set stats = { 0 };
+ struct bch_fs_usage stats = { 0 };
BUG_ON(!pending->index_update_done);
ob = bch_alloc_sectors(c, &c->btree_write_point,
bkey_i_to_extent(&tmp.k),
res->nr_replicas,
+ c->opts.metadata_replicas_required,
use_reserve ? RESERVE_BTREE : RESERVE_NONE,
cl);
if (IS_ERR(ob))
* bch_btree_root_read()) - do marking while holding
* btree_root_lock:
*/
- struct bucket_stats_cache_set stats = { 0 };
+ struct bch_fs_usage stats = { 0 };
bch_mark_key(c, bkey_i_to_s_c(&b->key),
c->sb.btree_node_size, true,
struct disk_reservation *disk_res)
{
struct cache_set *c = iter->c;
- struct bucket_stats_cache_set stats = { 0 };
+ struct bch_fs_usage stats = { 0 };
struct bkey_packed *k;
struct bkey tmp;
static void bch_fs_stats_verify(struct cache_set *c)
{
- struct bucket_stats_cache_set stats =
- __bch_bucket_stats_read_cache_set(c);
+ struct bch_fs_usage stats =
+ __bch_fs_usage_read(c);
if ((s64) stats.sectors_dirty < 0)
panic("sectors_dirty underflow: %lli\n", stats.sectors_dirty);
_ret; \
})
-struct bucket_stats_cache __bch_bucket_stats_read_cache(struct cache *ca)
+struct bch_dev_usage __bch_dev_usage_read(struct cache *ca)
{
return bucket_stats_read_raw(ca->bucket_stats_percpu);
}
-struct bucket_stats_cache bch_bucket_stats_read_cache(struct cache *ca)
+struct bch_dev_usage bch_dev_usage_read(struct cache *ca)
{
return bucket_stats_read_cached(ca->set,
ca->bucket_stats_cached,
ca->bucket_stats_percpu);
}
-struct bucket_stats_cache_set
-__bch_bucket_stats_read_cache_set(struct cache_set *c)
+struct bch_fs_usage
+__bch_fs_usage_read(struct cache_set *c)
{
return bucket_stats_read_raw(c->bucket_stats_percpu);
}
-struct bucket_stats_cache_set
-bch_bucket_stats_read_cache_set(struct cache_set *c)
+struct bch_fs_usage
+bch_fs_usage_read(struct cache_set *c)
{
return bucket_stats_read_cached(c,
c->bucket_stats_cached,
}
void bch_fs_stats_apply(struct cache_set *c,
- struct bucket_stats_cache_set *stats,
+ struct bch_fs_usage *stats,
struct disk_reservation *disk_res,
struct gc_pos gc_pos)
{
}
static void bucket_stats_update(struct cache *ca,
- struct bucket_mark old, struct bucket_mark new,
- struct bucket_stats_cache_set *bch_alloc_stats)
+ struct bucket_mark old, struct bucket_mark new,
+ struct bch_fs_usage *bch_alloc_stats)
{
struct cache_set *c = ca->set;
- struct bucket_stats_cache *cache_stats;
+ struct bch_dev_usage *cache_stats;
bch_fs_inconsistent_on(old.data_type && new.data_type &&
old.data_type != new.data_type, c,
#define bucket_data_cmpxchg(ca, g, new, expr) \
({ \
- struct bucket_stats_cache_set _stats = { 0 }; \
+ struct bch_fs_usage _stats = { 0 }; \
struct bucket_mark _old = bucket_cmpxchg(g, new, expr); \
\
bucket_stats_update(ca, _old, new, &_stats); \
void bch_invalidate_bucket(struct cache *ca, struct bucket *g)
{
- struct bucket_stats_cache_set stats = { 0 };
+ struct bch_fs_usage stats = { 0 };
struct bucket_mark old, new;
old = bucket_cmpxchg(g, new, ({
*/
static void bch_mark_pointer(struct cache_set *c,
struct bkey_s_c_extent e,
- struct cache *ca,
const union bch_extent_crc *crc,
const struct bch_extent_ptr *ptr,
s64 sectors, enum s_alloc type,
bool may_make_unavailable,
- struct bucket_stats_cache_set *stats,
+ struct bch_fs_usage *stats,
bool gc_will_visit, u64 journal_seq)
{
struct bucket_mark old, new;
unsigned saturated;
- struct bucket *g = ca->buckets + PTR_BUCKET_NR(ca, ptr);
- u64 v = READ_ONCE(g->_mark.counter);
+ struct cache *ca;
+ struct bucket *g;
+ u64 v;
unsigned old_sectors, new_sectors;
int disk_sectors, compressed_sectors;
compressed_sectors = -__compressed_sectors(crc, old_sectors)
+ __compressed_sectors(crc, new_sectors);
+ ca = PTR_CACHE(c, ptr);
+ if (!ca)
+ goto out;
+
+ g = ca->buckets + PTR_BUCKET_NR(ca, ptr);
+
if (gc_will_visit) {
if (journal_seq)
bucket_cmpxchg(g, new, new.journal_seq = journal_seq);
goto out;
}
+ v = READ_ONCE(g->_mark.counter);
do {
new.counter = old.counter = v;
saturated = 0;
static void bch_mark_extent(struct cache_set *c, struct bkey_s_c_extent e,
s64 sectors, bool metadata,
bool may_make_unavailable,
- struct bucket_stats_cache_set *stats,
+ struct bch_fs_usage *stats,
bool gc_will_visit, u64 journal_seq)
{
const struct bch_extent_ptr *ptr;
const union bch_extent_crc *crc;
- struct cache *ca;
enum s_alloc type = metadata ? S_META : S_DIRTY;
BUG_ON(metadata && bkey_extent_is_cached(e.k));
BUG_ON(!sectors);
rcu_read_lock();
- extent_for_each_online_device_crc(c, e, crc, ptr, ca) {
- trace_bcache_mark_bucket(ca, e.k, ptr, sectors, !ptr->cached);
-
- bch_mark_pointer(c, e, ca, crc, ptr, sectors,
+ extent_for_each_ptr_crc(e, ptr, crc)
+ bch_mark_pointer(c, e, crc, ptr, sectors,
ptr->cached ? S_CACHED : type,
may_make_unavailable,
stats, gc_will_visit, journal_seq);
- }
rcu_read_unlock();
}
static void __bch_mark_key(struct cache_set *c, struct bkey_s_c k,
s64 sectors, bool metadata,
bool may_make_unavailable,
- struct bucket_stats_cache_set *stats,
+ struct bch_fs_usage *stats,
bool gc_will_visit, u64 journal_seq)
{
switch (k.k->type) {
void __bch_gc_mark_key(struct cache_set *c, struct bkey_s_c k,
s64 sectors, bool metadata,
- struct bucket_stats_cache_set *stats)
+ struct bch_fs_usage *stats)
{
__bch_mark_key(c, k, sectors, metadata, true, stats, false, 0);
}
void bch_gc_mark_key(struct cache_set *c, struct bkey_s_c k,
s64 sectors, bool metadata)
{
- struct bucket_stats_cache_set stats = { 0 };
+ struct bch_fs_usage stats = { 0 };
__bch_gc_mark_key(c, k, sectors, metadata, &stats);
void bch_mark_key(struct cache_set *c, struct bkey_s_c k,
s64 sectors, bool metadata, struct gc_pos gc_pos,
- struct bucket_stats_cache_set *stats, u64 journal_seq)
+ struct bch_fs_usage *stats, u64 journal_seq)
{
/*
* synchronization w.r.t. GC:
struct disk_reservation *res,
unsigned sectors, int flags)
{
- struct bucket_stats_cache_set *stats;
+ struct bch_fs_usage *stats;
u64 old, new, v;
s64 sectors_available;
int ret;
/* Per device stats: */
-struct bucket_stats_cache __bch_bucket_stats_read_cache(struct cache *);
-struct bucket_stats_cache bch_bucket_stats_read_cache(struct cache *);
+struct bch_dev_usage __bch_dev_usage_read(struct cache *);
+struct bch_dev_usage bch_dev_usage_read(struct cache *);
static inline u64 __buckets_available_cache(struct cache *ca,
- struct bucket_stats_cache stats)
+ struct bch_dev_usage stats)
{
return max_t(s64, 0,
ca->mi.nbuckets - ca->mi.first_bucket -
*/
static inline u64 buckets_available_cache(struct cache *ca)
{
- return __buckets_available_cache(ca, bch_bucket_stats_read_cache(ca));
+ return __buckets_available_cache(ca, bch_dev_usage_read(ca));
}
static inline u64 __buckets_free_cache(struct cache *ca,
- struct bucket_stats_cache stats)
+ struct bch_dev_usage stats)
{
return __buckets_available_cache(ca, stats) +
fifo_used(&ca->free[RESERVE_NONE]) +
static inline u64 buckets_free_cache(struct cache *ca)
{
- return __buckets_free_cache(ca, bch_bucket_stats_read_cache(ca));
+ return __buckets_free_cache(ca, bch_dev_usage_read(ca));
}
/* Cache set stats: */
-struct bucket_stats_cache_set __bch_bucket_stats_read_cache_set(struct cache_set *);
-struct bucket_stats_cache_set bch_bucket_stats_read_cache_set(struct cache_set *);
-void bch_fs_stats_apply(struct cache_set *,
- struct bucket_stats_cache_set *,
- struct disk_reservation *,
- struct gc_pos);
+struct bch_fs_usage __bch_fs_usage_read(struct cache_set *);
+struct bch_fs_usage bch_fs_usage_read(struct cache_set *);
+void bch_fs_stats_apply(struct cache_set *, struct bch_fs_usage *,
+ struct disk_reservation *, struct gc_pos);
static inline u64 __bch_fs_sectors_used(struct cache_set *c)
{
- struct bucket_stats_cache_set stats = __bch_bucket_stats_read_cache_set(c);
+ struct bch_fs_usage stats = __bch_fs_usage_read(c);
u64 reserved = stats.persistent_reserved +
stats.online_reserved;
enum bucket_data_type, bool);
void __bch_gc_mark_key(struct cache_set *, struct bkey_s_c, s64, bool,
- struct bucket_stats_cache_set *);
+ struct bch_fs_usage *);
void bch_gc_mark_key(struct cache_set *, struct bkey_s_c, s64, bool);
void bch_mark_key(struct cache_set *, struct bkey_s_c, s64, bool,
- struct gc_pos, struct bucket_stats_cache_set *, u64);
+ struct gc_pos, struct bch_fs_usage *, u64);
void bch_recalc_sectors_available(struct cache_set *);
};
};
-struct bucket_stats_cache {
+struct bch_dev_usage {
u64 buckets_dirty;
u64 buckets_cached;
u64 buckets_meta;
S_COMPRESSED_NR,
};
-struct bucket_stats_cache_set {
+struct bch_fs_usage {
/* all fields are in units of 512 byte sectors: */
u64 s[S_COMPRESSED_NR][S_ALLOC_NR];
u64 persistent_reserved;
if (IS_ERR(ca))
return PTR_ERR(ca);
- ret = bch_dev_remove(ca, arg.flags & BCH_FORCE_IF_DATA_MISSING)
- ? 0 : -EBUSY;
+ ret = bch_dev_remove(c, ca, arg.flags);
percpu_ref_put(&ca->ref);
return ret;
}
-static long bch_ioctl_disk_fail(struct cache_set *c,
- struct bch_ioctl_disk_fail __user *user_arg)
+static long bch_ioctl_disk_set_state(struct cache_set *c,
+ struct bch_ioctl_disk_set_state __user *user_arg)
{
- struct bch_ioctl_disk_fail arg;
+ struct bch_ioctl_disk_set_state arg;
struct cache *ca;
int ret;
if (IS_ERR(ca))
return PTR_ERR(ca);
- /* XXX: failed not actually implemented yet */
- ret = bch_dev_remove(ca, true);
+ ret = bch_dev_set_state(c, ca, arg.new_state, arg.flags);
percpu_ref_put(&ca->ref);
return ret;
return bch_ioctl_disk_add(c, arg);
case BCH_IOCTL_DISK_REMOVE:
return bch_ioctl_disk_remove(c, arg);
- case BCH_IOCTL_DISK_FAIL:
- return bch_ioctl_disk_fail(c, arg);
+ case BCH_IOCTL_DISK_SET_STATE:
+ return bch_ioctl_disk_set_state(c, arg);
case BCH_IOCTL_DISK_REMOVE_BY_UUID:
return bch_ioctl_disk_remove_by_uuid(c, arg);
bch_notify_dev_error(ca, true);
mutex_lock(&c->state_lock);
- dev = bch_dev_may_remove(ca);
+ dev = bch_dev_state_allowed(c, ca, BCH_MEMBER_STATE_RO,
+ BCH_FORCE_IF_DEGRADED);
if (dev
- ? bch_dev_read_only(ca)
+ ? __bch_dev_set_state(c, ca, BCH_MEMBER_STATE_RO,
+ BCH_FORCE_IF_DEGRADED)
: bch_fs_emergency_read_only(c))
bch_err(c,
"too many IO errors on %s, setting %s RO",
PTR_BUCKET_NR(ca, ptr)))
continue;
+ if (ca->mi.state == BCH_MEMBER_STATE_FAILED)
+ continue;
+
if (pick.ca && pick.ca->mi.tier < ca->mi.tier)
continue;
struct btree_insert *trans;
struct btree_insert_entry *insert;
struct bpos committed;
- struct bucket_stats_cache_set stats;
+ struct bch_fs_usage stats;
/* for deleting: */
struct bkey_i whiteout;
if (ptr_stale(ca, ptr))
continue;
+ if (ca->mi.state == BCH_MEMBER_STATE_FAILED)
+ continue;
+
if (ret->ca &&
(ca == avoid ||
ret->ca->mi.tier < ca->mi.tier))
new.reserved = 0;
});
- w->io->op.op.res.sectors += PAGE_SECTORS * (old.reserved - new.reserved);
+ w->io->op.op.res.sectors += PAGE_SECTORS *
+ (old.reserved - new.reserved) *
+ old.nr_replicas;
out:
BUG_ON(PageWriteback(page));
set_page_writeback(page);
BKEY_EXTENT_U64s_MAX))
continue_at(cl, bch_write_index, index_update_wq(op));
- b = bch_alloc_sectors_start(c, op->wp, op->nr_replicas,
+ b = bch_alloc_sectors_start(c, op->wp,
+ op->nr_replicas,
+ c->opts.data_replicas_required,
op->alloc_reserve,
(op->flags & BCH_WRITE_ALLOC_NOWAIT) ? NULL : cl);
EBUG_ON(!b);
}
rcu_read_unlock();
- if (nr_online < c->opts.metadata_replicas)
+ if (nr_online < c->opts.metadata_replicas_required)
return -EROFS;
- if (nr_devs < c->opts.metadata_replicas)
+ if (nr_devs < min_t(unsigned, nr_online, c->opts.metadata_replicas))
return 0;
return sectors_available;
closure_init_stack(&cl);
- mutex_lock(&c->sb_lock);
-
/* don't handle reducing nr of buckets yet: */
if (nr <= ja->nr)
- goto err;
+ return 0;
/*
* note: journal buckets aren't really counted as _sectors_ used yet, so
* reservation to ensure we'll actually be able to allocate:
*/
- ret = ENOSPC;
if (bch_disk_reservation_get(c, &disk_res,
(nr - ja->nr) << ca->bucket_bits, 0))
- goto err;
+ return -ENOSPC;
+
+ mutex_lock(&c->sb_lock);
ret = -ENOMEM;
new_buckets = kzalloc(nr * sizeof(u64), GFP_KERNEL);
j->prev_buf_sectors = 0;
spin_unlock(&j->lock);
- if (replicas < replicas_want)
+ if (replicas < c->opts.metadata_replicas_required)
return -EROFS;
+ BUG_ON(!replicas);
+
return 0;
}
#include "keylist.h"
#include "migrate.h"
#include "move.h"
+#include "super-io.h"
static int issue_migration_move(struct cache *ca,
struct moving_context *ctxt,
{
struct moving_context ctxt;
struct cache_set *c = ca->set;
+ struct bch_sb_field_members *mi;
unsigned pass = 0;
u64 seen_key_count;
int ret = 0;
BUG_ON(ca->mi.state == BCH_MEMBER_STATE_ACTIVE);
+ if (!ca->mi.has_data)
+ return 0;
+
bch_move_ctxt_init(&ctxt, NULL, SECTORS_IN_FLIGHT_PER_DEVICE);
ctxt.avoid = ca;
return -1;
}
+ mutex_lock(&c->sb_lock);
+ mi = bch_sb_get_members(c->disk_sb);
+ SET_BCH_MEMBER_HAS_DATA(&mi->members[ca->dev_idx], false);
+
+ bch_write_super(c);
+ mutex_unlock(&c->sb_lock);
+
return 0;
}
* is written.
*/
-int bch_move_meta_data_off_device(struct cache *ca)
+int bch_move_metadata_off_device(struct cache *ca)
{
+ struct cache_set *c = ca->set;
+ struct bch_sb_field_members *mi;
unsigned i;
int ret;
+ BUG_ON(ca->mi.state == BCH_MEMBER_STATE_ACTIVE);
+
+ if (!ca->mi.has_metadata)
+ return 0;
+
/* 1st, Move the btree nodes off the device */
for (i = 0; i < BTREE_ID_NR; i++) {
if (ret)
return ret;
+ mutex_lock(&c->sb_lock);
+ mi = bch_sb_get_members(c->disk_sb);
+ SET_BCH_MEMBER_HAS_METADATA(&mi->members[ca->dev_idx], false);
+
+ bch_write_super(c);
+ mutex_unlock(&c->sb_lock);
+
return 0;
}
* and don't have other valid pointers. If there are valid pointers,
* the necessary pointers to the removed device are replaced with
* bad pointers instead.
+ *
* This is only called if bch_move_data_off_device above failed, meaning
* that we've already tried to move the data MAX_DATA_OFF_ITER times and
* are not likely to succeed if we try again.
*/
-
int bch_flag_data_bad(struct cache *ca)
{
int ret = 0;
#define _BCACHE_MIGRATE_H
int bch_move_data_off_device(struct cache *);
-int bch_move_meta_data_off_device(struct cache *);
+int bch_move_metadata_off_device(struct cache *);
int bch_flag_data_bad(struct cache *);
#endif /* _BCACHE_MIGRATE_H */
BCH_OPT(errors, 0644, BCH_SB_ERROR_ACTION, \
s8, OPT_STR(bch_error_actions)) \
BCH_OPT(metadata_replicas, 0444, BCH_SB_META_REPLICAS_WANT,\
- s8, OPT_UINT(0, BCH_REPLICAS_MAX)) \
+ s8, OPT_UINT(1, BCH_REPLICAS_MAX)) \
BCH_OPT(data_replicas, 0444, BCH_SB_DATA_REPLICAS_WANT,\
- s8, OPT_UINT(0, BCH_REPLICAS_MAX)) \
+ s8, OPT_UINT(1, BCH_REPLICAS_MAX)) \
+ BCH_OPT(metadata_replicas_required, 0444, BCH_SB_META_REPLICAS_REQ,\
+ s8, OPT_UINT(1, BCH_REPLICAS_MAX)) \
+ BCH_OPT(data_replicas_required, 0444, BCH_SB_DATA_REPLICAS_REQ,\
+ s8, OPT_UINT(1, BCH_REPLICAS_MAX)) \
BCH_OPT(metadata_checksum, 0644, BCH_SB_META_CSUM_TYPE, \
s8, OPT_STR(bch_csum_types)) \
BCH_OPT(data_checksum, 0644, BCH_SB_DATA_CSUM_TYPE, \
BCH_SB_META_REPLICAS_WANT(sb) >= BCH_REPLICAS_MAX)
return "Invalid number of metadata replicas";
+ if (!BCH_SB_META_REPLICAS_REQ(sb) ||
+ BCH_SB_META_REPLICAS_REQ(sb) >= BCH_REPLICAS_MAX)
+ return "Invalid number of metadata replicas";
+
if (!BCH_SB_META_REPLICAS_HAVE(sb) ||
BCH_SB_META_REPLICAS_HAVE(sb) >
BCH_SB_META_REPLICAS_WANT(sb))
BCH_SB_DATA_REPLICAS_WANT(sb) >= BCH_REPLICAS_MAX)
return "Invalid number of data replicas";
+ if (!BCH_SB_DATA_REPLICAS_REQ(sb) ||
+ BCH_SB_DATA_REPLICAS_REQ(sb) >= BCH_REPLICAS_MAX)
+ return "Invalid number of metadata replicas";
+
if (!BCH_SB_DATA_REPLICAS_HAVE(sb) ||
BCH_SB_DATA_REPLICAS_HAVE(sb) >
BCH_SB_DATA_REPLICAS_WANT(sb))
struct bch_member *mi;
struct bkey_s_c_extent e = bkey_i_to_s_c_extent(k);
const struct bch_extent_ptr *ptr;
+ unsigned nr_replicas = 0;
mutex_lock(&c->sb_lock);
mi = bch_sb_get_members(c->disk_sb)->members;
extent_for_each_ptr(e, ptr)
- if (!ptr->cached)
+ if (!ptr->cached) {
(meta
? SET_BCH_MEMBER_HAS_METADATA
: SET_BCH_MEMBER_HAS_DATA)(mi + ptr->dev, true);
+ nr_replicas++;
+ }
+
+ nr_replicas = min_t(unsigned, nr_replicas,
+ (meta
+ ? BCH_SB_META_REPLICAS_HAVE
+ : BCH_SB_DATA_REPLICAS_HAVE)(c->disk_sb));
+ (meta
+ ? SET_BCH_SB_META_REPLICAS_HAVE
+ : SET_BCH_SB_DATA_REPLICAS_HAVE)(c->disk_sb, nr_replicas);
bch_write_super(c);
mutex_unlock(&c->sb_lock);
struct bkey_s_c_extent e = bkey_i_to_s_c_extent(k);
const struct bch_extent_ptr *ptr;
struct cache_member_cpu *mi = cache_member_info_get(c)->m;
+ unsigned nr_replicas = 0;
bool ret = true;
- extent_for_each_ptr(e, ptr)
- if (!ptr->cached &&
- !(meta
+ extent_for_each_ptr(e, ptr) {
+ if (ptr->cached)
+ continue;
+
+ if (!(meta
? mi[ptr->dev].has_metadata
: mi[ptr->dev].has_data)) {
ret = false;
break;
}
+ nr_replicas++;
+ }
+
+ if (nr_replicas <
+ (meta ? c->sb.meta_replicas_have : c->sb.data_replicas_have))
+ ret = false;
+
cache_member_info_put();
return ret;
c->sb.btree_node_size,
BCH_ENCODED_EXTENT_MAX) /
PAGE_SECTORS, 0) ||
- !(c->bucket_stats_percpu = alloc_percpu(struct bucket_stats_cache_set)) ||
+ !(c->bucket_stats_percpu = alloc_percpu(struct bch_fs_usage)) ||
lg_lock_init(&c->bucket_stats_lock) ||
mempool_init_page_pool(&c->btree_bounce_pool, 1,
ilog2(btree_pages(c))) ||
return NULL;
}
-/* Device startup/shutdown, ro/rw: */
-
-bool bch_dev_read_only(struct cache *ca)
-{
- struct cache_set *c = ca->set;
- struct bch_sb_field_members *mi;
- char buf[BDEVNAME_SIZE];
-
- bdevname(ca->disk_sb.bdev, buf);
-
- lockdep_assert_held(&c->state_lock);
-
- if (ca->mi.state != BCH_MEMBER_STATE_ACTIVE)
- return false;
-
- if (!bch_dev_may_remove(ca)) {
- bch_err(c, "required member %s going RO, forcing fs RO", buf);
- bch_fs_read_only(c);
- }
-
- trace_bcache_cache_read_only(ca);
-
- bch_moving_gc_stop(ca);
-
- /*
- * This stops new data writes (e.g. to existing open data
- * buckets) and then waits for all existing writes to
- * complete.
- */
- bch_dev_allocator_stop(ca);
-
- bch_dev_group_remove(&c->journal.devs, ca);
-
- /*
- * Device data write barrier -- no non-meta-data writes should
- * occur after this point. However, writes to btree buckets,
- * journal buckets, and the superblock can still occur.
- */
- trace_bcache_cache_read_only_done(ca);
-
- bch_notice(c, "%s read only", bdevname(ca->disk_sb.bdev, buf));
- bch_notify_dev_read_only(ca);
-
- mutex_lock(&c->sb_lock);
- mi = bch_sb_get_members(c->disk_sb);
- SET_BCH_MEMBER_STATE(&mi->members[ca->dev_idx],
- BCH_MEMBER_STATE_RO);
- bch_write_super(c);
- mutex_unlock(&c->sb_lock);
- return true;
-}
-
-static const char *__bch_dev_read_write(struct cache_set *c, struct cache *ca)
-{
- lockdep_assert_held(&c->state_lock);
-
- if (ca->mi.state == BCH_MEMBER_STATE_ACTIVE)
- return NULL;
-
- if (test_bit(BCH_DEV_REMOVING, &ca->flags))
- return "removing";
-
- trace_bcache_cache_read_write(ca);
-
- if (bch_dev_allocator_start(ca))
- return "error starting allocator thread";
-
- if (bch_moving_gc_start(ca))
- return "error starting moving GC thread";
-
- if (bch_tiering_start(c))
- return "error starting tiering thread";
-
- bch_notify_dev_read_write(ca);
- trace_bcache_cache_read_write_done(ca);
-
- return NULL;
-}
-
-const char *bch_dev_read_write(struct cache *ca)
-{
- struct cache_set *c = ca->set;
- struct bch_sb_field_members *mi;
- const char *err;
-
- err = __bch_dev_read_write(c, ca);
- if (err)
- return err;
-
- mutex_lock(&c->sb_lock);
- mi = bch_sb_get_members(c->disk_sb);
- SET_BCH_MEMBER_STATE(&mi->members[ca->dev_idx],
- BCH_MEMBER_STATE_ACTIVE);
- bch_write_super(c);
- mutex_unlock(&c->sb_lock);
-
- return NULL;
-}
+/* Device startup/shutdown: */
void bch_dev_release(struct kobject *kobj)
{
call_rcu(&ca->free_rcu, bch_dev_free_rcu);
}
-static void bch_dev_remove_work(struct work_struct *work)
-{
- struct cache *ca = container_of(work, struct cache, remove_work);
- struct bch_sb_field_members *mi;
- struct cache_set *c = ca->set;
- char name[BDEVNAME_SIZE];
- bool force = test_bit(BCH_DEV_FORCE_REMOVE, &ca->flags);
- unsigned dev_idx = ca->dev_idx;
-
- bdevname(ca->disk_sb.bdev, name);
-
- /*
- * Device should already be RO, now migrate data off:
- *
- * XXX: locking is sketchy, bch_dev_read_write() has to check
- * BCH_DEV_REMOVING bit
- */
- if (!ca->mi.has_data) {
- /* Nothing to do: */
- } else if (!bch_move_data_off_device(ca)) {
- mutex_lock(&c->sb_lock);
- mi = bch_sb_get_members(c->disk_sb);
- SET_BCH_MEMBER_HAS_DATA(&mi->members[ca->dev_idx], false);
-
- bch_write_super(c);
- mutex_unlock(&c->sb_lock);
- } else if (force) {
- bch_flag_data_bad(ca);
-
- mutex_lock(&c->sb_lock);
- mi = bch_sb_get_members(c->disk_sb);
- SET_BCH_MEMBER_HAS_DATA(&mi->members[ca->dev_idx], false);
-
- bch_write_super(c);
- mutex_unlock(&c->sb_lock);
- } else {
- bch_err(c, "Remove of %s failed, unable to migrate data off",
- name);
- clear_bit(BCH_DEV_REMOVING, &ca->flags);
- return;
- }
-
- /* Now metadata: */
-
- if (!ca->mi.has_metadata) {
- /* Nothing to do: */
- } else if (!bch_move_meta_data_off_device(ca)) {
- mutex_lock(&c->sb_lock);
- mi = bch_sb_get_members(c->disk_sb);
- SET_BCH_MEMBER_HAS_METADATA(&mi->members[ca->dev_idx], false);
-
- bch_write_super(c);
- mutex_unlock(&c->sb_lock);
- } else {
- bch_err(c, "Remove of %s failed, unable to migrate metadata off",
- name);
- clear_bit(BCH_DEV_REMOVING, &ca->flags);
- return;
- }
-
- /*
- * Ok, really doing the remove:
- * Drop device's prio pointer before removing it from superblock:
- */
- bch_notify_dev_removed(ca);
-
- spin_lock(&c->journal.lock);
- c->journal.prio_buckets[dev_idx] = 0;
- spin_unlock(&c->journal.lock);
-
- bch_journal_meta(&c->journal);
-
- /*
- * Stop device before removing it from the cache set's list of devices -
- * and get our own ref on cache set since ca is going away:
- */
- closure_get(&c->cl);
-
- mutex_lock(&c->state_lock);
-
- bch_dev_stop(ca);
-
- /*
- * RCU barrier between dropping between c->cache and dropping from
- * member info:
- */
- synchronize_rcu();
-
- /*
- * Free this device's slot in the bch_member array - all pointers to
- * this device must be gone:
- */
- mutex_lock(&c->sb_lock);
- mi = bch_sb_get_members(c->disk_sb);
- memset(&mi->members[dev_idx].uuid, 0, sizeof(mi->members[dev_idx].uuid));
-
- bch_write_super(c);
-
- mutex_unlock(&c->sb_lock);
- mutex_unlock(&c->state_lock);
-
- closure_put(&c->cl);
-}
-
-static bool __bch_dev_remove(struct cache_set *c, struct cache *ca, bool force)
-{
- if (test_bit(BCH_DEV_REMOVING, &ca->flags))
- return false;
-
- if (!bch_dev_may_remove(ca)) {
- bch_err(ca->set, "Can't remove last RW device");
- bch_notify_dev_remove_failed(ca);
- return false;
- }
-
- /* First, go RO before we try to migrate data off: */
- bch_dev_read_only(ca);
-
- if (force)
- set_bit(BCH_DEV_FORCE_REMOVE, &ca->flags);
-
- set_bit(BCH_DEV_REMOVING, &ca->flags);
- bch_notify_dev_removing(ca);
-
- /* Migrate the data and finish removal asynchronously: */
-
- queue_work(system_long_wq, &ca->remove_work);
- return true;
-}
-
-bool bch_dev_remove(struct cache *ca, bool force)
-{
- struct cache_set *c = ca->set;
- bool ret;
-
- mutex_lock(&c->state_lock);
- ret = __bch_dev_remove(c, ca, force);
- mutex_unlock(&c->state_lock);
-
- return ret;
-}
-
static int bch_dev_online(struct cache *ca)
{
char buf[12];
ca->dev_idx = sb->sb->dev_idx;
INIT_WORK(&ca->free_work, bch_dev_free_work);
- INIT_WORK(&ca->remove_work, bch_dev_remove_work);
spin_lock_init(&ca->freelist_lock);
spin_lock_init(&ca->prio_buckets_lock);
mutex_init(&ca->heap_lock);
!(ca->prio_buckets = kzalloc(sizeof(uint64_t) * prio_buckets(ca) *
2, GFP_KERNEL)) ||
!(ca->disk_buckets = alloc_bucket_pages(GFP_KERNEL, ca)) ||
- !(ca->bucket_stats_percpu = alloc_percpu(struct bucket_stats_cache)) ||
+ !(ca->bucket_stats_percpu = alloc_percpu(struct bch_dev_usage)) ||
!(ca->bio_prio = bio_kmalloc(GFP_NOIO, bucket_pages(ca))) ||
bioset_init(&ca->replica_set, 4,
offsetof(struct bch_write_bio, bio)) ||
return err;
}
+/* Device management: */
+
+static void __bch_dev_read_only(struct cache_set *c, struct cache *ca)
+{
+ bch_moving_gc_stop(ca);
+
+ /*
+ * This stops new data writes (e.g. to existing open data
+ * buckets) and then waits for all existing writes to
+ * complete.
+ */
+ bch_dev_allocator_stop(ca);
+
+ bch_dev_group_remove(&c->journal.devs, ca);
+}
+
+static const char *__bch_dev_read_write(struct cache_set *c, struct cache *ca)
+{
+ lockdep_assert_held(&c->state_lock);
+
+ if (ca->mi.state == BCH_MEMBER_STATE_ACTIVE)
+ return NULL;
+
+ trace_bcache_cache_read_write(ca);
+
+ if (bch_dev_allocator_start(ca))
+ return "error starting allocator thread";
+
+ if (bch_moving_gc_start(ca))
+ return "error starting moving GC thread";
+
+ if (bch_tiering_start(c))
+ return "error starting tiering thread";
+
+ bch_notify_dev_read_write(ca);
+ trace_bcache_cache_read_write_done(ca);
+
+ return NULL;
+}
+
+bool bch_dev_state_allowed(struct cache_set *c, struct cache *ca,
+ enum bch_member_state new_state, int flags)
+{
+ lockdep_assert_held(&c->state_lock);
+
+ if (new_state == BCH_MEMBER_STATE_ACTIVE)
+ return true;
+
+ if (ca->mi.has_data &&
+ !(flags & BCH_FORCE_IF_DATA_DEGRADED))
+ return false;
+
+ if (ca->mi.has_data &&
+ c->sb.data_replicas_have <= 1 &&
+ !(flags & BCH_FORCE_IF_DATA_LOST))
+ return false;
+
+ if (ca->mi.has_metadata &&
+ !(flags & BCH_FORCE_IF_METADATA_DEGRADED))
+ return false;
+
+ if (ca->mi.has_metadata &&
+ c->sb.meta_replicas_have <= 1 &&
+ !(flags & BCH_FORCE_IF_METADATA_LOST))
+ return false;
+
+ return true;
+}
+
+int __bch_dev_set_state(struct cache_set *c, struct cache *ca,
+ enum bch_member_state new_state, int flags)
+{
+ struct bch_sb_field_members *mi;
+ char buf[BDEVNAME_SIZE];
+
+ if (ca->mi.state == new_state)
+ return 0;
+
+ if (!bch_dev_state_allowed(c, ca, new_state, flags))
+ return -EINVAL;
+
+ if (new_state == BCH_MEMBER_STATE_ACTIVE) {
+ if (__bch_dev_read_write(c, ca))
+ return -ENOMEM;
+ } else {
+ __bch_dev_read_only(c, ca);
+ }
+
+ bch_notice(c, "%s %s",
+ bdevname(ca->disk_sb.bdev, buf),
+ bch_dev_state[new_state]);
+
+ mutex_lock(&c->sb_lock);
+ mi = bch_sb_get_members(c->disk_sb);
+ SET_BCH_MEMBER_STATE(&mi->members[ca->dev_idx], new_state);
+ bch_write_super(c);
+ mutex_unlock(&c->sb_lock);
+
+ return 0;
+}
+
+int bch_dev_set_state(struct cache_set *c, struct cache *ca,
+ enum bch_member_state new_state, int flags)
+{
+ int ret;
+
+ mutex_lock(&c->state_lock);
+ ret = __bch_dev_set_state(c, ca, new_state, flags);
+ mutex_unlock(&c->state_lock);
+
+ return ret;
+}
+
+#if 0
+int bch_dev_migrate_from(struct cache_set *c, struct cache *ca)
+{
+ /* First, go RO before we try to migrate data off: */
+ ret = bch_dev_set_state(c, ca, BCH_MEMBER_STATE_RO, flags);
+ if (ret)
+ return ret;
+
+ bch_notify_dev_removing(ca);
+
+ /* Migrate data, metadata off device: */
+
+ ret = bch_move_data_off_device(ca);
+ if (ret && !(flags & BCH_FORCE_IF_DATA_LOST)) {
+ bch_err(c, "Remove of %s failed, unable to migrate data off",
+ name);
+ return ret;
+ }
+
+ if (ret)
+ ret = bch_flag_data_bad(ca);
+ if (ret) {
+ bch_err(c, "Remove of %s failed, unable to migrate data off",
+ name);
+ return ret;
+ }
+
+ ret = bch_move_metadata_off_device(ca);
+ if (ret)
+ return ret;
+}
+#endif
+
+/* Device add/removal: */
+
+static int __bch_dev_remove(struct cache_set *c, struct cache *ca, int flags)
+{
+ struct bch_sb_field_members *mi;
+ char name[BDEVNAME_SIZE];
+ unsigned dev_idx = ca->dev_idx;
+ int ret;
+
+ bdevname(ca->disk_sb.bdev, name);
+
+ if (ca->mi.state == BCH_MEMBER_STATE_ACTIVE) {
+ bch_err(ca->set, "Cannot remove RW device");
+ bch_notify_dev_remove_failed(ca);
+ return -EINVAL;
+ }
+
+ if (!bch_dev_state_allowed(c, ca, BCH_MEMBER_STATE_FAILED, flags)) {
+ bch_err(ca->set, "Cannot remove %s without losing data", name);
+ bch_notify_dev_remove_failed(ca);
+ return -EINVAL;
+ }
+
+ /*
+ * XXX: verify that dev_idx is really not in use anymore, anywhere
+ *
+ * flag_data_bad() does not check btree pointers
+ */
+ ret = bch_flag_data_bad(ca);
+ if (ret) {
+ bch_err(c, "Remove of %s failed", name);
+ return ret;
+ }
+
+ /*
+ * Ok, really doing the remove:
+ * Drop device's prio pointer before removing it from superblock:
+ */
+ bch_notify_dev_removed(ca);
+
+ spin_lock(&c->journal.lock);
+ c->journal.prio_buckets[dev_idx] = 0;
+ spin_unlock(&c->journal.lock);
+
+ bch_journal_meta(&c->journal);
+
+ bch_dev_stop(ca);
+
+ /*
+ * RCU barrier between dropping between c->cache and dropping from
+ * member info:
+ */
+ synchronize_rcu();
+
+ /*
+ * Free this device's slot in the bch_member array - all pointers to
+ * this device must be gone:
+ */
+ mutex_lock(&c->sb_lock);
+ mi = bch_sb_get_members(c->disk_sb);
+ memset(&mi->members[dev_idx].uuid, 0, sizeof(mi->members[dev_idx].uuid));
+
+ bch_write_super(c);
+
+ mutex_unlock(&c->sb_lock);
+
+ return 0;
+}
+
+int bch_dev_remove(struct cache_set *c, struct cache *ca, int flags)
+{
+ int ret;
+
+ mutex_lock(&c->state_lock);
+ ret = __bch_dev_remove(c, ca, flags);
+ mutex_unlock(&c->state_lock);
+
+ return ret;
+}
+
int bch_dev_add(struct cache_set *c, const char *path)
{
struct bcache_superblock sb;
return ret ?: -EINVAL;
}
+/* Filesystem open: */
+
const char *bch_fs_open(char * const *devices, unsigned nr_devices,
struct bch_opts opts, struct cache_set **ret)
{
#include "extents.h"
+#include <linux/bcache-ioctl.h>
+
static inline size_t sector_to_bucket(const struct cache *ca, sector_t s)
{
return s >> ca->bucket_bits;
(ca = bch_get_next_cache(c, &(iter))); \
percpu_ref_put(&ca->ref), (iter)++)
-static inline bool bch_dev_may_remove(struct cache *ca)
-{
- struct cache_set *c = ca->set;
- struct cache_group *grp = &c->cache_all;
-
- /* Can't remove the last RW device: */
- return grp->nr != 1 ||
- rcu_access_pointer(grp->d[0].dev) != ca;
-}
-
void bch_dev_release(struct kobject *);
-bool bch_dev_read_only(struct cache *);
-const char *bch_dev_read_write(struct cache *);
-bool bch_dev_remove(struct cache *, bool force);
+bool bch_dev_state_allowed(struct cache_set *, struct cache *,
+ enum bch_member_state, int);
+int __bch_dev_set_state(struct cache_set *, struct cache *,
+ enum bch_member_state, int);
+int bch_dev_set_state(struct cache_set *, struct cache *,
+ enum bch_member_state, int);
+
+int bch_dev_fail(struct cache *, int);
+int bch_dev_remove(struct cache_set *, struct cache *, int);
int bch_dev_add(struct cache_set *, const char *);
void bch_fs_detach(struct cache_set *);
static struct attribute sysfs_state_rw = {
.name = "state",
- .mode = S_IRUGO|S_IWUSR
+ .mode = S_IRUGO
};
SHOW(bch_cached_dev)
static ssize_t show_fs_alloc_debug(struct cache_set *c, char *buf)
{
- struct bucket_stats_cache_set stats = bch_bucket_stats_read_cache_set(c);
+ struct bch_fs_usage stats = bch_fs_usage_read(c);
return scnprintf(buf, PAGE_SIZE,
"capacity:\t\t%llu\n"
static ssize_t show_dev_alloc_debug(struct cache *ca, char *buf)
{
struct cache_set *c = ca->set;
- struct bucket_stats_cache stats = bch_bucket_stats_read_cache(ca);
+ struct bch_dev_usage stats = bch_dev_usage_read(ca);
return scnprintf(buf, PAGE_SIZE,
"free_inc: %zu/%zu\n"
{
struct cache *ca = container_of(kobj, struct cache, kobj);
struct cache_set *c = ca->set;
- struct bucket_stats_cache stats = bch_bucket_stats_read_cache(ca);
+ struct bch_dev_usage stats = bch_dev_usage_read(ca);
sysfs_printf(uuid, "%pU\n", ca->uuid.b);
bch_tiering_start(c);
}
- if (attr == &sysfs_state_rw) {
- char name[BDEVNAME_SIZE];
- const char *err = NULL;
- ssize_t v = bch_read_string_list(buf, bch_dev_state);
-
- if (v < 0)
- return v;
-
- if (v == ca->mi.state)
- return size;
-
- switch (v) {
- case BCH_MEMBER_STATE_ACTIVE:
- err = bch_dev_read_write(ca);
- break;
- case BCH_MEMBER_STATE_RO:
- bch_dev_read_only(ca);
- break;
- case BCH_MEMBER_STATE_FAILED:
- case BCH_MEMBER_STATE_SPARE:
- /*
- * XXX: need to migrate data off and set correct state
- */
- pr_err("can't set %s %s: not supported",
- bdevname(ca->disk_sb.bdev, name),
- bch_dev_state[v]);
- return -EINVAL;
- }
-
- if (err) {
- pr_err("can't set %s %s: %s",
- bdevname(ca->disk_sb.bdev, name),
- bch_dev_state[v], err);
- return -EINVAL;
- }
- }
-
- if (attr == &sysfs_unregister) {
- bool force = false;
-
- if (!strncmp(buf, "force", 5) &&
- (buf[5] == '\0' || buf[5] == '\n'))
- force = true;
- bch_dev_remove(ca, force);
- }
-
if (attr == &sysfs_clear_stats) {
int cpu;
static struct attribute *bch_dev_files[] = {
&sysfs_uuid,
- &sysfs_unregister,
&sysfs_bucket_size,
&sysfs_bucket_size_bytes,
&sysfs_block_size,