aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorShaoxiong Li <dahefanteng@gmail.com>2018-08-14 21:58:47 +0800
committerColy Li <colyli@suse.de>2019-09-28 15:39:23 +0800
commit0c6eab309746ba7c9e8caf10b610ff76ff1c525e (patch)
treedd2238d393113eca9ee41c501f97fbad5d279a06
parenta73679b22c333763597d39c72112ef5a53f55419 (diff)
downloadbcache-tools-0c6eab309746ba7c9e8caf10b610ff76ff1c525e.tar.gz
bcache-tools: Add command line tool 'bcache'
show the bcache devices in both table-like format and tree-like format. users can use this tools to manage device(register,unregister,attach,detach,set-cachemode). Signed-off-by: Shaoxiong Li <dahefanteng@gmail.com> Signed-off-by: Coly Li <colyli@suse.de>
-rw-r--r--Makefile21
-rw-r--r--bcache-main.c607
-rw-r--r--bcache.c2
-rw-r--r--bcache.h2
-rw-r--r--lib.c479
-rw-r--r--lib.h67
-rw-r--r--list.h519
-rw-r--r--make.c463
8 files changed, 2155 insertions, 5 deletions
diff --git a/Makefile b/Makefile
index c824ae31..6c53995e 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 00000000..9e87b1e1
--- /dev/null
+++ b/bcache-main.c
@@ -0,0 +1,607 @@
+/*
+ * Author: Shaoxiong Li <dahefanteng@gmail.com>
+ *
+ * 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(&reg, pattern, REG_EXTENDED) != 0) {
+ fprintf(stderr, "Error happen when check uuid format:%m");
+ }
+ status = regexec(&reg, uuid, 1, &regmatche, 0);
+ regfree(&reg);
+ 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(&reg, pattern, REG_EXTENDED) != 0) {
+ fprintf(stderr,
+ "Error happen when check device name format:%m");
+ }
+ status = regexec(&reg, devname, 1, &regmatche, 0);
+ regfree(&reg);
+ 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 8f37445d..8b4b9866 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/bcache.h b/bcache.h
index 61e42524..a6263318 100644
--- a/bcache.h
+++ b/bcache.h
@@ -123,4 +123,6 @@ uint64_t crc64(const void *_data, size_t len);
#define csum_set(i) \
crc64(((void *) (i)) + 8, ((void *) end(i)) - (((void *) (i)) + 8))
+int make_bcache(int argc, char **argv);
+
#endif
diff --git a/lib.c b/lib.c
new file mode 100644
index 00000000..c4cef4d6
--- /dev/null
+++ b/lib.c
@@ -0,0 +1,479 @@
+/*
+ * Author: Shaoxiong Li <dahefanteng@gmail.com>
+ *
+ * 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 00000000..f8fd2185
--- /dev/null
+++ b/lib.h
@@ -0,0 +1,67 @@
+/*
+ * Author: Shaoxiong Li <dahefanteng@gmail.com>
+ *
+ * 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 00000000..fb03012d
--- /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@mcs.anl.gov
+ */
+#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 00000000..b0fd3808
--- /dev/null
+++ b/make.c
@@ -0,0 +1,463 @@
+/*
+ * Author: Kent Overstreet <kmo@daterainc.com>
+ *
+ * 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;
+}