+
+ return ret;
+}
+
+/*
+ * Given a path to a block device, open the filesystem it belongs to; also
+ * return the device's idx:
+ */
+struct bchfs_handle bchu_fs_open_by_dev(const char *path, int *idx)
+{
+ char buf[1024], *uuid_str;
+
+ struct stat stat = xstat(path);
+
+ if (!S_ISBLK(stat.st_mode))
+ die("%s is not a block device", path);
+
+ char *sysfs = mprintf("/sys/dev/block/%u:%u/bcachefs",
+ major(stat.st_dev),
+ minor(stat.st_dev));
+ ssize_t len = readlink(sysfs, buf, sizeof(buf));
+ free(sysfs);
+
+ if (len > 0) {
+ char *p = strrchr(buf, '/');
+ if (!p || sscanf(p + 1, "dev-%u", idx) != 1)
+ die("error parsing sysfs");
+
+ *p = '\0';
+ p = strrchr(buf, '/');
+ uuid_str = p + 1;
+ } else {
+ struct bch_opts opts = bch2_opts_empty();
+
+ opt_set(opts, noexcl, true);
+ opt_set(opts, nochanges, true);
+
+ struct bch_sb_handle sb;
+ int ret = bch2_read_super(path, &opts, &sb);
+ if (ret)
+ die("Error opening %s: %s", path, strerror(-ret));
+
+ *idx = sb.sb->dev_idx;
+ uuid_str = buf;
+ uuid_unparse(sb.sb->user_uuid.b, uuid_str);
+
+ bch2_free_super(&sb);
+ }
+
+ return bcache_fs_open(uuid_str);
+}
+
+int bchu_dev_path_to_idx(struct bchfs_handle fs, const char *dev_path)
+{
+ int idx;
+ struct bchfs_handle fs2 = bchu_fs_open_by_dev(dev_path, &idx);
+
+ if (memcmp(&fs.uuid, &fs2.uuid, sizeof(fs.uuid)))
+ idx = -1;
+ bcache_fs_close(fs2);
+ return idx;
+}
+
+int bchu_data(struct bchfs_handle fs, struct bch_ioctl_data cmd)
+{
+ int progress_fd = xioctl(fs.ioctl_fd, BCH_IOCTL_DATA, &cmd);
+
+ while (1) {
+ struct bch_ioctl_data_event e;
+
+ if (read(progress_fd, &e, sizeof(e)) != sizeof(e))
+ die("error reading from progress fd %m");
+
+ if (e.type)
+ continue;
+
+ if (e.p.data_type == U8_MAX)
+ break;
+
+ printf("\33[2K\r");
+
+ printf("%llu%% complete: current position %s",
+ e.p.sectors_total
+ ? e.p.sectors_done * 100 / e.p.sectors_total
+ : 0,
+ bch2_data_types[e.p.data_type]);
+
+ switch (e.p.data_type) {
+ case BCH_DATA_btree:
+ case BCH_DATA_user:
+ printf(" %s:%llu:%llu",
+ bch2_btree_ids[e.p.btree_id],
+ e.p.pos.inode,
+ e.p.pos.offset);
+ }
+
+ fflush(stdout);
+ sleep(1);
+ }
+ printf("\nDone\n");
+
+ close(progress_fd);
+ return 0;
+}
+
+/* option parsing */
+
+void bch2_opt_strs_free(struct bch_opt_strs *opts)
+{
+ unsigned i;
+
+ for (i = 0; i < bch2_opts_nr; i++) {
+ free(opts->by_id[i]);
+ opts->by_id[i] = NULL;
+ }
+}
+
+struct bch_opt_strs bch2_cmdline_opts_get(int *argc, char *argv[],
+ unsigned opt_types)
+{
+ struct bch_opt_strs opts;
+ unsigned i = 1;
+
+ memset(&opts, 0, sizeof(opts));
+
+ while (i < *argc) {
+ char *optstr = strcmp_prefix(argv[i], "--");
+ char *valstr = NULL, *p;
+ int optid, nr_args = 1;
+
+ if (!optstr) {
+ i++;
+ continue;
+ }
+
+ optstr = strdup(optstr);
+
+ p = optstr;
+ while (isalpha(*p) || *p == '_')
+ p++;
+
+ if (*p == '=') {
+ *p = '\0';
+ valstr = p + 1;
+ }
+
+ optid = bch2_opt_lookup(optstr);
+ if (optid < 0 ||
+ !(bch2_opt_table[optid].mode & opt_types)) {
+ i++;
+ goto next;
+ }
+
+ if (!valstr &&
+ bch2_opt_table[optid].type != BCH_OPT_BOOL) {
+ nr_args = 2;
+ valstr = argv[i + 1];
+ }
+
+ if (!valstr)
+ valstr = "1";
+
+ opts.by_id[optid] = strdup(valstr);
+
+ *argc -= nr_args;
+ memmove(&argv[i],
+ &argv[i + nr_args],
+ sizeof(char *) * (*argc - i));
+ argv[*argc] = NULL;
+next:
+ free(optstr);
+ }
+
+ return opts;
+}
+
+struct bch_opts bch2_parse_opts(struct bch_opt_strs strs)
+{
+ struct bch_opts opts = bch2_opts_empty();
+ unsigned i;
+ int ret;
+ u64 v;
+
+ for (i = 0; i < bch2_opts_nr; i++) {
+ if (!strs.by_id[i] ||
+ bch2_opt_table[i].type == BCH_OPT_FN)
+ continue;
+
+ ret = bch2_opt_parse(NULL, &bch2_opt_table[i],
+ strs.by_id[i], &v);
+ if (ret < 0)
+ die("Invalid %s: %s",
+ bch2_opt_table[i].attr.name,
+ strerror(-ret));
+
+ bch2_opt_set_by_id(&opts, i, v);
+ }
+
+ return opts;
+}
+
+#define newline(c) \
+ do { \
+ printf("\n"); \
+ c = 0; \
+ } while(0)
+void bch2_opts_usage(unsigned opt_types)
+{
+ const struct bch_option *opt;
+ unsigned i, c = 0, helpcol = 30;
+
+
+
+ for (opt = bch2_opt_table;
+ opt < bch2_opt_table + bch2_opts_nr;
+ opt++) {
+ if (!(opt->mode & opt_types))
+ continue;
+
+ c += printf(" --%s", opt->attr.name);
+
+ switch (opt->type) {
+ case BCH_OPT_BOOL:
+ break;
+ case BCH_OPT_STR:
+ c += printf("=(");
+ for (i = 0; opt->choices[i]; i++) {
+ if (i)
+ c += printf("|");
+ c += printf("%s", opt->choices[i]);
+ }
+ c += printf(")");
+ break;
+ default:
+ c += printf("=%s", opt->hint);
+ break;
+ }
+
+ if (opt->help) {
+ const char *l = opt->help;
+
+ if (c >= helpcol)
+ newline(c);
+
+ while (1) {
+ const char *n = strchrnul(l, '\n');
+
+ while (c < helpcol) {
+ putchar(' ');
+ c++;
+ }
+ printf("%.*s", (int) (n - l), l);
+ newline(c);
+
+ if (!*n)
+ break;
+ l = n + 1;
+ }
+ } else {
+ newline(c);
+ }
+ }
+}
+
+dev_names bchu_fs_get_devices(struct bchfs_handle fs)
+{
+ DIR *dir = fdopendir(fs.sysfs_fd);
+ struct dirent *d;
+ dev_names devs;
+
+ darray_init(devs);
+
+ while ((errno = 0), (d = readdir(dir))) {
+ struct dev_name n = { 0, NULL, NULL };
+
+ if (sscanf(d->d_name, "dev-%u", &n.idx) != 1)
+ continue;
+
+ char *block_attr = mprintf("dev-%u/block", n.idx);
+
+ char sysfs_block_buf[4096];
+ ssize_t r = readlinkat(fs.sysfs_fd, block_attr,
+ sysfs_block_buf, sizeof(sysfs_block_buf));
+ if (r > 0) {
+ sysfs_block_buf[r] = '\0';
+ n.dev = strdup(basename(sysfs_block_buf));
+ }
+
+ free(block_attr);
+
+ char *label_attr = mprintf("dev-%u/label", n.idx);
+ n.label = read_file_str(fs.sysfs_fd, label_attr);
+ free(label_attr);
+
+ darray_append(devs, n);
+ }
+
+ closedir(dir);
+
+ return devs;