show the bcache devices in both table-like format and tree-like format. users can use this tools to manage device(regist,unregist,attach,detach,set-cachemode). --- Makefile | 21 +- bcache-main.c | 608 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ bcache.c | 2 +- lib.c | 479 +++++++++++++++++++++++++++++++++++++++++++++ lib.h | 67 +++++++ list.h | 519 +++++++++++++++++++++++++++++++++++++++++++++++++ make.c | 463 ++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 2154 insertions(+), 5 deletions(-) create mode 100644 bcache-main.c create mode 100644 lib.c create mode 100644 lib.h create mode 100644 list.h create mode 100644 make.c diff --git a/Makefile b/Makefile index c824ae3..6c53995 100644 --- a/Makefile +++ b/Makefile @@ -3,12 +3,12 @@ PREFIX=/usr UDEVLIBDIR=/lib/udev DRACUTLIBDIR=/lib/dracut INSTALL=install -CFLAGS+=-O2 -Wall -g +#CFLAGS+=-O2 -Wall -g -all: make-bcache probe-bcache bcache-super-show bcache-register +all: make-bcache probe-bcache bcache-super-show bcache-register depend bcache install: make-bcache probe-bcache bcache-super-show - $(INSTALL) -m0755 make-bcache bcache-super-show $(DESTDIR)${PREFIX}/sbin/ + $(INSTALL) -m0755 make-bcache bcache-super-show bcache $(DESTDIR)${PREFIX}/sbin/ $(INSTALL) -m0755 probe-bcache bcache-register $(DESTDIR)$(UDEVLIBDIR)/ $(INSTALL) -m0644 69-bcache.rules $(DESTDIR)$(UDEVLIBDIR)/rules.d/ $(INSTALL) -m0644 -- *.8 $(DESTDIR)${PREFIX}/share/man/man8/ @@ -18,7 +18,7 @@ install: make-bcache probe-bcache bcache-super-show # $(INSTALL) -m0755 bcache-test $(DESTDIR)${PREFIX}/sbin/ clean: - $(RM) -f make-bcache probe-bcache bcache-super-show bcache-test -- *.o + $(RM) -f bcache make-bcache probe-bcache bcache-super-show bcache-register bcache-test -- *.o bcache-test: LDLIBS += `pkg-config --libs openssl` -lm make-bcache: LDLIBS += `pkg-config --libs uuid blkid` @@ -30,3 +30,16 @@ bcache-super-show: LDLIBS += `pkg-config --libs uuid` bcache-super-show: CFLAGS += -std=gnu99 bcache-super-show: bcache.o bcache-register: bcache-register.o + +CFLAGS+=`pkg-config --cflags blkid uuid smartcols` +LDFLAGS+=`pkg-config --libs blkid uuid smartcols` + +SRCS=bcache-main.c bcache.c lib.c make.c +depend: .depend +.depend: $(SRCS) + rm -f ./.depend + $(CC) $(CFLAGS) -MM $^ > ./.depend; + +include .depend + +bcache: bcache-main.o bcache.o lib.o make.o diff --git a/bcache-main.c b/bcache-main.c new file mode 100644 index 0000000..7b0ca0a --- /dev/null +++ b/bcache-main.c @@ -0,0 +1,608 @@ +/* + * Author: Shaoxiong Li <dahefanteng@xxxxxxxxx> + * + * GPLv2 + */ + + +#include <stdio.h> +#include <inttypes.h> +#include <string.h> +#include <stdint.h> +#include <stdlib.h> +#include <stdbool.h> +#include <unistd.h> +#include <getopt.h> +#include <regex.h> +#include "bcache.h" +#include "lib.h" +#include "libsmartcols/libsmartcols.h" +#include <locale.h> +#include "list.h" + + +//utils function +static bool accepted_char(char c) +{ + if ('0' <= c && c <= '9') + return true; + if ('A' <= c && c <= 'Z') + return true; + if ('a' <= c && c <= 'z') + return true; + if (strchr(".-_", c)) + return true; + return false; +} + +static void print_encode(char *in) +{ + char *pos; + for (pos = in; *pos; pos++) + if (accepted_char(*pos)) + putchar(*pos); + else + printf("%%%x", *pos); +} + +bool bad_uuid(char *uuid) +{ + const char *pattern = + "^[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}$"; + regex_t reg; + int status; + regmatch_t regmatche; + if (regcomp(®, pattern, REG_EXTENDED) != 0) { + fprintf(stderr, "Error happen when check uuid format:%m"); + } + status = regexec(®, uuid, 1, ®matche, 0); + regfree(®); + if (status == REG_NOMATCH) { + return true; + } else { + return false; + } +} + +bool bad_dev(char *devname) +{ + const char *pattern = "^/dev/[a-zA-Z0-9]*$"; + regex_t reg; + int status; + regmatch_t regmatche; + if (regcomp(®, pattern, REG_EXTENDED) != 0) { + fprintf(stderr, + "Error happen when check device name format:%m"); + } + status = regexec(®, devname, 1, ®matche, 0); + regfree(®); + if (status == REG_NOMATCH) { + return true; + } else { + return false; + } +} + + +int ctlusage() +{ + fprintf(stderr, + "Usage:bcache-ctl [SUBCMD]\n" + " show show all bcache devices in this host\n" + " tree show active bcache devices in this host\n" + " make make regular device to bcache device\n" + " regist regist device to kernel\n" + " unregist unregist device from kernel\n" + " attach attach backend device(data device) to cache device\n" + " detach detach backend device(data device) from cache device\n" + " set-cachemode set cachemode for backend device\n"); + return EXIT_FAILURE; +} + +int showusage() +{ + fprintf(stderr, + "Usage: show [option]" + " show overall information about all devices\n" + " -d --device {devname} show the detail infomation about this device\n" + " -m --more show overall information about all devices with detail info \n" + " -h --help show help information \n"); + return EXIT_FAILURE; +} + +int treeusage() +{ + fprintf(stderr, + "Usage: tree show active bcache devices in this host\n"); + return EXIT_FAILURE; +} + +int registusage() +{ + fprintf(stderr, + "Usage:regist devicename regist device as bcache device to kernel\n"); + return EXIT_FAILURE; +} + +int unregistusage() +{ + fprintf(stderr, + "Usage:unregist devicename unregist device from kernel\n"); + return EXIT_FAILURE; +} + +int attachusage() +{ + fprintf(stderr, "Usage:attach cset_uuid|cachedevice datadevice\n"); + return EXIT_FAILURE; +} + +int detachusage() +{ + fprintf(stderr, "Usage:detach devicename\n"); + return EXIT_FAILURE; +} + +int setcachemodeusage() +{ + fprintf(stderr, "Usage:set-cachemode devicename modetype\n"); + return EXIT_FAILURE; +} + + +void free_dev(struct list_head *head) +{ + struct dev *dev; + list_for_each_entry(dev, head, dev_list) { + free(dev); + } +} + +int show_bdevs_detail() +{ + struct list_head head; + struct dev *devs; + INIT_LIST_HEAD(&head); + int ret; + + ret = list_bdevs(&head); + if (ret != 0) { + fprintf(stderr, "Failed to list devices\n"); + return ret; + } + printf + ("Name\t\tUuid\t\t\t\t\tCset_Uuid\t\t\t\tType\t\tState\t\tBname\t\tAttachToDev\tAttachToCset\n"); + list_for_each_entry(devs, &head, dev_list) { + printf("%s\t%s\t%s\t%d", devs->name, devs->uuid, + devs->cset, devs->version); + switch (devs->version) { + // These are handled the same by the kernel + case BCACHE_SB_VERSION_CDEV: + case BCACHE_SB_VERSION_CDEV_WITH_UUID: + printf(" (cache)"); + break; + + // The second adds data offset supporet + case BCACHE_SB_VERSION_BDEV: + case BCACHE_SB_VERSION_BDEV_WITH_OFFSET: + printf(" (data)"); + break; + + default: + printf(" (unknown)"); + break; + } + + printf("\t%-8s", devs->state); + printf("\t%-16s", devs->bname); + + char attachdev[30]; + if (strlen(devs->attachuuid) == 36) { + cset_to_devname(&head, devs->cset, attachdev); + } else if (devs->version == BCACHE_SB_VERSION_CDEV + || devs->version == + BCACHE_SB_VERSION_CDEV_WITH_UUID) { + strcpy(attachdev, BCACHE_NO_SUPPORT); + } else { + strcpy(attachdev, BCACHE_ATTACH_ALONE); + } + printf("%-16s", attachdev); + + printf("%s", devs->attachuuid); + putchar('\n'); + } + free_dev(&head); + return 0; +} + + +int show_bdevs() +{ + struct list_head head; + struct dev *devs; + INIT_LIST_HEAD(&head); + int ret; + ret = list_bdevs(&head); + if (ret != 0) { + fprintf(stderr, "Failed to list devices\n"); + return ret; + } + + printf("Name\t\tType\t\tState\t\tBname\t\tAttachToDev\n"); + list_for_each_entry(devs, &head, dev_list) { + printf("%s\t%d", devs->name, devs->version); + switch (devs->version) { + // These are handled the same by the kernel + case BCACHE_SB_VERSION_CDEV: + case BCACHE_SB_VERSION_CDEV_WITH_UUID: + printf(" (cache)"); + break; + + // The second adds data offset supporet + case BCACHE_SB_VERSION_BDEV: + case BCACHE_SB_VERSION_BDEV_WITH_OFFSET: + printf(" (data)"); + break; + + default: + printf(" (unknown)"); + break; + } + + printf("\t%-8s", devs->state); + printf("\t%-16s", devs->bname); + + char attachdev[30]; + if (strlen(devs->attachuuid) == 36) { + cset_to_devname(&head, devs->cset, attachdev); + } else if (devs->version == BCACHE_SB_VERSION_CDEV + || devs->version == + BCACHE_SB_VERSION_CDEV_WITH_UUID) { + strcpy(attachdev, BCACHE_NO_SUPPORT); + } else { + strcpy(attachdev, BCACHE_ATTACH_ALONE); + } + printf("%s", attachdev); + putchar('\n'); + } + free_dev(&head); + return 0; +} + +int detail(char *devname) +{ + struct bdev bd; + struct cdev cd; + int type = 1; + int ret; + ret = detail_dev(devname, &bd, &cd, &type); + if (ret != 0) { + fprintf(stderr, "Failed to detail device\n"); + return ret; + } + if (type == BCACHE_SB_VERSION_BDEV) { + printf("sb.magic\t\t%s\n", bd.base.magic); + printf("sb.first_sector\t\t%" PRIu64 "\n", + bd.base.first_sector); + printf("sb.csum\t\t\t%" PRIX64 "\n", bd.base.csum); + printf("sb.version\t\t%" PRIu64, bd.base.version); + printf(" [backing device]\n"); + putchar('\n'); + printf("dev.label\t\t"); + if (*bd.base.label) { + print_encode(bd.base.label); + } else { + printf("(empty)"); + } + putchar('\n'); + printf("dev.uuid\t\t%s\n", bd.base.uuid); + printf("dev.sectors_per_block\t%u\n" + "dev.sectors_per_bucket\t%u\n", + bd.base.sectors_per_block, + bd.base.sectors_per_bucket); + printf("dev.data.first_sector\t%ju\n" + "dev.data.cache_mode\t%ju", + bd.first_sector, bd.cache_mode); + switch (bd.cache_mode) { + case CACHE_MODE_WRITETHROUGH: + printf(" [writethrough]\n"); + break; + case CACHE_MODE_WRITEBACK: + printf(" [writeback]\n"); + break; + case CACHE_MODE_WRITEAROUND: + printf(" [writearound]\n"); + break; + case CACHE_MODE_NONE: + printf(" [no caching]\n"); + break; + default: + putchar('\n'); + } + printf("dev.data.cache_state\t%ju", bd.cache_state); + switch (bd.cache_state) { + case BDEV_STATE_NONE: + printf(" [detached]\n"); + break; + case BDEV_STATE_CLEAN: + printf(" [clean]\n"); + break; + case BDEV_STATE_DIRTY: + printf(" [dirty]\n"); + break; + case BDEV_STATE_STALE: + printf(" [inconsistent]\n"); + break; + default: + putchar('\n'); + } + + putchar('\n'); + printf("cset.uuid\t\t%s\n", bd.base.cset); + } else if (type == BCACHE_SB_VERSION_CDEV + || type == BCACHE_SB_VERSION_CDEV_WITH_UUID) { + printf("sb.magic\t\t%s\n", cd.base.magic); + printf("sb.first_sector\t\t%" PRIu64 "\n", + cd.base.first_sector); + printf("sb.csum\t\t\t%" PRIX64 "\n", cd.base.csum); + printf("sb.version\t\t%" PRIu64, cd.base.version); + printf(" [cache device]\n"); + putchar('\n'); + printf("dev.label\t\t"); + if (*cd.base.label) { + print_encode(cd.base.label); + } else { + printf("(empty)"); + } + putchar('\n'); + printf("dev.uuid\t\t%s\n", cd.base.uuid); + printf("dev.sectors_per_block\t%u\n" + "dev.sectors_per_bucket\t%u\n", + cd.base.sectors_per_block, + cd.base.sectors_per_bucket); + printf("dev.cache.first_sector\t%u\n" + "dev.cache.cache_sectors\t%ju\n" + "dev.cache.total_sectors\t%ju\n" + "dev.cache.ordered\t%s\n" + "dev.cache.discard\t%s\n" + "dev.cache.pos\t\t%u\n" + "dev.cache.replacement\t%d", + cd.first_sector, + cd.cache_sectors, + cd.total_sectors, + cd.ordered ? "yes" : "no", + cd.discard ? "yes" : "no", cd.pos, cd.replacement); + switch (cd.replacement) { + case CACHE_REPLACEMENT_LRU: + printf(" [lru]\n"); + break; + case CACHE_REPLACEMENT_FIFO: + printf(" [fifo]\n"); + break; + case CACHE_REPLACEMENT_RANDOM: + printf(" [random]\n"); + break; + default: + putchar('\n'); + } + + putchar('\n'); + printf("cset.uuid\t\t%s\n", cd.base.cset); + } else { + return 1; + } + return 0; +} + +int tree() +{ + struct list_head head; + struct dev *devs, *tmp; + INIT_LIST_HEAD(&head); + int ret; + ret = list_bdevs(&head); + if (ret != 0) { + fprintf(stderr, "Failed to list devices\n"); + return ret; + } + struct libscols_table *tb; + struct libscols_line *dad, *son; + enum { COL_CSET, COL_BNAME }; + setlocale(LC_ALL, ""); + tb = scols_new_table(); + scols_table_new_column(tb, ".", 0.1, SCOLS_FL_TREE); + scols_table_new_column(tb, "", 2, SCOLS_FL_TRUNC); + list_for_each_entry(devs, &head, dev_list) { + if ((devs->version == BCACHE_SB_VERSION_CDEV + || devs->version == BCACHE_SB_VERSION_CDEV_WITH_UUID) + && strcmp(devs->state, BCACHE_BASIC_STATE_ACTIVE) == 0) { + dad = scols_table_new_line(tb, NULL); + scols_line_set_data(dad, COL_CSET, devs->name); + list_for_each_entry(tmp, &head, dev_list) { + if (strcmp(devs->cset, tmp->attachuuid) == + 0) { + son = + scols_table_new_line(tb, dad); + scols_line_set_data(son, COL_CSET, + tmp->name); + scols_line_set_data(son, COL_BNAME, + tmp->bname); + } + } + } + } + scols_print_table(tb); + scols_unref_table(tb); + free_dev(&head); + return 0; +} + +int attach_both(char *cdev, char *backdev) +{ + struct bdev bd; + struct cdev cd; + int type = 1; + int ret; + char buf[100]; + ret = detail_dev(backdev, &bd, &cd, &type); + if (ret < 0) { + return ret; + } + if (type != BCACHE_SB_VERSION_BDEV + && type != BCACHE_SB_VERSION_BDEV_WITH_OFFSET) { + fprintf(stderr, "%s is not an backend device\n", backdev); + return 1; + } + if (strcmp(bd.base.attachuuid, BCACHE_BNAME_NOT_EXIST) != 0) { + fprintf(stderr, + "This device have attached to another cset\n"); + return 1; + } + + if (strlen(cdev) != 36) { + ret = detail_dev(cdev, &bd, &cd, &type); + if (type != BCACHE_SB_VERSION_CDEV + && type != BCACHE_SB_VERSION_CDEV_WITH_UUID) { + fprintf(stderr, "%s is not an cache device", cdev); + return 1; + } + strcpy(buf, cd.base.cset); + } else { + strcpy(buf, cdev); + } + return attach(buf, backdev); +} + +int main(int argc, char **argv) +{ + char *subcmd; + if (argc < 2) { + ctlusage(); + return 1; + } else { + subcmd = argv[1]; + argc--; + argv += 1; + } + + if (strcmp(subcmd, "make") == 0) { + return make_bcache(argc, argv); + + } else if (strcmp(subcmd, "show") == 0) { + int o = 0; + char *devname; + int more = 0; + int device = 0; + int help = 0; + + static struct option long_options[] = { + {"more", no_argument, 0, 'm'}, + {"help", no_argument, 0, 'h'}, + {"device", required_argument, 0, 'd'}, + {0, 0, 0, 0} + }; + int option_index = 0; + while ((o = + getopt_long(argc, argv, "hmd:", long_options, + &option_index)) != EOF) { + switch (o) { + case 'd': + devname = optarg; + device = 1; + break; + case 'm': + more = 1; + break; + case 'h': + help = 1; + break; + case '?': + return 1; + } + } + argc -= optind; + if (help || argc != 0) { + return showusage(); + } else if (more) { + return show_bdevs_detail(); + } else if (device) { + if (bad_dev(devname)) { + fprintf(stderr, + "Error:Wrong device name found\n"); + return 1; + } + return detail(devname); + } else { + return show_bdevs(); + } + } else if (strcmp(subcmd, "tree") == 0) { + if (argc != 1) { + return treeusage(); + } + return tree(); + } else if (strcmp(subcmd, "regist") == 0) { + if (argc != 2 || strcmp(argv[1], "-h") == 0) { + return registusage(); + } + if (bad_dev(argv[1])) { + fprintf(stderr, "Error:Wrong device name found\n"); + return 1; + } + return regist(argv[1]); + } else if (strcmp(subcmd, "unregist") == 0) { + if (argc != 2 || strcmp(argv[1], "-h") == 0) { + return unregistusage(); + } + if (bad_dev(argv[1])) { + fprintf(stderr, "Error:Wrong device name found\n"); + return 1; + } + struct bdev bd; + struct cdev cd; + int type = 1; + int ret; + ret = detail_dev(argv[1], &bd, &cd, &type); + if (ret != 0) { + return ret; + } + if (type == BCACHE_SB_VERSION_BDEV) { + return stop_backdev(argv[1]); + } else if (type == BCACHE_SB_VERSION_CDEV + || type == BCACHE_SB_VERSION_CDEV_WITH_UUID) { + return unregist_cset(cd.base.cset); + } + return 1; + } else if (strcmp(subcmd, "attach") == 0) { + if (argc != 3 || strcmp(argv[1], "-h") == 0) { + return attachusage(); + } + if (bad_dev(argv[1]) && bad_uuid(argv[1]) + || bad_dev(argv[2])) { + fprintf(stderr, + "Error:Wrong device name or cache_set uuid found\n"); + return 1; + } + return attach_both(argv[1], argv[2]); + } else if (strcmp(subcmd, "detach") == 0) { + if (argc != 2 || strcmp(argv[1], "-h") == 0) { + return detachusage(); + } + if (bad_dev(argv[1])) { + fprintf(stderr, "Error:Wrong device name found\n"); + return 1; + } + return detach(argv[1]); + } else if (strcmp(subcmd, "set-cachemode") == 0) { + if (argc != 3) { + return setcachemodeusage(); + } + if (bad_dev(argv[1])) { + fprintf(stderr, "Error:Wrong device name found\n"); + return 1; + } + return set_backdev_cachemode(argv[1], argv[2]); + } else { + ctlusage(); + } + return 0; +} diff --git a/bcache.c b/bcache.c index 8f37445..8b4b986 100644 --- a/bcache.c +++ b/bcache.c @@ -115,7 +115,7 @@ static const uint64_t crc_table[256] = { 0x9AFCE626CE85B507ULL }; -inline uint64_t crc64(const void *_data, size_t len) +uint64_t crc64(const void *_data, size_t len) { uint64_t crc = 0xFFFFFFFFFFFFFFFFULL; const unsigned char *data = _data; diff --git a/lib.c b/lib.c new file mode 100644 index 0000000..c4cef4d --- /dev/null +++ b/lib.c @@ -0,0 +1,479 @@ +/* + * Author: Shaoxiong Li <dahefanteng@xxxxxxxxx> + * + * GPLv2 + */ + + +#include <stdbool.h> +#include <blkid/blkid.h> +#include <dirent.h> +#include <sys/types.h> +#include <unistd.h> +#include <stdio.h> +#include <fcntl.h> +#include "bcache.h" +#include "lib.h" +#include <uuid/uuid.h> +#include <string.h> +#include <malloc.h> + + +/* + * utils function + */ + +static void trim_prefix(char *dest, char *src, int num) +{ + strcpy(dest, src + num); +} + +static void get_tail(char *dest, char *src, int n) +{ + int num, i; + num = strlen(src); + for (i = 0; i < n; i++) { + dest[i] = src[num - n + i]; + } + dest[i] = '\0'; +} + +static void trim_tail(char *src, int n) +{ + int num; + num = strlen(src); + src[num - n] = '\0'; +} + + +int get_backdev_state(char *devname, char *state) +{ + FILE *fd; + char path[100]; + char buf[40]; + trim_prefix(buf, devname, DEV_PREFIX_LEN); + sprintf(path, "/sys/block/%s/bcache/state", buf); + fd = fopen(path, "r"); + if (fd == NULL) { + strcpy(state, BCACHE_BASIC_STATE_INACTIVE); + return 0; + } + int i = 0; + while ((state[i] = getc(fd)) != '\n') { + i++; + } + state[i] = '\0'; + fclose(fd); + return 0; +} + +int get_cachedev_state(char *cset_id, char *state) +{ + DIR *dir = NULL; + char path[100]; + sprintf(path, "/sys/fs/bcache/%s/", cset_id); + dir = opendir(path); + if (dir == NULL) { + strcpy(state, BCACHE_BASIC_STATE_INACTIVE); + } else { + strcpy(state, BCACHE_BASIC_STATE_ACTIVE); + } + closedir(dir); + return 0; +} + +int get_state(struct dev *dev, char *state) +{ + if (dev->version == BCACHE_SB_VERSION_CDEV + || dev->version == BCACHE_SB_VERSION_CDEV_WITH_UUID) { + return get_cachedev_state(dev->cset, state); + } else if (dev->version == BCACHE_SB_VERSION_BDEV + || dev->version == BCACHE_SB_VERSION_BDEV_WITH_OFFSET) { + return get_backdev_state(dev->name, state); + } else { + return 1; + } +} + + +int get_dev_bname(char *devname, char *bname) +{ + int ret; + char path[100]; + char buf[40]; + char link[100]; + trim_prefix(buf, devname, DEV_PREFIX_LEN); + sprintf(path, "/sys/block/%s/bcache/dev", buf); + ret = readlink(path, link, sizeof(link)); + if (ret < 0) { + strcpy(bname, BCACHE_BNAME_NOT_EXIST); + } else { + trim_tail(link, strlen(link) - ret); + strcpy(bname, link + 41); + } + return 0; +} + +int get_bname(struct dev *dev, char *bname) +{ + if (dev->version == BCACHE_SB_VERSION_CDEV + || dev->version == BCACHE_SB_VERSION_CDEV_WITH_UUID) { + strcpy(bname, BCACHE_NO_SUPPORT); + } else if (dev->version == BCACHE_SB_VERSION_BDEV + || dev->version == BCACHE_SB_VERSION_BDEV_WITH_OFFSET) { + return get_dev_bname(dev->name, bname); + } + return 0; +} + +int get_backdev_attachpoint(char *devname, char *point) +{ + int ret; + char path[100]; + char buf[20]; + char link[100]; + char uuid[40]; + trim_prefix(buf, devname, DEV_PREFIX_LEN); + sprintf(path, "/sys/block/%s/bcache/cache", buf); + ret = readlink(path, link, sizeof(link)); + if (ret < 0) { + strcpy(point, BCACHE_BNAME_NOT_EXIST); + } else { + trim_tail(link, strlen(link) - ret); + get_tail(uuid, link, 36); + strcpy(point, uuid); + } + return 0; +} + +int get_point(struct dev *dev, char *point) +{ + if (dev->version == BCACHE_SB_VERSION_CDEV + || dev->version == BCACHE_SB_VERSION_CDEV_WITH_UUID) { + strcpy(point, BCACHE_NO_SUPPORT); + } else if (dev->version == BCACHE_SB_VERSION_BDEV + || dev->version == BCACHE_SB_VERSION_BDEV_WITH_OFFSET) { + return get_backdev_attachpoint(dev->name, point); + } + return 0; +} + +int cset_to_devname(struct list_head *head, char *cset, char *devname) +{ + struct dev *dev; + list_for_each_entry(dev, head, dev_list) { + if ((dev->version == BCACHE_SB_VERSION_CDEV + || dev->version == BCACHE_SB_VERSION_CDEV_WITH_UUID) + && strcmp(dev->cset, cset) == 0) { + strcpy(devname, dev->name); + } + } + return 0; +} + + +int detail_base(char *devname, struct cache_sb sb, struct dev *base) +{ + int ret; + strcpy(base->name, devname); + base->magic = "ok"; + base->first_sector = SB_SECTOR; + base->csum = sb.csum; + base->version = sb.version; + + strncpy(base->label, (char *) sb.label, SB_LABEL_SIZE); + base->label[SB_LABEL_SIZE] = '\0'; + + uuid_unparse(sb.uuid, base->uuid); + uuid_unparse(sb.set_uuid, base->cset); + base->sectors_per_block = sb.block_size; + base->sectors_per_bucket = sb.bucket_size; + if ((ret = get_state(base, base->state)) != 0) { + fprintf(stderr, "Failed to get state for %s\n", devname); + return ret; + } + if ((ret = get_bname(base, base->bname)) != 0) { + fprintf(stderr, "Failed to get bname for %s\n", devname); + return ret; + } + if ((ret = get_point(base, base->attachuuid)) != 0) { + fprintf(stderr, "Failed to get attachuuid for %s\n", + devname); + return ret; + } + return 0; +} + +int list_bdevs(struct list_head *head) +{ + DIR *dir; + struct dirent *ptr; + struct cache_sb sb; + dir = opendir("/sys/block"); + if (dir == NULL) { + fprintf(stderr, "Unable to open dir /sys/block\n"); + return 1; + } + while ((ptr = readdir(dir)) != NULL) { + if (strcmp(ptr->d_name, ".") == 0 + || strcmp(ptr->d_name, "..") == 0) { + continue; + } + char dev[20]; + sprintf(dev, "/dev/%s", ptr->d_name); + int fd = open(dev, O_RDONLY); + if (fd == -1) { + continue; + } + + if (pread(fd, &sb, sizeof(sb), SB_START) != sizeof(sb)) { + close(fd); + continue; + } + if (memcmp(sb.magic, bcache_magic, 16)) { + close(fd); + continue; + } + struct dev *tmp; + int ret; + tmp = (struct dev *) malloc(DEVLEN); + ret = detail_base(dev, sb, tmp); + if (ret != 0) { + fprintf(stderr, + "Failed to get information for %s\n", dev); + return 1; + } else { + list_add_tail(&tmp->dev_list, head); + } + } + closedir(dir); + return 0; +} + +int detail_dev(char *devname, struct bdev *bd, struct cdev *cd, int *type) +{ + struct cache_sb sb; + uint64_t expected_csum; + int fd = open(devname, O_RDONLY); + if (fd < 0) { + fprintf(stderr, "Error: Can't open dev %s\n", devname); + return 1; + } + + if (pread(fd, &sb, sizeof(sb), SB_START) != sizeof(sb)) { + fprintf(stderr, "Couldn't read\n"); + goto Fail; + } + + if (memcmp(sb.magic, bcache_magic, 16)) { + fprintf(stderr, + "Bad magic,make sure this is an bcache device\n"); + goto Fail; + } + + if (!sb.offset == SB_SECTOR) { + fprintf(stderr, "Invalid superblock (bad sector)\n"); + goto Fail; + } + + expected_csum = csum_set(&sb); + if (!sb.csum == expected_csum) { + fprintf(stderr, "Csum is not match with expected one"); + goto Fail; + } + + *type = sb.version; + if (sb.version == BCACHE_SB_VERSION_BDEV) { + detail_base(devname, sb, &bd->base); + bd->first_sector = BDEV_DATA_START_DEFAULT; + bd->cache_mode = BDEV_CACHE_MODE(&sb); + bd->cache_state = BDEV_STATE(&sb); + } else if (sb.version == BCACHE_SB_VERSION_CDEV + || sb.version == BCACHE_SB_VERSION_CDEV_WITH_UUID) { + detail_base(devname, sb, &cd->base); + cd->first_sector = sb.bucket_size * sb.first_bucket; + cd->cache_sectors = + sb.bucket_size * (sb.nbuckets - sb.first_bucket); + cd->total_sectors = sb.bucket_size * sb.nbuckets; + cd->ordered = CACHE_SYNC(&sb); + cd->discard = CACHE_DISCARD(&sb); + cd->pos = sb.nr_this_dev; + cd->replacement = CACHE_REPLACEMENT(&sb); + } else { + fprintf(stderr, "Unknown bcache device type found"); + goto Fail; + } + return 0; + Fail: + close(fd); + return 1; +} + +int regist(char *devname) +{ + int fd; + fd = open("/sys/fs/bcache/register", O_WRONLY); + if (fd < 0) { + perror("Error opening /sys/fs/bcache/register"); + fprintf(stderr, + "The bcache kernel module must be loaded\n"); + return 1; + } + if (dprintf(fd, "%s\n", devname) < 0) { + fprintf(stderr, "Error registering %s with bcache: %m\n", + devname); + close(fd); + return 1; + } + close(fd); + return 0; +} + +int unregist_cset(char *cset) +{ + int fd; + char path[100]; + sprintf(path, "/sys/fs/bcache/%s/unregister", cset); + fd = open(path, O_WRONLY); + if (fd < 0) { + fprintf(stderr, "Can't open %s\n", path); + return 1; + } + if (dprintf(fd, "%d\n", 1) < 0) { + fprintf(stderr, "Failed to unregist this cache device"); + close(fd); + return 1; + } + close(fd); + return 0; +} + +int stop_backdev(char *devname) +{ + char path[100]; + int fd; + char buf[20]; + trim_prefix(buf, devname, DEV_PREFIX_LEN); + sprintf(path, "/sys/block/%s/bcache/stop", buf); + fd = open(path, O_WRONLY); + if (fd < 0) { + fprintf(stderr, "Can't open %s\n", path); + return 1; + } + if (dprintf(fd, "%s\n", "1") < 0) { + fprintf(stderr, "Error stop back device %s\n", devname); + close(fd); + return 1; + } + close(fd); + return 0; +} + +int unregist_both(char *cset) +{ + int fd; + char path[100]; + sprintf(path, "/sys/fs/bcache/%s/stop", cset); + fd = open(path, O_WRONLY); + if (fd < 0) { + fprintf(stderr, "Can't open %s\n", path); + return 1; + } + if (dprintf(fd, "%d\n", 1) < 0) { + fprintf(stderr, "Failed to stop cset and its backends %m"); + close(fd); + return 1; + } + close(fd); + return 0; +} + +int attach(char *cset, char *devname) +{ + int fd; + char buf[20]; + trim_prefix(buf, devname, DEV_PREFIX_LEN); + char path[100]; + sprintf(path, "/sys/block/%s/bcache/attach", buf); + fd = open(path, O_WRONLY); + if (fd < 0) { + fprintf(stderr, "Can't open %s:%m\n", path); + return 1; + } + if (dprintf(fd, "%s\n", cset) < 0) { + fprintf(stderr, "Failed to attache to cset %s\n", cset); + close(fd); + return 1; + } + close(fd); + return 0; +} + +int detach(char *devname) +{ + int fd; + char buf[20]; + trim_prefix(buf, devname, DEV_PREFIX_LEN); + char path[100]; + sprintf(path, "/sys/block/%s/bcache/detach", buf); + fd = open(path, O_WRONLY); + if (fd < 0) { + fprintf(stderr, + "Can't open %s,Make sure the device name is correct\n", + path); + return 1; + } + if (dprintf(fd, "%d\n", 1) < 0) { + close(fd); + fprintf(stderr, "Error detach device %s:%m", devname); + return 1; + } + close(fd); + return 0; +} + +int set_backdev_cachemode(char *devname, char *cachemode) +{ + int fd; + char path[100]; + char buf[20]; + trim_prefix(buf, devname, DEV_PREFIX_LEN); + sprintf(path, "/sys/block/%s/bcache/cache_mode", buf); + fd = open(path, O_WRONLY); + if (fd < 0) { + fprintf(stderr, + "Can't open %s,Make sure the device name is correct\n", + path); + return 1; + } + if (dprintf(fd, "%s\n", cachemode) < 0) { + printf("Failed to set cachemode for device %s:%m\n", + devname); + close(fd); + return 1; + } + close(fd); + return 0; +} + +int get_backdev_cachemode(char *devname, char *mode) +{ + int fd; + char path[100]; + sprintf(path, "/sys/block/%s/bcache/cache_mode", devname); + fd = open(path, O_RDONLY); + if (fd < 0) { + perror("Error opening /sys/fs/bcache/register"); + fprintf(stderr, + "The bcache kernel module must be loaded\n"); + return 1; + } + printf("size in func is %d", sizeof(mode)); + if (read(fd, mode, 100) < 0) { + fprintf(stderr, "Failed to fetch device cache mode\n"); + close(fd); + return 1; + } + close(fd); + return 0; +} diff --git a/lib.h b/lib.h new file mode 100644 index 0000000..f8fd218 --- /dev/null +++ b/lib.h @@ -0,0 +1,67 @@ +/* + * Author: Shaoxiong Li <dahefanteng@xxxxxxxxx> + * + * GPLv2 + */ + + +#include "list.h" + +struct dev { + char name[40]; + char *magic; + uint64_t first_sector; + uint64_t csum; + int version; + char label[SB_LABEL_SIZE + 1]; + char uuid[40]; + int sectors_per_block; + int sectors_per_bucket; + char cset[40]; + char state[40]; + char bname[40]; + char attachuuid[40]; + struct list_head dev_list; +}; + +struct bdev { + struct dev base; + int first_sector; + int cache_mode; + int cache_state; +}; + +//typedef int bool; +struct cdev { + struct dev base; + int first_sector; + int cache_sectors; + int total_sectors; + bool ordered; + bool discard; + int pos; + unsigned int replacement; +}; + + +int list_bdevs(struct list_head *head); +int detail_dev(char *devname, struct bdev *bd, struct cdev *cd, int *type); +int regist(char *devname); +int stop_backdev(char *devname); +int unregist_cset(char *cset); +int attach(char *cset, char *devname); +int detach(char *devname); +int set_backdev_cachemode(char *devname, char *cachemode); +int cset_to_devname(struct list_head *head, char *cset, char *devname); + + +#define DEVLEN sizeof(struct dev) + +#define BCACHE_NO_SUPPORT "N/A" + +#define BCACHE_BASIC_STATE_ACTIVE "active" +#define BCACHE_BASIC_STATE_INACTIVE "inactive" + +#define BCACHE_ATTACH_ALONE "Alone" +#define BCACHE_BNAME_NOT_EXIST "Non-Exist" +#define DEV_PREFIX_LEN 5 diff --git a/list.h b/list.h new file mode 100644 index 0000000..224dd03 --- /dev/null +++ b/list.h @@ -0,0 +1,519 @@ +/** + * Copy from http://www.mcs.anl.gov/~kazutomo/list/list.h.And eventually + * copy from kernel as the author said below: + * + * I grub it from linux kernel source code and fix it for user space + * program. Of course, this is a GPL licensed header file. + * + * Here is a recipe to cook list.h for user space program + * + * 1. copy list.h from linux/include/list.h + * 2. remove + * - #ifdef __KERNE__ and its #endif + * - all #include line + * - prefetch() and rcu related functions + * 3. add macro offsetof() and container_of + * + * - kazutomo@xxxxxxxxxxx + */ +#ifndef _LINUX_LIST_H +#define _LINUX_LIST_H + +/** + * @name from other kernel headers + */ +/*@{*/ + +/** + * Get offset of a member + */ +#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) + +/** + * Casts a member of a structure out to the containing structure + * @param ptr the pointer to the member. + * @param type the type of the container struct this is embedded in. + * @param member the name of the member within the struct. + * + */ +#define container_of(ptr, type, member) ({ \ + const typeof( ((type *)0)->member ) *__mptr = (ptr); \ + (type *)( (char *)__mptr - offsetof(type,member) );}) +/*@}*/ + + +/* + * These are non-NULL pointers that will result in page faults + * under normal circumstances, used to verify that nobody uses + * non-initialized list entries. + */ +#define LIST_POISON1 ((void *) 0x00100100) +#define LIST_POISON2 ((void *) 0x00200200) + +/** + * Simple doubly linked list implementation. + * + * Some of the internal functions ("__xxx") are useful when + * manipulating whole lists rather than single entries, as + * sometimes we already know the next/prev entries and we can + * generate better code by using them directly rather than + * using the generic single-entry routines. + */ +struct list_head { + struct list_head *next, *prev; +}; + +#define LIST_HEAD_INIT(name) { &(name), &(name) } + +#define LIST_HEAD(name) \ + struct list_head name = LIST_HEAD_INIT(name) + +#define INIT_LIST_HEAD(ptr) do { \ + (ptr)->next = (ptr); (ptr)->prev = (ptr); \ +} while (0) + +/* + * Insert a new entry between two known consecutive entries. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +static inline void __list_add(struct list_head *new, + struct list_head *prev, + struct list_head *next) +{ + next->prev = new; + new->next = next; + new->prev = prev; + prev->next = new; +} + +/** + * list_add - add a new entry + * @new: new entry to be added + * @head: list head to add it after + * + * Insert a new entry after the specified head. + * This is good for implementing stacks. + */ +static inline void list_add(struct list_head *new, struct list_head *head) +{ + __list_add(new, head, head->next); +} + +/** + * list_add_tail - add a new entry + * @new: new entry to be added + * @head: list head to add it before + * + * Insert a new entry before the specified head. + * This is useful for implementing queues. + */ +static inline void list_add_tail(struct list_head *new, struct list_head *head) +{ + __list_add(new, head->prev, head); +} + + +/* + * Delete a list entry by making the prev/next entries + * point to each other. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +static inline void __list_del(struct list_head * prev, struct list_head * next) +{ + next->prev = prev; + prev->next = next; +} + +/** + * list_del - deletes entry from list. + * @entry: the element to delete from the list. + * Note: list_empty on entry does not return true after this, the entry is + * in an undefined state. + */ +static inline void list_del(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); + entry->next = LIST_POISON1; + entry->prev = LIST_POISON2; +} + + + +/** + * list_del_init - deletes entry from list and reinitialize it. + * @entry: the element to delete from the list. + */ +static inline void list_del_init(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); + INIT_LIST_HEAD(entry); +} + +/** + * list_move - delete from one list and add as another's head + * @list: the entry to move + * @head: the head that will precede our entry + */ +static inline void list_move(struct list_head *list, struct list_head *head) +{ + __list_del(list->prev, list->next); + list_add(list, head); +} + +/** + * list_move_tail - delete from one list and add as another's tail + * @list: the entry to move + * @head: the head that will follow our entry + */ +static inline void list_move_tail(struct list_head *list, + struct list_head *head) +{ + __list_del(list->prev, list->next); + list_add_tail(list, head); +} + +/** + * list_empty - tests whether a list is empty + * @head: the list to test. + */ +static inline int list_empty(const struct list_head *head) +{ + return head->next == head; +} + +static inline void __list_splice(struct list_head *list, + struct list_head *head) +{ + struct list_head *first = list->next; + struct list_head *last = list->prev; + struct list_head *at = head->next; + + first->prev = head; + head->next = first; + + last->next = at; + at->prev = last; +} + +/** + * list_splice - join two lists + * @list: the new list to add. + * @head: the place to add it in the first list. + */ +static inline void list_splice(struct list_head *list, struct list_head *head) +{ + if (!list_empty(list)) + __list_splice(list, head); +} + +/** + * list_splice_init - join two lists and reinitialise the emptied list. + * @list: the new list to add. + * @head: the place to add it in the first list. + * + * The list at @list is reinitialised + */ +static inline void list_splice_init(struct list_head *list, + struct list_head *head) +{ + if (!list_empty(list)) { + __list_splice(list, head); + INIT_LIST_HEAD(list); + } +} + +/** + * list_entry - get the struct for this entry + * @ptr: the &struct list_head pointer. + * @type: the type of the struct this is embedded in. + * @member: the name of the list_struct within the struct. + */ +#define list_entry(ptr, type, member) \ + container_of(ptr, type, member) + +/** + * list_for_each - iterate over a list + * @pos: the &struct list_head to use as a loop counter. + * @head: the head for your list. + */ + +#define list_for_each(pos, head) \ + for (pos = (head)->next; pos != (head); \ + pos = pos->next) + +/** + * __list_for_each - iterate over a list + * @pos: the &struct list_head to use as a loop counter. + * @head: the head for your list. + * + * This variant differs from list_for_each() in that it's the + * simplest possible list iteration code, no prefetching is done. + * Use this for code that knows the list to be very short (empty + * or 1 entry) most of the time. + */ +#define __list_for_each(pos, head) \ + for (pos = (head)->next; pos != (head); pos = pos->next) + +/** + * list_for_each_prev - iterate over a list backwards + * @pos: the &struct list_head to use as a loop counter. + * @head: the head for your list. + */ +#define list_for_each_prev(pos, head) \ + for (pos = (head)->prev; prefetch(pos->prev), pos != (head); \ + pos = pos->prev) + +/** + * list_for_each_safe - iterate over a list safe against removal of list entry + * @pos: the &struct list_head to use as a loop counter. + * @n: another &struct list_head to use as temporary storage + * @head: the head for your list. + */ +#define list_for_each_safe(pos, n, head) \ + for (pos = (head)->next, n = pos->next; pos != (head); \ + pos = n, n = pos->next) + +/** + * list_for_each_entry - iterate over list of given type + * @pos: the type * to use as a loop counter. + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + */ +#define list_for_each_entry(pos, head, member) \ + for (pos = list_entry((head)->next, typeof(*pos), member); \ + &pos->member != (head); \ + pos = list_entry(pos->member.next, typeof(*pos), member)) + +/** + * list_for_each_entry_reverse - iterate backwards over list of given type. + * @pos: the type * to use as a loop counter. + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + */ +#define list_for_each_entry_reverse(pos, head, member) \ + for (pos = list_entry((head)->prev, typeof(*pos), member); \ + &pos->member != (head); \ + pos = list_entry(pos->member.prev, typeof(*pos), member)) + +/** + * list_prepare_entry - prepare a pos entry for use as a start point in + * list_for_each_entry_continue + * @pos: the type * to use as a start point + * @head: the head of the list + * @member: the name of the list_struct within the struct. + */ +#define list_prepare_entry(pos, head, member) \ + ((pos) ? : list_entry(head, typeof(*pos), member)) + +/** + * list_for_each_entry_continue - iterate over list of given type + * continuing after existing point + * @pos: the type * to use as a loop counter. + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + */ +#define list_for_each_entry_continue(pos, head, member) \ + for (pos = list_entry(pos->member.next, typeof(*pos), member); \ + &pos->member != (head); \ + pos = list_entry(pos->member.next, typeof(*pos), member)) + +/** + * list_for_each_entry_safe - iterate over list of given type safe against removal of list entry + * @pos: the type * to use as a loop counter. + * @n: another type * to use as temporary storage + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + */ +#define list_for_each_entry_safe(pos, n, head, member) \ + for (pos = list_entry((head)->next, typeof(*pos), member), \ + n = list_entry(pos->member.next, typeof(*pos), member); \ + &pos->member != (head); \ + pos = n, n = list_entry(n->member.next, typeof(*n), member)) + +/** + * list_for_each_entry_safe_continue - iterate over list of given type + * continuing after existing point safe against removal of list entry + * @pos: the type * to use as a loop counter. + * @n: another type * to use as temporary storage + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + */ +#define list_for_each_entry_safe_continue(pos, n, head, member) \ + for (pos = list_entry(pos->member.next, typeof(*pos), member), \ + n = list_entry(pos->member.next, typeof(*pos), member); \ + &pos->member != (head); \ + pos = n, n = list_entry(n->member.next, typeof(*n), member)) + +/** + * list_for_each_entry_safe_reverse - iterate backwards over list of given type safe against + * removal of list entry + * @pos: the type * to use as a loop counter. + * @n: another type * to use as temporary storage + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + */ +#define list_for_each_entry_safe_reverse(pos, n, head, member) \ + for (pos = list_entry((head)->prev, typeof(*pos), member), \ + n = list_entry(pos->member.prev, typeof(*pos), member); \ + &pos->member != (head); \ + pos = n, n = list_entry(n->member.prev, typeof(*n), member)) + + + + +/* + * Double linked lists with a single pointer list head. + * Mostly useful for hash tables where the two pointer list head is + * too wasteful. + * You lose the ability to access the tail in O(1). + */ + +struct hlist_head { + struct hlist_node *first; +}; + +struct hlist_node { + struct hlist_node *next, **pprev; +}; + +#define HLIST_HEAD_INIT { .first = NULL } +#define HLIST_HEAD(name) struct hlist_head name = { .first = NULL } +#define INIT_HLIST_HEAD(ptr) ((ptr)->first = NULL) +#define INIT_HLIST_NODE(ptr) ((ptr)->next = NULL, (ptr)->pprev = NULL) + +static inline int hlist_unhashed(const struct hlist_node *h) +{ + return !h->pprev; +} + +static inline int hlist_empty(const struct hlist_head *h) +{ + return !h->first; +} + +static inline void __hlist_del(struct hlist_node *n) +{ + struct hlist_node *next = n->next; + struct hlist_node **pprev = n->pprev; + *pprev = next; + if (next) + next->pprev = pprev; +} + +static inline void hlist_del(struct hlist_node *n) +{ + __hlist_del(n); + n->next = LIST_POISON1; + n->pprev = LIST_POISON2; +} + + +static inline void hlist_del_init(struct hlist_node *n) +{ + if (n->pprev) { + __hlist_del(n); + INIT_HLIST_NODE(n); + } +} + +static inline void hlist_add_head(struct hlist_node *n, struct hlist_head *h) +{ + struct hlist_node *first = h->first; + n->next = first; + if (first) + first->pprev = &n->next; + h->first = n; + n->pprev = &h->first; +} + + + +/* next must be != NULL */ +static inline void hlist_add_before(struct hlist_node *n, + struct hlist_node *next) +{ + n->pprev = next->pprev; + n->next = next; + next->pprev = &n->next; + *(n->pprev) = n; +} + +static inline void hlist_add_after(struct hlist_node *n, + struct hlist_node *next) +{ + next->next = n->next; + n->next = next; + next->pprev = &n->next; + + if(next->next) + next->next->pprev = &next->next; +} + + + +#define hlist_entry(ptr, type, member) container_of(ptr,type,member) + +#define hlist_for_each(pos, head) \ + for (pos = (head)->first; pos && ({ prefetch(pos->next); 1; }); \ + pos = pos->next) + +#define hlist_for_each_safe(pos, n, head) \ + for (pos = (head)->first; pos && ({ n = pos->next; 1; }); \ + pos = n) + +/** + * hlist_for_each_entry - iterate over list of given type + * @tpos: the type * to use as a loop counter. + * @pos: the &struct hlist_node to use as a loop counter. + * @head: the head for your list. + * @member: the name of the hlist_node within the struct. + */ +#define hlist_for_each_entry(tpos, pos, head, member) \ + for (pos = (head)->first; \ + pos && ({ prefetch(pos->next); 1;}) && \ + ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \ + pos = pos->next) + +/** + * hlist_for_each_entry_continue - iterate over a hlist continuing after existing point + * @tpos: the type * to use as a loop counter. + * @pos: the &struct hlist_node to use as a loop counter. + * @member: the name of the hlist_node within the struct. + */ +#define hlist_for_each_entry_continue(tpos, pos, member) \ + for (pos = (pos)->next; \ + pos && ({ prefetch(pos->next); 1;}) && \ + ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \ + pos = pos->next) + +/** + * hlist_for_each_entry_from - iterate over a hlist continuing from existing point + * @tpos: the type * to use as a loop counter. + * @pos: the &struct hlist_node to use as a loop counter. + * @member: the name of the hlist_node within the struct. + */ +#define hlist_for_each_entry_from(tpos, pos, member) \ + for (; pos && ({ prefetch(pos->next); 1;}) && \ + ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \ + pos = pos->next) + +/** + * hlist_for_each_entry_safe - iterate over list of given type safe against removal of list entry + * @tpos: the type * to use as a loop counter. + * @pos: the &struct hlist_node to use as a loop counter. + * @n: another &struct hlist_node to use as temporary storage + * @head: the head for your list. + * @member: the name of the hlist_node within the struct. + */ +#define hlist_for_each_entry_safe(tpos, pos, n, head, member) \ + for (pos = (head)->first; \ + pos && ({ n = pos->next; 1; }) && \ + ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \ + pos = n) + + +#endif diff --git a/make.c b/make.c new file mode 100644 index 0000000..b0fd380 --- /dev/null +++ b/make.c @@ -0,0 +1,463 @@ +/* + * Author: Kent Overstreet <kmo@xxxxxxxxxxxxx> + * + * GPLv2 + * + * TODO: It's OK to merge this file with 'make-bcache.c' after the 'bcache' tool has been well tested. + */ + +#define _FILE_OFFSET_BITS 64 +#define __USE_FILE_OFFSET64 +#define _XOPEN_SOURCE 600 + +#include <blkid/blkid.h> +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <getopt.h> +#include <limits.h> +#include <linux/fs.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <uuid/uuid.h> + +#include "bcache.h" + +#define max(x, y) ({ \ + typeof(x) _max1 = (x); \ + typeof(y) _max2 = (y); \ + (void) (&_max1 == &_max2); \ + _max1 > _max2 ? _max1 : _max2; }) + +uint64_t getblocks(int fd) +{ + uint64_t ret; + struct stat statbuf; + if (fstat(fd, &statbuf)) { + perror("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"); + exit(EXIT_FAILURE); + } + return ret; +} + +uint64_t hatoi(const char *s) +{ + char *e; + long long i = strtoll(s, &e, 10); + switch (*e) { + case 't': + case 'T': + i *= 1024; + case 'g': + case 'G': + i *= 1024; + case 'm': + case 'M': + i *= 1024; + case 'k': + case 'K': + i *= 1024; + } + return i; +} + +unsigned hatoi_validate(const char *s, const char *msg) +{ + uint64_t v = hatoi(s); + + if (v & (v - 1)) { + fprintf(stderr, "%s must be a power of two\n", msg); + exit(EXIT_FAILURE); + } + + v /= 512; + + if (v > USHRT_MAX) { + fprintf(stderr, "%s too large\n", msg); + exit(EXIT_FAILURE); + } + + if (!v) { + fprintf(stderr, "%s too small\n", msg); + exit(EXIT_FAILURE); + } + + return v; +} + +char *skip_spaces(const char *str) +{ + while (isspace(*str)) + ++str; + return (char *)str; +} + +char *strim(char *s) +{ + size_t size; + char *end; + + s = skip_spaces(s); + size = strlen(s); + if (!size) + return s; + + end = s + size - 1; + while (end >= s && isspace(*end)) + end--; + *(end + 1) = '\0'; + + return s; +} + +ssize_t read_string_list(const char *buf, const char * const list[]) +{ + size_t i; + char *s, *d = strdup(buf); + if (!d) + return -ENOMEM; + + s = strim(d); + + for (i = 0; list[i]; i++) + if (!strcmp(list[i], s)) + break; + + free(d); + + if (!list[i]) + return -EINVAL; + + return i; +} + +void usage() +{ + fprintf(stderr, + "Usage: make-bcache [options] device\n" + " -C, --cache Format a cache device\n" + " -B, --bdev Format a backing device\n" + " -b, --bucket bucket size\n" + " -w, --block block size (hard sector size of SSD, often 2k)\n" + " -o, --data-offset data offset in sectors\n" + " --cset-uuid UUID for the cache set\n" +// " -U UUID\n" + " --writeback enable writeback\n" + " --discard enable discards\n" + " --cache_replacement_policy=(lru|fifo)\n" + " -h, --help display this help and exit\n"); + exit(EXIT_FAILURE); +} + +const char * const cache_replacement_policies[] = { + "lru", + "fifo", + "random", + NULL +}; + +static void write_sb(char *dev, unsigned block_size, unsigned bucket_size, + bool writeback, bool discard, bool wipe_bcache, + unsigned cache_replacement_policy, + uint64_t data_offset, + uuid_t set_uuid, bool bdev) +{ + int fd; + char uuid_str[40], set_uuid_str[40], zeroes[SB_START] = {0}; + struct cache_sb sb; + blkid_probe pr; + + if ((fd = open(dev, O_RDWR|O_EXCL)) == -1) { + fprintf(stderr, "Can't open dev %s: %s\n", dev, strerror(errno)); + exit(EXIT_FAILURE); + } + + if (pread(fd, &sb, sizeof(sb), SB_START) != sizeof(sb)) + exit(EXIT_FAILURE); + + if (!memcmp(sb.magic, bcache_magic, 16) && !wipe_bcache) { + fprintf(stderr, "Already a bcache device on %s, " + "overwrite with --wipe-bcache\n", dev); + exit(EXIT_FAILURE); + } + + if (!(pr = blkid_new_probe())) + exit(EXIT_FAILURE); + if (blkid_probe_set_device(pr, fd, 0, 0)) + exit(EXIT_FAILURE); + /* enable ptable probing; superblock probing is enabled by default */ + if (blkid_probe_enable_partitions(pr, true)) + exit(EXIT_FAILURE); + 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); + } + + memset(&sb, 0, sizeof(struct cache_sb)); + + sb.offset = SB_SECTOR; + sb.version = bdev + ? BCACHE_SB_VERSION_BDEV + : BCACHE_SB_VERSION_CDEV; + + memcpy(sb.magic, bcache_magic, 16); + uuid_generate(sb.uuid); + memcpy(sb.set_uuid, set_uuid, sizeof(sb.set_uuid)); + + sb.bucket_size = bucket_size; + sb.block_size = block_size; + + uuid_unparse(sb.uuid, uuid_str); + uuid_unparse(sb.set_uuid, set_uuid_str); + + if (SB_IS_BDEV(&sb)) { + SET_BDEV_CACHE_MODE( + &sb, writeback ? CACHE_MODE_WRITEBACK : CACHE_MODE_WRITETHROUGH); + + if (data_offset != BDEV_DATA_START_DEFAULT) { + sb.version = BCACHE_SB_VERSION_BDEV_WITH_OFFSET; + sb.data_offset = data_offset; + } + + printf("UUID: %s\n" + "Set UUID: %s\n" + "version: %u\n" + "block_size: %u\n" + "data_offset: %ju\n", + uuid_str, set_uuid_str, + (unsigned) sb.version, + sb.block_size, + data_offset); + } else { + sb.nbuckets = getblocks(fd) / sb.bucket_size; + sb.nr_in_set = 1; + sb.first_bucket = (23 / sb.bucket_size) + 1; + + if (sb.nbuckets < 1 << 7) { + fprintf(stderr, "Not enough buckets: %ju, need %u\n", + sb.nbuckets, 1 << 7); + exit(EXIT_FAILURE); + } + + SET_CACHE_DISCARD(&sb, discard); + SET_CACHE_REPLACEMENT(&sb, cache_replacement_policy); + + printf("UUID: %s\n" + "Set UUID: %s\n" + "version: %u\n" + "nbuckets: %ju\n" + "block_size: %u\n" + "bucket_size: %u\n" + "nr_in_set: %u\n" + "nr_this_dev: %u\n" + "first_bucket: %u\n", + uuid_str, set_uuid_str, + (unsigned) sb.version, + sb.nbuckets, + sb.block_size, + sb.bucket_size, + sb.nr_in_set, + sb.nr_this_dev, + sb.first_bucket); + } + + sb.csum = csum_set(&sb); + + /* Zero start of disk */ + if (pwrite(fd, zeroes, SB_START, 0) != SB_START) { + perror("write error\n"); + exit(EXIT_FAILURE); + } + /* Write superblock */ + if (pwrite(fd, &sb, sizeof(sb), SB_START) != sizeof(sb)) { + perror("write error\n"); + exit(EXIT_FAILURE); + } + + fsync(fd); + close(fd); +} + +static unsigned get_blocksize(const char *path) +{ + struct stat statbuf; + + if (stat(path, &statbuf)) { + fprintf(stderr, "Error statting %s: %s\n", + path, strerror(errno)); + exit(EXIT_FAILURE); + } + + if (S_ISBLK(statbuf.st_mode)) { + /* check IO limits: + * BLKALIGNOFF: alignment_offset + * BLKPBSZGET: physical_block_size + * BLKSSZGET: logical_block_size + * BLKIOMIN: minimum_io_size + * BLKIOOPT: optimal_io_size + * + * It may be tempting to use physical_block_size, + * or even minimum_io_size. + * But to be as transparent as possible, + * we want to use logical_block_size. + */ + unsigned int logical_block_size; + int fd = open(path, O_RDONLY); + + if (fd < 0) { + fprintf(stderr, "open(%s) failed: %m\n", path); + exit(EXIT_FAILURE); + } + if (ioctl(fd, BLKSSZGET, &logical_block_size)) { + fprintf(stderr, "ioctl(%s, BLKSSZGET) failed: %m\n", path); + exit(EXIT_FAILURE); + } + close(fd); + return logical_block_size / 512; + + } + /* else: not a block device. + * Why would we even want to write a bcache super block there? */ + + return statbuf.st_blksize / 512; +} + +int make_bcache(int argc, char **argv) +{ + int c, bdev = -1; + unsigned i, ncache_devices = 0, nbacking_devices = 0; + char *cache_devices[argc]; + char *backing_devices[argc]; + + unsigned block_size = 0, bucket_size = 1024; + int writeback = 0, discard = 0, wipe_bcache = 0; + unsigned cache_replacement_policy = 0; + uint64_t data_offset = BDEV_DATA_START_DEFAULT; + uuid_t set_uuid; + + uuid_generate(set_uuid); + + struct option opts[] = { + { "cache", 0, NULL, 'C' }, + { "bdev", 0, NULL, 'B' }, + { "bucket", 1, NULL, 'b' }, + { "block", 1, NULL, 'w' }, + { "writeback", 0, &writeback, 1 }, + { "wipe-bcache", 0, &wipe_bcache, 1 }, + { "discard", 0, &discard, 1 }, + { "cache_replacement_policy", 1, NULL, 'p' }, + { "cache-replacement-policy", 1, NULL, 'p' }, + { "data_offset", 1, NULL, 'o' }, + { "data-offset", 1, NULL, 'o' }, + { "cset-uuid", 1, NULL, 'u' }, + { "help", 0, NULL, 'h' }, + { NULL, 0, NULL, 0 }, + }; + + while ((c = getopt_long(argc, argv, + "-hCBUo:w:b:", + opts, NULL)) != -1) + switch (c) { + case 'C': + bdev = 0; + break; + case 'B': + bdev = 1; + break; + case 'b': + bucket_size = hatoi_validate(optarg, "bucket size"); + break; + case 'w': + block_size = hatoi_validate(optarg, "block size"); + break; +#if 0 + case 'U': + if (uuid_parse(optarg, sb.uuid)) { + fprintf(stderr, "Bad uuid\n"); + exit(EXIT_FAILURE); + } + break; +#endif + case 'p': + cache_replacement_policy = read_string_list(optarg, + cache_replacement_policies); + break; + case 'o': + data_offset = atoll(optarg); + if (data_offset < BDEV_DATA_START_DEFAULT) { + fprintf(stderr, "Bad data offset; minimum %d sectors\n", + BDEV_DATA_START_DEFAULT); + exit(EXIT_FAILURE); + } + break; + case 'u': + if (uuid_parse(optarg, set_uuid)) { + fprintf(stderr, "Bad uuid\n"); + exit(EXIT_FAILURE); + } + break; + case 'h': + usage(); + break; + case 1: + if (bdev == -1) { + fprintf(stderr, "Please specify -C or -B\n"); + exit(EXIT_FAILURE); + } + + if (bdev) + backing_devices[nbacking_devices++] = optarg; + else + cache_devices[ncache_devices++] = optarg; + break; + } + + if (!ncache_devices && !nbacking_devices) { + fprintf(stderr, "Please supply a device\n"); + usage(); + } + + if (bucket_size < block_size) { + fprintf(stderr, "Bucket size cannot be smaller than block size\n"); + exit(EXIT_FAILURE); + } + + if (!block_size) { + for (i = 0; i < ncache_devices; i++) + block_size = max(block_size, + get_blocksize(cache_devices[i])); + + for (i = 0; i < nbacking_devices; i++) + block_size = max(block_size, + get_blocksize(backing_devices[i])); + } + + for (i = 0; i < ncache_devices; i++) + write_sb(cache_devices[i], block_size, bucket_size, + writeback, discard, wipe_bcache, + cache_replacement_policy, + data_offset, set_uuid, false); + + for (i = 0; i < nbacking_devices; i++) + write_sb(backing_devices[i], block_size, bucket_size, + writeback, discard, wipe_bcache, + cache_replacement_policy, + data_offset, set_uuid, true); + + return 0; +} -- 1.8.3.1