X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=bcache.c;h=2bc32c035a20efb55cebbd4945178d869bd0f235;hb=d7d289efaf6620f87a2686a25a00e78b26a1a9a8;hp=fbaaa0effc13129664696024cb0bac4423ac01fe;hpb=e8c1cee578e3911ea95c3f063234f7bf271b17dd;p=bcachefs-tools-debian diff --git a/bcache.c b/bcache.c index fbaaa0e..2bc32c0 100644 --- a/bcache.c +++ b/bcache.c @@ -15,12 +15,17 @@ #include #include #include +#include #include #include #include #include "bcache.h" +#define __KERNEL__ +#include +#undef __KERNEL__ + const char * const cache_state[] = { "active", "ro", @@ -369,13 +374,13 @@ uint64_t getblocks(int fd) uint64_t ret; struct stat statbuf; if (fstat(fd, &statbuf)) { - perror("stat error\n"); + perror("getblocks: stat error\n"); exit(EXIT_FAILURE); } ret = statbuf.st_size / 512; if (S_ISBLK(statbuf.st_mode)) if (ioctl(fd, BLKGETSIZE, &ret)) { - perror("ioctl error"); + perror("ioctl error getting blksize"); exit(EXIT_FAILURE); } return ret; @@ -433,12 +438,12 @@ static void do_write_sb(int fd, struct cache_sb *sb) /* Zero start of disk */ if (pwrite(fd, zeroes, SB_START, 0) != SB_START) { - perror("write error\n"); + perror("write error trying to zero start of disk\n"); exit(EXIT_FAILURE); } /* Write superblock */ if (pwrite(fd, sb, bytes, SB_START) != bytes) { - perror("write error\n"); + perror("write error trying to write superblock\n"); exit(EXIT_FAILURE); } @@ -498,36 +503,60 @@ int dev_open(const char *dev, bool wipe_bcache) struct cache_sb sb; blkid_probe pr; int fd; + char err[MAX_PATH]; if ((fd = open(dev, O_RDWR|O_EXCL)) == -1) { - fprintf(stderr, "Can't open dev %s: %s\n", dev, strerror(errno)); - exit(EXIT_FAILURE); + sprintf(err, "Can't open dev %s: %s\n", dev, strerror(errno)); + goto err; } - if (pread(fd, &sb, sizeof(sb), SB_START) != sizeof(sb)) - exit(EXIT_FAILURE); + if (pread(fd, &sb, sizeof(sb), SB_START) != sizeof(sb)) { + sprintf(err, "Failed to read superblock"); + goto err; + } if (!memcmp(&sb.magic, &BCACHE_MAGIC, 16) && !wipe_bcache) { - fprintf(stderr, "Already a bcache device on %s, " + sprintf(err, "Already a bcache device on %s, " "overwrite with --wipe-bcache\n", dev); - exit(EXIT_FAILURE); + goto err; } - if (!(pr = blkid_new_probe())) - exit(EXIT_FAILURE); - if (blkid_probe_set_device(pr, fd, 0, 0)) - exit(EXIT_FAILURE); + if (!(pr = blkid_new_probe())) { + sprintf(err, "Failed to create a new probe"); + goto err; + } + if (blkid_probe_set_device(pr, fd, 0, 0)) { + sprintf(err, "failed to set probe to device"); + goto err; + } /* enable ptable probing; superblock probing is enabled by default */ - if (blkid_probe_enable_partitions(pr, true)) - exit(EXIT_FAILURE); + if (blkid_probe_enable_partitions(pr, true)) { + sprintf(err, "Failed to enable partitions on probe"); + goto err; + } if (!blkid_do_probe(pr)) { /* XXX wipefs doesn't know how to remove partition tables */ - fprintf(stderr, "Device %s already has a non-bcache superblock, " - "remove it using wipefs and wipefs -a\n", dev); - exit(EXIT_FAILURE); + sprintf(err, "Device %s already has a non-bcache superblock, " + "remove it using wipefs and wipefs -a\n", dev); + goto err; } return fd; + + err: + fprintf(stderr, "dev_open failed with: %s", err); + exit(EXIT_FAILURE); +} + +static unsigned min_bucket_size(int num_bucket_sizes, unsigned *bucket_sizes) +{ + int i; + unsigned min = bucket_sizes[0]; + + for (i = 0; i < num_bucket_sizes; i++) + min = bucket_sizes[i] < min ? bucket_sizes[i] : min; + + return min; } void write_cache_sbs(int *fds, struct cache_sb *sb, @@ -536,6 +565,7 @@ void write_cache_sbs(int *fds, struct cache_sb *sb, { char uuid_str[40], set_uuid_str[40]; size_t i; + unsigned min_size = min_bucket_size(num_bucket_sizes, bucket_sizes); sb->offset = SB_SECTOR; sb->version = BCACHE_SB_VERSION_CDEV_V3; @@ -552,11 +582,14 @@ void write_cache_sbs(int *fds, struct cache_sb *sb, for (i = 0; i < sb->nr_in_set; i++) { struct cache_member *m = sb->members + i; - sb->uuid = m->uuid; - if(num_bucket_sizes <= 1) + if (num_bucket_sizes <= 1) sb->bucket_size = bucket_sizes[0]; else sb->bucket_size = bucket_sizes[i]; + SET_CACHE_BTREE_NODE_SIZE(sb, min_size); + + + sb->uuid = m->uuid; sb->nbuckets = getblocks(fds[i]) / sb->bucket_size; sb->nr_this_dev = i; sb->first_bucket = (23 / sb->bucket_size) + 1; @@ -595,7 +628,7 @@ void write_cache_sbs(int *fds, struct cache_sb *sb, void next_cache_device(struct cache_sb *sb, unsigned replication_set, - unsigned tier, + int tier, unsigned replacement_policy, bool discard) { @@ -657,7 +690,7 @@ unsigned get_blocksize(const char *path) long strtoul_or_die(const char *p, size_t max, const char *msg) { errno = 0; - long v = strtol(optarg, NULL, 10); + long v = strtol(p, NULL, 10); if (errno || v < 0 || v >= max) { fprintf(stderr, "Invalid %s %zi\n", msg, v); exit(EXIT_FAILURE); @@ -676,6 +709,10 @@ static void print_encode(char *in) printf("%%%x", *pos); } +static void show_uuid_only(struct cache_sb *sb, char *dev_uuid) { + uuid_unparse(sb->uuid.b, dev_uuid); +} + static void show_super_common(struct cache_sb *sb, bool force_csum) { char uuid[40]; @@ -709,7 +746,7 @@ static void show_super_common(struct cache_sb *sb, bool force_csum) printf(" [match]\n"); } else { printf(" [expected %" PRIX64 "]\n", expected_csum); - if (!force_csum) { + if (force_csum) { fprintf(stderr, "Corrupt superblock (bad csum)\n"); exit(2); } @@ -778,11 +815,12 @@ void show_super_backingdev(struct cache_sb *sb, bool force_csum) bdev_state[BDEV_STATE(sb)]); } -void show_cache_member(struct cache_sb *sb, unsigned i) +static void show_cache_member(struct cache_sb *sb, unsigned i) { struct cache_member *m = ((struct cache_member *) sb->d) + i; printf("cache.state\t%s\n", cache_state[CACHE_STATE(m)]); + printf("cache.tier\t%llu\n", CACHE_TIER(m)); printf("cache.replication_set\t%llu\n", CACHE_REPLICATION_SET(m)); @@ -821,3 +859,570 @@ void show_super_cache(struct cache_sb *sb, bool force_csum) show_cache_member(sb, sb->nr_this_dev); } + +static int __sysfs_attr_type(char *attr, const char **attr_arr) { + int i, j; + for(i = 0; attr_arr[i] != NULL; i++) + if(!strcmp(attr, attr_arr[i])) + return 1; + return 0; +} + +enum sysfs_attr sysfs_attr_type(char *attr) { + int ret; + if(__sysfs_attr_type(attr, set_attrs)) + return SET_ATTR; + if(__sysfs_attr_type(attr, cache_attrs)) + return CACHE_ATTR; + if(__sysfs_attr_type(attr, internal_attrs)) + return INTERNAL_ATTR; + + printf("No attribute called %s, try --list to see options\n", attr); + + return -1; +} + +static void __sysfs_attr_list(const char **attr_arr) { + int i, j; + for (i = 0; attr_arr[i] != NULL; i++) + printf("%s\n", attr_arr[i]); +} + +void sysfs_attr_list() { + __sysfs_attr_list(set_attrs); + __sysfs_attr_list(cache_attrs); + __sysfs_attr_list(internal_attrs); +} + +struct cache_sb *query_dev(char *dev, bool force_csum, + bool print_sb, bool uuid_only, char *dev_uuid) +{ + size_t bytes = 4096; + struct cache_sb *sb = aligned_alloc(bytes, bytes); + + int fd = open(dev, O_RDONLY|O_DIRECT); + if (fd < 0) { + printf("Can't open dev %s: %s\n", dev, strerror(errno)); + return NULL; + } + + while (true) { + int ret = pread(fd, sb, bytes, SB_START); + if (ret < 0) { + fprintf(stderr, "Couldn't read superblock: %s\n", + strerror(errno)); + close(fd); + free(sb); + return NULL; + } else if (bytes > sizeof(sb) + sb->keys * sizeof(u64)) { + /* We read the whole superblock */ + break; + } + + /* + * otherwise double the size of our dest + * and read again + */ + free(sb); + bytes *= 2; + sb = aligned_alloc(4096, bytes); + } + + close(fd); + + if(uuid_only) { + show_uuid_only(sb, dev_uuid); + return sb; + } + + if(print_sb) { + if (!SB_IS_BDEV(sb)) + show_super_cache(sb, force_csum); + else + show_super_backingdev(sb, force_csum); + } + + return sb; +} + +static char *dev_name(const char *ugly_path) { + char buf[32]; + int i, end = strlen(ugly_path); + + //Chop off "/bcache", then look for the next '/' from the end + for (i = end - 8; ; i--) + if(ugly_path[i] == '/') + break; + + strcpy(buf, ugly_path + i); + buf[end - i - 7] = 0; + + // Is the dev guaranteed to be in /dev? + // This is needed for finding the superblock with a query-dev + return strdup(buf); +} + +static void list_cacheset_devs(char *cset_dir, char *cset_name, bool parse_dev_name) { + int i = 0; + DIR *cachedir, *dir; + struct stat cache_stat; + char entry[MAX_PATH]; + struct dirent *ent; + snprintf(entry, MAX_PATH, "%s/%s", cset_dir, cset_name); + + if((dir = opendir(entry)) != NULL) { + while((ent = readdir(dir)) != NULL) { + char buf[MAX_PATH]; + int len; + char *tmp; + + /* + * We are looking for all cache# directories + * do a strlen < 9 to skip over other entries + * that also start with "cache" + */ + if(strncmp(ent->d_name, "cache", 5) || + !(strlen(ent->d_name) < 9)) + continue; + + snprintf(entry, MAX_PATH, "%s/%s/%s", + cset_dir, + cset_name, + ent->d_name); + + if((cachedir = opendir(entry)) == NULL) + continue; + + if(stat(entry, &cache_stat)) + continue; + + if((len = readlink(entry, buf, sizeof(buf) - 1)) != + -1) { + buf[len] = '\0'; + if(parse_dev_name) { + tmp = dev_name(buf); + printf("/dev%s\n", tmp); + free(tmp); + } else { + printf("\t%s\n", buf); + } + } + } + } +} + +char *find_matching_uuid(char *stats_dir, char *subdir, const char *stats_dev_uuid) { + /* Do a query-dev --uuid only to get the uuid + * repeat on each dev until we find a matching one + * append that cache# to subdir and return + */ + + int i = 0; + DIR *cachedir; + struct stat cache_stat; + char intbuf[4]; + char entry[MAX_PATH]; + char *err = NULL; + + snprintf(entry, MAX_PATH, "%s%s", stats_dir, subdir); + snprintf(intbuf, 4, "%d", i); + strcat(entry, intbuf); + + while(true) { + char buf[MAX_PATH]; + int len; + + if((cachedir = opendir(entry)) == NULL) + break; + + if(stat(entry, &cache_stat)) + break; + + if((len = readlink(entry, buf, sizeof(buf) - 1)) != -1) { + char dev_uuid[40]; + buf[len] = '\0'; + int i, end = strlen(buf); + char tmp[32], devname[32]; + struct cache_sb *sb; + + /* Chop off "/bcache", then look for the + * next '/' from the end + */ + for (i = end - 8; ; i--) + if(buf[i] == '/') + break; + + strcpy(tmp, buf + i); + tmp[end - i - 7] = 0; + strcpy(devname, "/dev"); + strcat(devname, tmp); + + err = "Unable to open superblock"; + sb = query_dev(devname, false, false, true, dev_uuid); + if(!sb) + return err; + else + free(sb); + + if(!strcmp(stats_dev_uuid, dev_uuid)) { + strcat(subdir, intbuf); + return NULL; + } + } + + /* remove i from end and append i++ */ + entry[strlen(entry)-strlen(intbuf)] = 0; + i++; + snprintf(intbuf, 4, "%d", i); + strcat(entry, intbuf); + } + + + err = "dev uuid doesn't exist in cache_set"; + return err; +} + +char *list_cachesets(char *cset_dir, bool list_devs) +{ + struct dirent *ent; + DIR *dir; + char *err = NULL; + + dir = opendir(cset_dir); + if (!dir) { + err = "Failed to open cacheset dir"; + goto err; + } + + while ((ent = readdir(dir)) != NULL) { + struct stat statbuf; + char entry[MAX_PATH]; + + if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, "..")) + continue; + + snprintf(entry, MAX_PATH, "%s/%s", cset_dir, ent->d_name); + if(stat(entry, &statbuf) == -1) { + err = "Failed to stat cacheset subdir"; + goto err; + } + + if (S_ISDIR(statbuf.st_mode)) { + printf("%s\n", ent->d_name); + + if(list_devs) { + list_cacheset_devs(cset_dir, ent->d_name, true); + } + } + } + +err: + closedir(dir); + return err; +} + +char *register_bcache(char *const *devs) +{ + int ret, bcachefd; + char *err = NULL; + + bcachefd = open("/dev/bcache", O_RDWR); + if (bcachefd < 0) { + err = "Can't open bcache device"; + goto err; + } + + ret = ioctl(bcachefd, BCH_IOCTL_REGISTER, devs); + if (ret < 0) { + char tmp[64]; + snprintf(tmp, 64, "ioctl register error: %s\n", + strerror(ret)); + err = strdup(tmp); + goto err; + } + +err: + if (bcachefd) + close(bcachefd); + return err; +} + +char *unregister_bcache(char *const *devs) +{ + int ret, bcachefd; + char *err = NULL; + + bcachefd = open("/dev/bcache", O_RDWR); + if (bcachefd < 0) { + err = "Can't open bcache device"; + goto err; + } + + ret = ioctl(bcachefd, BCH_IOCTL_UNREGISTER, devs); + if (ret < 0) { + char tmp[64]; + snprintf(tmp, 64, "ioctl unregister error: %s\n", + strerror(ret)); + err = strdup(tmp); + goto err; + } + +err: + close(bcachefd); + return err; +} + +char *add_devices(char *const *devs, char *uuid) +{ + int ret, bcachefd; + char *err = NULL; + + bcachefd = open("/dev/bcache", O_RDWR); + if (bcachefd < 0) { + err = "Can't open bcache device"; + goto err; + } + + struct bch_ioctl_add_disks ia; + ia.devs = devs; + ia.uuid = uuid; + + ret = ioctl(bcachefd, BCH_IOCTL_ADD_DISKS, &ia); + if (ret < 0) { + char tmp[128]; + snprintf(tmp, 128, "ioctl add disk error: %s\n", + strerror(ret)); + err = strdup(tmp); + } + +err: + close(bcachefd); + return err; +} + +char *remove_device(const char *dev, bool force) +{ + int ret, bcachefd; + char *err = NULL; + + bcachefd = open("/dev/bcache", O_RDWR); + if (bcachefd < 0) { + err = "Can't open bcache device"; + goto err; + } + + struct bch_ioctl_rm_disk ir; + ir.dev = dev; + ir.force = force ? 1 : 0; + + ret = ioctl(bcachefd, BCH_IOCTL_RM_DISK, &ir); + if (ret < 0) { + char tmp[128]; + snprintf(tmp, 128, "ioctl add disk error: %s\n", + strerror(ret)); + err = strdup(tmp); + } + +err: + close(bcachefd); + return err; +} + +char *probe(char *dev, int udev) +{ + struct cache_sb sb; + char uuid[40]; + blkid_probe pr; + char *err = NULL; + + int fd = open(dev, O_RDONLY); + if (fd == -1) { + err = "Got file descriptor -1 trying to open dev"; + goto err; + } + + if (!(pr = blkid_new_probe())) { + err = "Failed trying to get a blkid for new probe"; + goto err; + } + + if (blkid_probe_set_device(pr, fd, 0, 0)) { + err = "Failed blkid probe set device"; + goto err; + } + + /* probe partitions too */ + if (blkid_probe_enable_partitions(pr, true)) { + err = "Enable probe partitions"; + goto err; + } + + /* bail if anything was found + * probe-bcache isn't needed once blkid recognizes bcache */ + if (!blkid_do_probe(pr)) { + err = "blkid recognizes bcache"; + goto err; + } + + if (pread(fd, &sb, sizeof(sb), SB_START) != sizeof(sb)) { + err = "Failed to read superblock"; + goto err; + } + + if (memcmp(&sb.magic, &BCACHE_MAGIC, sizeof(sb.magic))) { + err = "Bcache magic incorrect"; + goto err; + } + + uuid_unparse(sb.uuid.b, uuid); + + if (udev) + printf("ID_FS_UUID=%s\n" + "ID_FS_UUID_ENC=%s\n" + "ID_FS_TYPE=bcache\n", + uuid, uuid); + else + printf("%s: UUID=\"\" TYPE=\"bcache\"\n", uuid); + + return 0; + +err: + return err; +} + +char *read_stat_dir(DIR *dir, char *stats_dir, char *stat_name, char *ret) +{ + struct stat statbuf; + char entry[MAX_PATH]; + char *err = NULL; + + snprintf(entry, MAX_PATH, "%s/%s", stats_dir, stat_name); + if(stat(entry, &statbuf) == -1) { + char tmp[MAX_PATH]; + snprintf(tmp, MAX_PATH, "Failed to stat %s\n", entry); + err = strdup(tmp); + goto err; + } + + if (S_ISREG(statbuf.st_mode)) { + char buf[MAX_PATH]; + FILE *fp = NULL; + + fp = fopen(entry, "r"); + if(!fp) { + /* If we can't open the file, this is probably because + * of permissions, just move to the next file */ + return NULL; + } + + while(fgets(ret, MAX_PATH, fp)); + fclose(fp); + } +err: + return err; +} + +char *bcache_get_capacity(const char *cset_dir, const char *capacity_uuid, + bool show_devs) +{ + char *err = NULL; + char bucket_size_path[MAX_PATH]; + char nbuckets_path[MAX_PATH]; + char avail_buckets_path[MAX_PATH]; + char cache_path[MAX_PATH]; + + double bucket_sizes[MAX_DEVS]; + double nbuckets[MAX_DEVS]; + double avail_buckets[MAX_DEVS]; + char *dev_names[MAX_DEVS]; + int dev_count = 0, i; + char intbuf[4]; + double total_cap = 0, total_free = 0; + int precision = 2; + + snprintf(intbuf, 4, "%d", i); + snprintf(bucket_size_path, MAX_PATH, "%s/%s/%s/%s", cset_dir, + capacity_uuid, "cache0", "bucket_size_bytes"); + snprintf(nbuckets_path, MAX_PATH, "%s/%s/%s/%s", cset_dir, + capacity_uuid, "cache0", "nbuckets"); + snprintf(avail_buckets_path, MAX_PATH, "%s/%s/%s/%s", cset_dir, + capacity_uuid, "cache0", "available_buckets"); + snprintf(cache_path, MAX_PATH, "%s/%s/%s", cset_dir, capacity_uuid, + "cache0"); + + while(true) { + char buf[MAX_PATH]; + int len; + DIR *cache_dir; + + if((cache_dir = opendir(cache_path)) == NULL) + break; + + err = read_stat_dir(cache_dir, cache_path, + "bucket_size_bytes", buf); + if (err) + goto err; + else + bucket_sizes[dev_count] = atof(buf); + + err = read_stat_dir(cache_dir, cache_path, + "nbuckets", buf); + if (err) + goto err; + else + nbuckets[dev_count] = atof(buf); + + err = read_stat_dir(cache_dir, cache_path, + "available_buckets", buf); + if (err) + goto err; + else + avail_buckets[dev_count] = atof(buf); + + if((len = readlink(cache_path, buf, sizeof(buf) - 1)) != -1) { + buf[len] = '\0'; + dev_names[dev_count] = dev_name(buf); + } + + /* remove i/stat and append i++/stat */ + bucket_size_path[strlen(cache_path) - strlen(intbuf)] = 0; + nbuckets_path[strlen(cache_path) - strlen(intbuf)] = 0; + avail_buckets_path[strlen(cache_path) - strlen(intbuf)] = 0; + cache_path[strlen(cache_path) - strlen(intbuf)] = 0; + + dev_count++; + + snprintf(intbuf, 4, "%d", dev_count); + strcat(cache_path, intbuf); + strcat(bucket_size_path, intbuf); + strcat(nbuckets_path, intbuf); + strcat(avail_buckets_path, intbuf); + } + + printf("%-15s%-25s%-25s\n", "Device Name", "Capacity (512 Blocks)", "Free (512 Blocks)"); + + if (show_devs) { + for (i = 0; i < dev_count; i++) { + printf("%s%-11s%-25.*f%-25.*f\n", "/dev", dev_names[i], + precision, + (bucket_sizes[i] * nbuckets[i]) / 512, + precision, + (bucket_sizes[i] * avail_buckets[i]) / 512); + } + } + + for (i = 0; i < dev_count; i++) { + total_cap += (bucket_sizes[i] * nbuckets[i]) / 512; + total_free += (bucket_sizes[i] * avail_buckets[i]) / 512; + + } + + printf("%-15s%-25.*f%-25.*f\n", "Total", precision, total_cap, + precision, total_free); + +err: + for (i = 0; i < dev_count; i++) + if (dev_names[i]) + free(dev_names[i]); + return err; +}