// SPDX-License-Identifier: GPL-2.0 // Author: Shaoxiong Li #include #include #include #include #include #include #include #include #include #include #include #include #include "bcache.h" #include "lib.h" #include "bitwise.h" /* * utils function */ static unsigned int log2_u32(uint32_t n) { int r = 0; n = n >> 1; while (n) { n = n >> 1; r++; } return r; } 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 get_tail_compoment(char *dest, char *src) { int num = strlen(src); int i, index = 0; for (i = 0; i < num; i++) { if (src[i] == '/') index = i + 1; } strcpy(dest, src + index); } static void trim_tail(char *src, int n) { int num; num = strlen(src); src[num - n] = '\0'; } bool prefix_with(char *dst, char *prefix) { int length = strlen(prefix); if (length > strlen(dst)) return false; int i; for (i = 0; i < length; i++) { if (prefix[i] != dst[i]) return false; } return true; } void free_dev(struct list_head *head) { struct dev *dev, *n; list_for_each_entry_safe(dev, n, head, dev_list) { free(dev); } } 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; } void print_encode(char *in) { char *pos; for (pos = in; *pos; pos++) if (accepted_char(*pos)) putchar(*pos); else printf("%%%x", *pos); } bool part_of_disk(char *devname, char *partname) { char pattern[40]; int status; regmatch_t regmatche; regex_t reg; sprintf(pattern, "^%s.*[0-9]$", devname); if (regcomp(®, pattern, REG_EXTENDED) != 0) fprintf(stderr, "Error happen when compile reg\n"); status = regexec(®, partname, 1, ®matche, 0); regfree(®); if (status == REG_NOMATCH) return false; else return true; } int find_location(char *location, char *devname) { char path[300]; DIR *blockdir, *bcachedir, *partdir = NULL; struct dirent *ptr; blockdir = opendir("/sys/block"); if (blockdir == NULL) { fprintf(stderr, "Failed to open dir /sys/block/\n"); return 1; } sprintf(path, "/sys/block/%s/bcache", devname); bcachedir = opendir(path); if (bcachedir != NULL) { strcpy(location, devname); closedir(bcachedir); return 0; } while ((ptr = readdir(blockdir)) != NULL) { if (prefix_with(devname, ptr->d_name)) { sprintf(path, "/sys/block/%s/%s", ptr->d_name, devname); partdir = opendir(path); if (partdir != NULL) { sprintf(location, "%s/%s", ptr->d_name, devname); closedir(partdir); break; } } } closedir(blockdir); return 0; } int get_backdev_state(char *devname, char *state) { FILE *fd; int ret; char path[150]; char location[100] = ""; char buf[40]; trim_prefix(buf, devname, DEV_PREFIX_LEN); ret = find_location(location, buf); if (ret < 0) return ret; sprintf(path, "/sys/block/%s/bcache/state", location); 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); int fd_run; sprintf(path, "/sys/block/%s/bcache/running", location); fd_run = open(path, O_RDONLY); if (fd_run < 0) { fprintf(stderr, "Failed to open %s\n", path); return 1; } char running[20]; int num; num = read(fd_run, running, 10); if (num < 0) { fprintf(stderr, "Failed to fetch running infomation\n"); close(fd_run); return 1; } close(fd_run); if (running[0] == '1') strcat(state, "(running)"); 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 || dev->version == BCACHE_SB_VERSION_CDEV_WITH_FEATURES) return get_cachedev_state(dev->cset, state); else if (dev->version == BCACHE_SB_VERSION_BDEV || dev->version == BCACHE_SB_VERSION_BDEV_WITH_OFFSET || dev->version == BCACHE_SB_VERSION_BDEV_WITH_FEATURES) return get_backdev_state(dev->name, state); else return 1; } int get_dev_bname(char *devname, char *bname) { int ret; char path[150]; char location[100] = ""; char buf[40]; char link[100]; trim_prefix(buf, devname, DEV_PREFIX_LEN); ret = find_location(location, buf); if (ret < 0) return ret; sprintf(path, "/sys/block/%s/bcache/dev", location); ret = readlink(path, link, sizeof(link)); if (ret < 0) strcpy(bname, BCACHE_BNAME_NOT_EXIST); else { trim_tail(link, strlen(link) - ret); get_tail_compoment(bname, link); } 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 || dev->version == BCACHE_SB_VERSION_CDEV_WITH_FEATURES) strcpy(bname, BCACHE_NO_SUPPORT); else if (dev->version == BCACHE_SB_VERSION_BDEV || dev->version == BCACHE_SB_VERSION_BDEV_WITH_OFFSET || dev->version == BCACHE_SB_VERSION_BDEV_WITH_FEATURES) return get_dev_bname(dev->name, bname); return 0; } int get_backdev_attachpoint(char *devname, char *point) { int ret; char path[150]; char location[100] = ""; char buf[20]; char link[100]; char uuid[40]; trim_prefix(buf, devname, DEV_PREFIX_LEN); ret = find_location(location, buf); if (ret < 0) return ret; sprintf(path, "/sys/block/%s/bcache/cache", location); 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 || dev->version == BCACHE_SB_VERSION_CDEV_WITH_FEATURES) strcpy(point, BCACHE_NO_SUPPORT); else if (dev->version == BCACHE_SB_VERSION_BDEV || dev->version == BCACHE_SB_VERSION_BDEV_WITH_OFFSET || dev->version == BCACHE_SB_VERSION_BDEV_WITH_FEATURES) 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 || dev->version == BCACHE_SB_VERSION_CDEV_WITH_FEATURES) && 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; base->sb = sb; strcpy(base->name, devname); base->magic = "ok"; base->first_sector = SB_SECTOR; 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; ret = get_state(base, base->state); if (ret != 0) { fprintf(stderr, "Failed to get state for %s\n", devname); return ret; } ret = get_bname(base, base->bname); if (ret != 0) { fprintf(stderr, "Failed to get bname for %s\n", devname); return ret; } ret = get_point(base, base->attachuuid); if (ret != 0) { fprintf(stderr, "Failed to get attachuuid for %s\n", devname); return ret; } return 0; } int may_add_item(char *devname, struct list_head *head) { struct cache_sb_disk sb_disk; struct cache_sb sb; char dev[512]; struct dev *tmp; int ret = 0; if (strcmp(devname, ".") == 0 || strcmp(devname, "..") == 0) return 0; sprintf(dev, "/dev/%s", devname); int fd = open(dev, O_RDONLY); if (fd == -1) return 0; if (pread(fd, &sb_disk, sizeof(sb_disk), SB_START) != sizeof(sb_disk)) goto out; if (memcmp(sb_disk.magic, bcache_magic, 16)) goto out; to_cache_sb(&sb, &sb_disk); tmp = (struct dev *) malloc(DEVLEN); if (tmp == NULL) { fprintf(stderr, "Error: fail to allocate memory buffer\n"); ret = 1; goto out; } tmp->csum = le64_to_cpu(sb_disk.csum); ret = detail_base(dev, sb, tmp); if (ret != 0) { fprintf(stderr, "Failed to get information for %s\n", dev); free(tmp); goto out; } list_add_tail(&tmp->dev_list, head); out: close(fd); return ret; } int list_bdevs(struct list_head *head) { int ret; DIR *dir, *subdir; struct dirent *ptr, *subptr; char path[300]; 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; sprintf(path, "/sys/block/%s", ptr->d_name); subdir = opendir(path); if (subdir == NULL) { fprintf(stderr, "Unable to open dir /sys/block\n"); return 1; } while ((subptr = readdir(subdir)) != NULL) { if (strcmp(subptr->d_name, ".") == 0 || strcmp(subptr->d_name, "..") == 0) continue; if (part_of_disk(ptr->d_name, subptr->d_name)) { ret = may_add_item(subptr->d_name, head); if (ret != 0) return ret; } } ret = may_add_item(ptr->d_name, head); if (ret != 0) return ret; } closedir(dir); return 0; } int __detail_dev(char *devname, struct cache_sb_disk *sb_disk, struct bdev *bd, struct cdev *cd, int *type) { struct cache_sb sb; uint64_t expected_csum; to_cache_sb(&sb, sb_disk); 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_disk); if (le64_to_cpu(sb_disk->csum) != expected_csum) { fprintf(stderr, "Csum is not match with expected one\n"); goto Fail; } /* Check for incompat feature set */ if (sb.version >= BCACHE_SB_VERSION_BDEV_WITH_FEATURES || sb.version >= BCACHE_SB_VERSION_CDEV_WITH_FEATURES) { uint64_t features; features = sb.feature_compat & ~BCH_FEATURE_COMPAT_SUPP; if (features) { fprintf(stderr, "Unsupported compatible feature found\n"); goto Fail; } features = sb.feature_ro_compat & ~BCH_FEATURE_RO_COMPAT_SUPP; if (features) { fprintf(stderr, "Unsupported read-only compatible feature found\n"); goto Fail; } features = sb.feature_incompat & ~BCH_FEATURE_INCOMPAT_SUPP; if (features) { fprintf(stderr, "Unsupported incompatible feature found\n"); goto Fail; } } *type = sb.version; if (sb.version == BCACHE_SB_VERSION_BDEV || sb.version == BCACHE_SB_VERSION_BDEV_WITH_OFFSET || sb.version == BCACHE_SB_VERSION_BDEV_WITH_FEATURES) { detail_base(devname, sb, &bd->base); bd->base.csum = expected_csum; 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 || sb.version == BCACHE_SB_VERSION_CDEV_WITH_FEATURES) { detail_base(devname, sb, &cd->base); cd->base.csum = expected_csum; 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\n"); goto Fail; } return 0; Fail: return 1; } int detail_dev(char *devname, struct bdev *bd, struct cdev *cd, int *type) { char *buf = NULL; struct cache_sb_disk *sb_disk = NULL; int buf_size = 16<<10, ret = 1; int fd; buf = malloc(buf_size); if (buf == NULL) { fprintf(stderr, "Error: fail to allocate read buffer\n"); goto out; } fd = open(devname, O_RDONLY); if (fd < 0) { fprintf(stderr, "Error: Can't open dev %s\n", devname); goto out; } memset(buf, 0, buf_size); if (pread(fd, buf, buf_size, 0) != buf_size) { fprintf(stderr, "Couldn't read\n"); goto out_memory; } /* Try whether it is bcache super block */ sb_disk = (struct cache_sb_disk *)(buf + SB_START); if (!memcmp(sb_disk->magic, bcache_magic, 16)) { ret = __detail_dev(devname, sb_disk, bd, cd, type); goto out_memory; } fprintf(stderr, "Error: Bad magic, not bcache device\n"); out_memory: free(buf); out: return ret; } int register_dev(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 unregister_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 unregister this cache device\n"); close(fd); return 1; } close(fd); return 0; } int stop_backdev(char *devname) { char path[150]; char location[100] = ""; int fd, ret; char buf[20]; trim_prefix(buf, devname, DEV_PREFIX_LEN); ret = find_location(location, buf); if (ret < 0) return ret; sprintf(path, "/sys/block/%s/bcache/stop", location); 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; } //preserved for future use int unregister_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\n"); close(fd); return 1; } close(fd); return 0; } int attach_backdev(char *cset, char *devname) { int fd, ret; char buf[20]; char location[100] = ""; char path[150]; trim_prefix(buf, devname, DEV_PREFIX_LEN); ret = find_location(location, buf); if (ret < 0) return ret; sprintf(path, "/sys/block/%s/bcache/attach", location); 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_backdev(char *devname) { int fd, ret; char buf[20]; char path[150]; char location[100] = ""; trim_prefix(buf, devname, DEV_PREFIX_LEN); ret = find_location(location, buf); if (ret < 0) return ret; sprintf(path, "/sys/block/%s/bcache/detach", location); 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\n", devname); return 1; } close(fd); return 0; } int set_backdev_cachemode(char *devname, char *cachemode) { int fd, ret; char path[150]; char location[100] = ""; char buf[20]; trim_prefix(buf, devname, DEV_PREFIX_LEN); ret = find_location(location, buf); if (ret < 0) return ret; sprintf(path, "/sys/block/%s/bcache/cache_mode", location); 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) { fprintf(stderr, "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, ret; char path[150]; char location[100] = ""; ret = find_location(location, devname); if (ret < 0) return ret; sprintf(path, "/sys/block/%s/bcache/cache_mode", location); 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; } if (read(fd, mode, 100) < 0) { fprintf(stderr, "Failed to fetch device cache mode\n"); close(fd); return 1; } close(fd); return 0; } int set_label(char *devname, char *label) { int fd, ret; char path[150]; char location[100] = ""; char buf[20]; trim_prefix(buf, devname, DEV_PREFIX_LEN); ret = find_location(location, buf); if (ret < 0) return ret; sprintf(path, "/sys/block/%s/bcache/label", location); fd = open(path, O_WRONLY); if (fd < 0) { fprintf(stderr, "Please register this device first\n"); return 1; } if (dprintf(fd, "%s\n", label) < 0) { fprintf(stderr, "Failed to set label for device %s:%m\n", devname); close(fd); return 1; } close(fd); return 0; } struct cache_sb *to_cache_sb(struct cache_sb *sb, struct cache_sb_disk *sb_disk) { /* Convert common part */ sb->offset = le64_to_cpu(sb_disk->offset); sb->version = le64_to_cpu(sb_disk->version); memcpy(sb->magic, sb_disk->magic, 16); memcpy(sb->uuid, sb_disk->uuid, 16); memcpy(sb->set_uuid, sb_disk->set_uuid, 16); memcpy(sb->label, sb_disk->label, SB_LABEL_SIZE); sb->flags = le64_to_cpu(sb_disk->flags); sb->seq = le64_to_cpu(sb_disk->seq); sb->block_size = le16_to_cpu(sb_disk->block_size); sb->last_mount = le32_to_cpu(sb_disk->last_mount); sb->first_bucket = le16_to_cpu(sb_disk->first_bucket); sb->keys = le16_to_cpu(sb_disk->keys); /* For cache or backing devices*/ if (sb->version > BCACHE_SB_MAX_VERSION) { /* Unsupported version */ fprintf(stderr, "Unsupported super block version: %lld\n", sb->version); } else if (SB_IS_BDEV(sb)) { /* Backing device */ sb->data_offset = le64_to_cpu(sb_disk->data_offset); } else { int i; /* Cache device */ sb->nbuckets = le64_to_cpu(sb_disk->nbuckets); sb->nr_in_set = le16_to_cpu(sb_disk->nr_in_set); sb->nr_this_dev = le16_to_cpu(sb_disk->nr_this_dev); sb->bucket_size = le32_to_cpu(sb_disk->bucket_size); for (i = 0; i < SB_JOURNAL_BUCKETS; i++) sb->d[i]= le64_to_cpu(sb_disk->d[i]); } if (sb->version >= BCACHE_SB_VERSION_CDEV_WITH_FEATURES) { sb->feature_compat = le64_to_cpu(sb_disk->feature_compat); sb->feature_incompat = le64_to_cpu(sb_disk->feature_incompat); sb->feature_ro_compat = le64_to_cpu(sb_disk->feature_ro_compat); } if (sb->version >= BCACHE_SB_VERSION_CDEV_WITH_FEATURES) { if (bch_has_feature_large_bucket(sb)) sb->bucket_size = 1 << le16_to_cpu(sb_disk->bucket_size); else if (bch_has_feature_obso_large_bucket(sb)) sb->bucket_size += le16_to_cpu(sb_disk->obso_bucket_size_hi) << 16; } return sb; } struct cache_sb_disk *to_cache_sb_disk(struct cache_sb_disk *sb_disk, struct cache_sb *sb) { /* Convert common part */ sb_disk->offset = cpu_to_le64(sb->offset); sb_disk->version = cpu_to_le64(sb->version); memcpy(sb_disk->magic, sb->magic, 16); memcpy(sb_disk->uuid, sb->uuid, 16); memcpy(sb_disk->set_uuid, sb->set_uuid, 16); memcpy(sb_disk->label, sb->label, SB_LABEL_SIZE); sb_disk->flags = cpu_to_le64(sb->flags); sb_disk->seq = cpu_to_le64(sb->seq); sb_disk->block_size = cpu_to_le16(sb->block_size); sb_disk->last_mount = cpu_to_le32(sb->last_mount); sb_disk->first_bucket = cpu_to_le16(sb->first_bucket); sb_disk->keys = cpu_to_le16(sb->keys); /* For cache and backing devices */ if (sb->version > BCACHE_SB_MAX_VERSION) { /* Unsupported version */ fprintf(stderr, "Unsupported super block version: %lld\n", sb->version); } else if (SB_IS_BDEV(sb)) { /* Backing device */ sb_disk->data_offset = cpu_to_le64(sb->data_offset); } else { int i; /* Cache device */ sb_disk->nbuckets = cpu_to_le64(sb->nbuckets); sb_disk->nr_in_set = cpu_to_le16(sb->nr_in_set); sb_disk->nr_this_dev = cpu_to_le16(sb->nr_this_dev); sb_disk->bucket_size = cpu_to_le32(sb->bucket_size); for (i = 0; i < SB_JOURNAL_BUCKETS; i++) sb_disk->d[i] = cpu_to_le64(sb->d[i]); } if (sb->version >= BCACHE_SB_VERSION_CDEV_WITH_FEATURES) { sb_disk->feature_compat = cpu_to_le64(sb->feature_compat); sb_disk->feature_incompat = cpu_to_le64(sb->feature_incompat); sb_disk->feature_ro_compat = cpu_to_le64(sb->feature_ro_compat); } if (sb->version >= BCACHE_SB_VERSION_CDEV_WITH_FEATURES && bch_has_feature_large_bucket(sb)) sb_disk->bucket_size = cpu_to_le16(log2_u32(sb->bucket_size)); return sb_disk; } void set_bucket_size(struct cache_sb *sb, unsigned int bucket_size) { if (bucket_size > USHRT_MAX) { sb->version = BCACHE_SB_VERSION_CDEV_WITH_FEATURES; bch_set_feature_large_bucket(sb); } sb->bucket_size = bucket_size; }