| // SPDX-License-Identifier: MIT or GPL-2.0-only |
| |
| #include "config.h" |
| #include "ublksrv_tgt.h" |
| |
| /* per-task variable */ |
| static pthread_mutex_t jbuf_lock; |
| static int jbuf_size = 0; |
| static int queues_stored = 0; |
| static char *jbuf = NULL; |
| |
| struct ublksrv_queue_info { |
| const struct ublksrv_dev *dev; |
| int qid; |
| pthread_t thread; |
| }; |
| |
| |
| /********************cmd handling************************/ |
| static char *full_cmd; |
| static struct ublksrv_tgt_type *tgt_list[UBLKSRV_TGT_TYPE_MAX] = {}; |
| |
| int ublk_json_write_dev_info(struct ublksrv_dev *dev, char **jbuf, int *len) |
| { |
| int ret = 0; |
| |
| do { |
| ret = ublksrv_json_write_dev_info(ublksrv_get_ctrl_dev(dev), |
| *jbuf, *len); |
| if (ret < 0) |
| *jbuf = ublksrv_tgt_realloc_json_buf(dev, len); |
| } while (ret < 0); |
| |
| return ret; |
| } |
| |
| int ublk_json_write_params(struct ublksrv_dev *dev, char **jbuf, int *len, |
| const struct ublk_params *p) |
| { |
| int ret = 0; |
| |
| do { |
| ret = ublksrv_json_write_params(p, *jbuf, *len); |
| if (ret < 0) |
| *jbuf = ublksrv_tgt_realloc_json_buf(dev, len); |
| } while (ret < 0); |
| |
| return ret; |
| } |
| |
| int ublk_json_write_target_base(struct ublksrv_dev *dev, char **jbuf, int *len, |
| const struct ublksrv_tgt_base_json *tgt) |
| { |
| int ret = 0; |
| |
| do { |
| ret = ublksrv_json_write_target_base_info(*jbuf, *len, tgt); |
| if (ret < 0) |
| *jbuf = ublksrv_tgt_realloc_json_buf(dev, len); |
| } while (ret < 0); |
| |
| return ret; |
| |
| } |
| |
| int ublk_json_write_tgt_str(struct ublksrv_dev *dev, char **jbuf, |
| int *len, const char *name, const char *val) |
| { |
| int ret = 0; |
| |
| do { |
| if (val) |
| ret = ublksrv_json_write_target_str_info(*jbuf, |
| *len, name, val); |
| |
| if (ret < 0) |
| *jbuf = ublksrv_tgt_realloc_json_buf(dev, len); |
| } while (ret < 0); |
| |
| return ret; |
| } |
| |
| int ublk_json_write_tgt_ulong(struct ublksrv_dev *dev, char **jbuf, |
| int *len, const char *name, unsigned long val) |
| { |
| int ret = 0; |
| |
| do { |
| ret = ublksrv_json_write_target_ulong_info(*jbuf, |
| *len, name, val); |
| if (ret < 0) |
| *jbuf = ublksrv_tgt_realloc_json_buf(dev, len); |
| } while (ret < 0); |
| |
| return ret; |
| } |
| |
| int ublk_json_write_tgt_long(struct ublksrv_dev *dev, char **jbuf, |
| int *len, const char *name, long val) |
| { |
| int ret = 0; |
| |
| do { |
| ret = ublksrv_json_write_target_long_info(*jbuf, |
| *len, name, val); |
| if (ret < 0) |
| *jbuf = ublksrv_tgt_realloc_json_buf(dev, len); |
| } while (ret < 0); |
| |
| return ret; |
| } |
| |
| static const struct ublksrv_tgt_type *ublksrv_find_tgt_type(const char *name) |
| { |
| int i; |
| |
| for (i = 0; i < UBLKSRV_TGT_TYPE_MAX; i++) { |
| const struct ublksrv_tgt_type *type = tgt_list[i]; |
| |
| if (type == NULL) |
| continue; |
| |
| if (!strcmp(type->name, name)) |
| return type; |
| } |
| |
| return NULL; |
| } |
| |
| static void ublksrv_for_each_tgt_type(void (*handle_tgt_type)(unsigned idx, |
| const struct ublksrv_tgt_type *type, void *data), |
| void *data) |
| { |
| int i; |
| |
| for (i = 0; i < UBLKSRV_TGT_TYPE_MAX; i++) { |
| const struct ublksrv_tgt_type *type = tgt_list[i]; |
| |
| if (!type) |
| continue; |
| handle_tgt_type(i, type, data); |
| } |
| } |
| |
| int ublksrv_register_tgt_type(struct ublksrv_tgt_type *type) |
| { |
| if (type->type < UBLKSRV_TGT_TYPE_MAX && !tgt_list[type->type]) { |
| tgt_list[type->type] = type; |
| return 0; |
| } |
| return -1; |
| } |
| |
| void ublksrv_unregister_tgt_type(struct ublksrv_tgt_type *type) |
| { |
| if (type->type < UBLKSRV_TGT_TYPE_MAX && tgt_list[type->type]) { |
| tgt_list[type->type] = NULL; |
| } |
| } |
| |
| |
| static char *mprintf(const char *fmt, ...) |
| { |
| va_list args; |
| char *str; |
| int ret; |
| |
| va_start(args, fmt); |
| ret = vasprintf(&str, fmt, args); |
| va_end(args); |
| |
| if (ret < 0) { |
| return NULL; |
| } |
| |
| return str; |
| } |
| |
| static char *pop_cmd(int *argc, char *argv[]) |
| { |
| char *cmd = argv[1]; |
| if (*argc < 2) { |
| return NULL; |
| } |
| |
| memmove(&argv[1], &argv[2], *argc * sizeof(argv[0])); |
| (*argc)--; |
| |
| full_cmd = mprintf("%s %s", full_cmd, cmd); |
| return cmd; |
| } |
| |
| static int start_daemon(void (*child_fn)(void *), void *data) |
| { |
| char path[PATH_MAX]; |
| int fd; |
| char *res; |
| |
| if (setsid() == -1) |
| return -1; |
| |
| res = getcwd(path, PATH_MAX); |
| if (!res) |
| ublk_err("%s: %d getcwd failed %m\n", __func__, __LINE__); |
| |
| switch (fork()) { |
| case -1: return -1; |
| case 0: break; |
| default: _exit(EXIT_SUCCESS); |
| } |
| |
| if (chdir(path) != 0) |
| ublk_err("%s: %d chdir failed %m\n", __func__, __LINE__); |
| |
| close(STDIN_FILENO); |
| fd = open("/dev/null", O_RDWR); |
| if (fd != STDIN_FILENO) |
| return -1; |
| if (dup2(fd, STDOUT_FILENO) != STDOUT_FILENO) |
| return -1; |
| if (dup2(fd, STDERR_FILENO) != STDERR_FILENO) |
| return -1; |
| |
| child_fn(data); |
| return 0; |
| } |
| |
| char *__ublksrv_tgt_return_json_buf(struct ublksrv_dev *dev, int *size) |
| { |
| if (jbuf == NULL) { |
| jbuf_size = 1024; |
| jbuf = (char *)realloc((void *)jbuf, jbuf_size); |
| } |
| *size = jbuf_size; |
| |
| return jbuf; |
| } |
| |
| char *ublksrv_tgt_return_json_buf(struct ublksrv_dev *dev, int *size) |
| { |
| char *buf; |
| |
| pthread_mutex_lock(&jbuf_lock); |
| buf = __ublksrv_tgt_return_json_buf(dev, size); |
| pthread_mutex_unlock(&jbuf_lock); |
| |
| return buf; |
| } |
| |
| static char *__ublksrv_tgt_realloc_json_buf(const struct ublksrv_dev *dev, int *size) |
| { |
| if (jbuf == NULL) |
| jbuf_size = 1024; |
| else |
| jbuf_size += 1024; |
| |
| jbuf = (char *)realloc((void *)jbuf, jbuf_size); |
| *size = jbuf_size; |
| |
| return jbuf; |
| } |
| |
| char *ublksrv_tgt_realloc_json_buf(struct ublksrv_dev *dev, int *size) |
| { |
| char *buf; |
| |
| pthread_mutex_lock(&jbuf_lock); |
| buf = __ublksrv_tgt_realloc_json_buf(dev, size); |
| pthread_mutex_unlock(&jbuf_lock); |
| |
| return buf; |
| } |
| |
| static int ublksrv_tgt_store_dev_data(const struct ublksrv_dev *dev, |
| const char *buf) |
| { |
| int ret; |
| int len = ublksrv_json_get_length(buf); |
| int fd = ublksrv_get_pidfile_fd(dev); |
| |
| if (fd < 0) { |
| ublk_err( "fail to get fd of pid file, ret %d\n", |
| fd); |
| return fd; |
| } |
| |
| ret = pwrite(fd, buf, len, JSON_OFFSET); |
| if (ret <= 0) |
| ublk_err( "fail to write json data to pid file, ret %d\n", |
| ret); |
| |
| return ret; |
| } |
| |
| static char *ublksrv_tgt_get_dev_data(struct ublksrv_ctrl_dev *cdev) |
| { |
| const struct ublksrv_ctrl_dev_info *info = |
| ublksrv_ctrl_get_dev_info(cdev); |
| int dev_id = info->dev_id; |
| struct stat st; |
| char pid_file[256]; |
| char *buf; |
| int size, fd, ret; |
| const char *run_dir = ublksrv_ctrl_get_run_dir(cdev); |
| |
| if (!run_dir) |
| return 0; |
| |
| snprintf(pid_file, 256, "%s/%d.pid", run_dir, dev_id); |
| fd = open(pid_file, O_RDONLY); |
| |
| if (fd <= 0) |
| return NULL; |
| |
| if (fstat(fd, &st) < 0) |
| return NULL; |
| |
| if (st.st_size <= JSON_OFFSET) |
| return NULL; |
| |
| size = st.st_size - JSON_OFFSET; |
| buf = (char *)malloc(size); |
| ret = pread(fd, buf, size, JSON_OFFSET); |
| if (ret <= 0) |
| fprintf(stderr, "fail to read json from %s ret %d\n", |
| pid_file, ret); |
| close(fd); |
| |
| return buf; |
| } |
| |
| static void *ublksrv_io_handler_fn(void *data) |
| { |
| struct ublksrv_queue_info *info = (struct ublksrv_queue_info *)data; |
| const struct ublksrv_dev *dev = info->dev; |
| const struct ublksrv_ctrl_dev *cdev = ublksrv_get_ctrl_dev(dev); |
| const struct ublksrv_ctrl_dev_info *dinfo = |
| ublksrv_ctrl_get_dev_info(cdev); |
| unsigned dev_id = dinfo->dev_id; |
| unsigned short q_id = info->qid; |
| const struct ublksrv_queue *q; |
| int ret; |
| int buf_size; |
| char *buf; |
| const char *jbuf; |
| |
| pthread_mutex_lock(&jbuf_lock); |
| |
| if (!ublksrv_is_recovering(cdev)) { |
| do { |
| buf = __ublksrv_tgt_realloc_json_buf(dev, &buf_size); |
| ret = ublksrv_json_write_queue_info(cdev, buf, buf_size, |
| q_id, ublksrv_gettid()); |
| } while (ret < 0); |
| jbuf = buf; |
| } else { |
| jbuf = ublksrv_ctrl_get_recovery_jbuf(cdev); |
| } |
| queues_stored++; |
| |
| /* |
| * A bit ugly to store json buffer to pid file here, but no easy |
| * way to do it in control task side, so far, so good |
| */ |
| if (queues_stored == dinfo->nr_hw_queues) |
| ublksrv_tgt_store_dev_data(dev, jbuf); |
| pthread_mutex_unlock(&jbuf_lock); |
| |
| q = ublksrv_queue_init(dev, q_id, NULL); |
| if (!q) { |
| ublk_err("ublk dev %d queue %d init queue failed", |
| dev_id, q_id); |
| return NULL; |
| } |
| |
| ublk_log("tid %d: ublk dev %d queue %d started", ublksrv_gettid(), |
| dev_id, q->q_id); |
| do { |
| if (ublksrv_process_io(q) < 0) |
| break; |
| } while (1); |
| |
| ublk_log("ublk dev %d queue %d exited", dev_id, q->q_id); |
| ublksrv_queue_deinit(q); |
| return NULL; |
| } |
| |
| static void sig_handler(int sig) |
| { |
| if (sig == SIGTERM) |
| ublk_log("got TERM signal"); |
| } |
| |
| static void setup_pthread_sigmask() |
| { |
| sigset_t signal_mask; |
| |
| if (signal(SIGTERM, sig_handler) == SIG_ERR) |
| return; |
| |
| /* make sure SIGTERM won't be blocked */ |
| sigemptyset(&signal_mask); |
| sigaddset(&signal_mask, SIGINT); |
| sigaddset(&signal_mask, SIGTERM); |
| pthread_sigmask(SIG_BLOCK, &signal_mask, NULL); |
| } |
| |
| /* |
| * Now STOP DEV ctrl command has been sent to /dev/ublk-control, |
| * and wait until all pending fetch commands are canceled |
| */ |
| static void ublksrv_drain_fetch_commands(const struct ublksrv_dev *dev, |
| struct ublksrv_queue_info *info) |
| { |
| const struct ublksrv_ctrl_dev_info *dinfo = |
| ublksrv_ctrl_get_dev_info(ublksrv_get_ctrl_dev(dev)); |
| unsigned nr_queues = dinfo->nr_hw_queues; |
| int i; |
| void *ret; |
| |
| for (i = 0; i < nr_queues; i++) |
| pthread_join(info[i].thread, &ret); |
| } |
| |
| |
| static void ublksrv_io_handler(void *data) |
| { |
| const struct ublksrv_ctrl_dev *ctrl_dev = (struct ublksrv_ctrl_dev *)data; |
| const struct ublksrv_ctrl_dev_info *dinfo = |
| ublksrv_ctrl_get_dev_info(ctrl_dev); |
| int dev_id = dinfo->dev_id; |
| int i; |
| char buf[32]; |
| const struct ublksrv_dev *dev; |
| struct ublksrv_queue_info *info_array; |
| |
| snprintf(buf, 32, "%s-%d", "ublksrvd", dev_id); |
| openlog(buf, LOG_PID, LOG_USER); |
| |
| ublk_log("start ublksrv io daemon %s\n", buf); |
| |
| pthread_mutex_init(&jbuf_lock, NULL); |
| |
| dev = ublksrv_dev_init(ctrl_dev); |
| if (!dev) { |
| ublk_err( "dev-%d start ubsrv failed", dev_id); |
| goto out; |
| } |
| |
| setup_pthread_sigmask(); |
| |
| if (!(dinfo->flags & UBLK_F_UNPRIVILEGED_DEV)) |
| ublksrv_apply_oom_protection(); |
| |
| info_array = (struct ublksrv_queue_info *)calloc(sizeof( |
| struct ublksrv_queue_info), |
| dinfo->nr_hw_queues); |
| |
| for (i = 0; i < dinfo->nr_hw_queues; i++) { |
| info_array[i].dev = dev; |
| info_array[i].qid = i; |
| pthread_create(&info_array[i].thread, NULL, |
| ublksrv_io_handler_fn, |
| &info_array[i]); |
| } |
| |
| /* wait until we are terminated */ |
| ublksrv_drain_fetch_commands(dev, info_array); |
| free(info_array); |
| free(jbuf); |
| |
| ublksrv_dev_deinit(dev); |
| out: |
| ublk_log("end ublksrv io daemon"); |
| closelog(); |
| } |
| |
| /* Not called from ublksrv daemon */ |
| static int ublksrv_start_io_daemon(const struct ublksrv_ctrl_dev *dev) |
| { |
| start_daemon(ublksrv_io_handler, (void *)dev); |
| return 0; |
| } |
| |
| static int ublksrv_check_dev_data(const char *buf, int size) |
| { |
| struct ublk_params p; |
| |
| if (size < JSON_OFFSET) |
| return -EINVAL; |
| |
| return ublksrv_json_read_params(&p, &buf[JSON_OFFSET]); |
| } |
| |
| static int ublksrv_get_io_daemon_pid(const struct ublksrv_ctrl_dev *ctrl_dev, |
| bool check_data) |
| { |
| const char *run_dir = ublksrv_ctrl_get_run_dir(ctrl_dev); |
| const struct ublksrv_ctrl_dev_info *info = |
| ublksrv_ctrl_get_dev_info(ctrl_dev); |
| int ret = -1, pid_fd; |
| char path[256]; |
| char *buf = NULL; |
| int size = JSON_OFFSET; |
| int daemon_pid; |
| struct stat st; |
| |
| if (!run_dir) |
| return -EINVAL; |
| |
| snprintf(path, 256, "%s/%d.pid", run_dir, info->dev_id); |
| |
| pid_fd = open(path, O_RDONLY); |
| if (pid_fd < 0) |
| goto out; |
| |
| if (fstat(pid_fd, &st) < 0) |
| goto out; |
| |
| if (check_data) |
| size = st.st_size; |
| else |
| size = JSON_OFFSET; |
| |
| buf = (char *)malloc(size); |
| if (read(pid_fd, buf, size) <= 0) |
| goto out; |
| |
| daemon_pid = strtol(buf, NULL, 10); |
| if (daemon_pid < 0) |
| goto out; |
| |
| ret = kill(daemon_pid, 0); |
| if (ret) |
| goto out; |
| |
| if (check_data) { |
| ret = ublksrv_check_dev_data(buf, size); |
| if (ret) |
| goto out; |
| } |
| ret = daemon_pid; |
| out: |
| if (pid_fd > 0) |
| close(pid_fd); |
| free(buf); |
| return ret; |
| } |
| |
| /* Not called from ublksrv daemon */ |
| static int ublksrv_stop_io_daemon(const struct ublksrv_ctrl_dev *ctrl_dev) |
| { |
| int daemon_pid, cnt = 0; |
| |
| /* wait until daemon is exited, or timeout after 3 seconds */ |
| do { |
| daemon_pid = ublksrv_get_io_daemon_pid(ctrl_dev, false); |
| if (daemon_pid > 0) { |
| usleep(100000); |
| cnt++; |
| } |
| } while (daemon_pid > 0 && cnt < 30); |
| |
| if (daemon_pid > 0) |
| return -1; |
| |
| return 0; |
| } |
| |
| /* Wait until ublk device is setup by udev */ |
| static void ublksrv_check_dev(const struct ublksrv_ctrl_dev_info *info) |
| { |
| unsigned int max_time = 1000000, wait = 0; |
| char buf[64]; |
| |
| snprintf(buf, 64, "%s%d", "/dev/ublkc", info->dev_id); |
| |
| while (wait < max_time) { |
| int fd = open(buf, O_RDWR); |
| |
| if (fd > 0) { |
| close(fd); |
| break; |
| } |
| |
| usleep(100000); |
| wait += 100000; |
| } |
| } |
| |
| static int ublksrv_start_daemon(struct ublksrv_ctrl_dev *ctrl_dev) |
| { |
| const struct ublksrv_ctrl_dev_info *dinfo = |
| ublksrv_ctrl_get_dev_info(ctrl_dev); |
| int cnt = 0, daemon_pid, ret; |
| |
| ublksrv_check_dev(dinfo); |
| |
| ret = ublksrv_ctrl_get_affinity(ctrl_dev); |
| if (ret < 0) { |
| fprintf(stderr, "dev %d get affinity failed %d\n", |
| dinfo->dev_id, ret); |
| return -1; |
| } |
| |
| switch (fork()) { |
| case -1: |
| return -1; |
| case 0: |
| ublksrv_start_io_daemon(ctrl_dev); |
| break; |
| } |
| |
| /* wait until daemon is started, or timeout after 3 seconds */ |
| do { |
| daemon_pid = ublksrv_get_io_daemon_pid(ctrl_dev, true); |
| if (daemon_pid < 0) { |
| usleep(100000); |
| cnt++; |
| } |
| } while (daemon_pid < 0 && cnt < 30); |
| |
| return daemon_pid; |
| } |
| |
| //todo: resolve stack usage warning for mkpath/__mkpath |
| #pragma GCC diagnostic push |
| #pragma GCC diagnostic ignored "-Wstack-usage=" |
| static int __mkpath(char *dir, mode_t mode) |
| { |
| struct stat sb; |
| int ret; |
| mode_t mask; |
| |
| if (!dir) |
| return -EINVAL; |
| |
| if (!stat(dir, &sb)) |
| return 0; |
| |
| __mkpath(dirname(strdupa(dir)), mode); |
| |
| mask = umask(0); |
| ret = mkdir(dir, mode); |
| umask(mask); |
| |
| return ret; |
| } |
| |
| static int mkpath(const char *dir) |
| { |
| return __mkpath(strdupa(dir), S_IRWXU | S_IRWXG | S_IRWXO); |
| } |
| #pragma GCC diagnostic pop |
| |
| static void ublksrv_tgt_set_params(struct ublksrv_ctrl_dev *cdev, |
| const char *jbuf) |
| { |
| const struct ublksrv_ctrl_dev_info *info = |
| ublksrv_ctrl_get_dev_info(cdev); |
| int dev_id = info->dev_id; |
| struct ublk_params p; |
| int ret; |
| |
| ret = ublksrv_json_read_params(&p, jbuf); |
| if (ret >= 0) { |
| ret = ublksrv_ctrl_set_params(cdev, &p); |
| if (ret) |
| fprintf(stderr, "set param for dev %d failed %d\n", |
| dev_id, ret); |
| } else { |
| fprintf(stderr, "params not found for dev %d failed %d\n", |
| dev_id, ret); |
| } |
| } |
| |
| static int cmd_dev_add(int argc, char *argv[]) |
| { |
| static const struct option longopts[] = { |
| { "type", 1, NULL, 't' }, |
| { "number", 1, NULL, 'n' }, |
| { "queues", 1, NULL, 'q' }, |
| { "depth", 1, NULL, 'd' }, |
| { "zero_copy", 1, NULL, 'z' }, |
| { "uring_comp", 1, NULL, 'u' }, |
| { "need_get_data", 1, NULL, 'g' }, |
| { "user_recovery", 1, NULL, 'r'}, |
| { "user_recovery_reissue", 1, NULL, 'i'}, |
| { "debug_mask", 1, NULL, 0}, |
| { "unprivileged", 0, NULL, 0}, |
| { "usercopy", 0, NULL, 0}, |
| { NULL } |
| }; |
| struct ublksrv_dev_data data = {0}; |
| struct ublksrv_ctrl_dev *dev; |
| const struct ublksrv_tgt_type *tgt_type; |
| int opt, ret; |
| int uring_comp = 0; |
| int need_get_data = 0; |
| int user_recovery = 0; |
| int user_recovery_reissue = 0; |
| int unprivileged = 0; |
| const char *dump_buf; |
| int option_index = 0; |
| unsigned int debug_mask = 0; |
| |
| data.queue_depth = DEF_QD; |
| data.nr_hw_queues = DEF_NR_HW_QUEUES; |
| data.dev_id = -1; |
| data.run_dir = UBLKSRV_PID_DIR; |
| |
| mkpath(data.run_dir); |
| |
| while ((opt = getopt_long(argc, argv, "-:t:n:d:q:u:g:r:i:z", |
| longopts, &option_index)) != -1) { |
| switch (opt) { |
| case 'n': |
| data.dev_id = strtol(optarg, NULL, 10); |
| break; |
| case 't': |
| data.tgt_type = optarg; |
| break; |
| case 'z': |
| data.flags |= UBLK_F_SUPPORT_ZERO_COPY; |
| break; |
| case 'q': |
| data.nr_hw_queues = strtol(optarg, NULL, 10); |
| break; |
| case 'd': |
| data.queue_depth = strtol(optarg, NULL, 10); |
| break; |
| case 'u': |
| uring_comp = strtol(optarg, NULL, 10); |
| break; |
| case 'g': |
| need_get_data = strtol(optarg, NULL, 10); |
| break; |
| case 'r': |
| user_recovery = strtol(optarg, NULL, 10); |
| break; |
| case 'i': |
| user_recovery_reissue = strtol(optarg, NULL, 10); |
| break; |
| case 0: |
| if (!strcmp(longopts[option_index].name, "debug_mask")) |
| debug_mask = strtol(optarg, NULL, 16); |
| if (!strcmp(longopts[option_index].name, "unprivileged")) |
| unprivileged = 1; |
| if (!strcmp(longopts[option_index].name, "usercopy")) |
| data.flags |= UBLK_F_USER_COPY; |
| break; |
| } |
| } |
| |
| ublk_set_debug_mask(debug_mask); |
| |
| data.max_io_buf_bytes = DEF_BUF_SIZE; |
| if (data.nr_hw_queues > MAX_NR_HW_QUEUES) |
| data.nr_hw_queues = MAX_NR_HW_QUEUES; |
| if (data.queue_depth > MAX_QD) |
| data.queue_depth = MAX_QD; |
| if (uring_comp) |
| data.flags |= UBLK_F_URING_CMD_COMP_IN_TASK; |
| if (need_get_data) |
| data.flags |= UBLK_F_NEED_GET_DATA; |
| if (user_recovery) |
| data.flags |= UBLK_F_USER_RECOVERY; |
| if (user_recovery_reissue) |
| data.flags |= UBLK_F_USER_RECOVERY | UBLK_F_USER_RECOVERY_REISSUE; |
| if (unprivileged) |
| data.flags |= UBLK_F_UNPRIVILEGED_DEV; |
| |
| if (data.tgt_type == NULL) { |
| fprintf(stderr, "no dev type specified\n"); |
| return -EINVAL; |
| } |
| tgt_type = ublksrv_find_tgt_type(data.tgt_type); |
| if (tgt_type == NULL) { |
| fprintf(stderr, "unknown dev type: %s\n", data.tgt_type); |
| return -EINVAL; |
| } |
| data.tgt_ops = tgt_type; |
| data.flags |= tgt_type->ublk_flags; |
| data.ublksrv_flags |= tgt_type->ublksrv_flags; |
| |
| //optind = 0; /* so that tgt code can parse their arguments */ |
| data.tgt_argc = argc; |
| data.tgt_argv = argv; |
| dev = ublksrv_ctrl_init(&data); |
| if (!dev) { |
| fprintf(stderr, "can't init dev %d\n", data.dev_id); |
| return -ENODEV; |
| } |
| |
| ret = ublksrv_ctrl_add_dev(dev); |
| if (ret < 0) { |
| fprintf(stderr, "can't add dev %d, ret %d\n", data.dev_id, ret); |
| goto fail; |
| } |
| |
| { |
| const struct ublksrv_ctrl_dev_info *info = |
| ublksrv_ctrl_get_dev_info(dev); |
| data.dev_id = info->dev_id; |
| } |
| ret = ublksrv_start_daemon(dev); |
| if (ret <= 0) { |
| fprintf(stderr, "start dev %d daemon failed, ret %d\n", |
| data.dev_id, ret); |
| goto fail_del_dev; |
| } |
| |
| dump_buf = ublksrv_tgt_get_dev_data(dev); |
| ublksrv_tgt_set_params(dev, dump_buf); |
| |
| ret = ublksrv_ctrl_start_dev(dev, ret); |
| if (ret < 0) { |
| fprintf(stderr, "start dev %d failed, ret %d\n", data.dev_id, |
| ret); |
| goto fail_stop_daemon; |
| } |
| ret = ublksrv_ctrl_get_info(dev); |
| ublksrv_ctrl_dump(dev, dump_buf); |
| ublksrv_ctrl_deinit(dev); |
| return 0; |
| |
| fail_stop_daemon: |
| ublksrv_stop_io_daemon(dev); |
| fail_del_dev: |
| ublksrv_ctrl_del_dev(dev); |
| fail: |
| ublksrv_ctrl_deinit(dev); |
| |
| return ret; |
| } |
| |
| struct tgt_types_name { |
| unsigned pos; |
| char names[4096 - sizeof(unsigned)]; |
| }; |
| |
| static void collect_tgt_types(unsigned int idx, |
| const struct ublksrv_tgt_type *type, void *pdata) |
| { |
| struct tgt_types_name *data = (struct tgt_types_name *)pdata; |
| |
| if (idx > 0) |
| data->pos += snprintf(data->names + data->pos, |
| sizeof(data->names) - data->pos, "|"); |
| data->pos += snprintf(data->names + data->pos, |
| sizeof(data->names) - data->pos, "%s", type->name); |
| } |
| |
| static void show_tgt_add_usage(unsigned int idx, |
| const struct ublksrv_tgt_type *type, void *data) |
| { |
| if (type->usage_for_add) |
| type->usage_for_add(); |
| } |
| |
| static void cmd_dev_add_usage(const char *cmd) |
| { |
| struct tgt_types_name data = { |
| .pos = 0, |
| }; |
| |
| data.pos += snprintf(data.names + data.pos, sizeof(data.names) - data.pos, "{"); |
| ublksrv_for_each_tgt_type(collect_tgt_types, &data); |
| data.pos += snprintf(data.names + data.pos, sizeof(data.names) - data.pos, "}"); |
| |
| printf("%s add -t %s\n", cmd, data.names); |
| printf("\t-n DEV_ID -q NR_HW_QUEUES -d QUEUE_DEPTH\n"); |
| printf("\t-u URING_COMP -g NEED_GET_DATA -r USER_RECOVERY\n"); |
| printf("\t-i USER_RECOVERY_REISSUE --debug_mask=0x{DBG_MASK}\n"); |
| printf("\t--unprivileged\n\n"); |
| printf("\ttarget specific command line:\n"); |
| ublksrv_for_each_tgt_type(show_tgt_add_usage, NULL); |
| } |
| |
| static int __cmd_dev_del(int number, bool log, bool async) |
| { |
| struct ublksrv_ctrl_dev *dev; |
| int ret; |
| struct ublksrv_dev_data data = { |
| .dev_id = number, |
| .run_dir = UBLKSRV_PID_DIR, |
| }; |
| |
| dev = ublksrv_ctrl_init(&data); |
| |
| ret = ublksrv_ctrl_get_info(dev); |
| if (ret < 0) { |
| ret = 0; |
| if (log) |
| fprintf(stderr, "can't get dev info from %d: %d\n", number, ret); |
| goto fail; |
| } |
| |
| ret = ublksrv_ctrl_stop_dev(dev); |
| if (ret < 0) { |
| fprintf(stderr, "stop dev %d failed\n", number); |
| goto fail; |
| } |
| |
| ret = ublksrv_stop_io_daemon(dev); |
| if (ret < 0) |
| fprintf(stderr, "stop daemon %d failed\n", number); |
| |
| if (async) |
| ret = ublksrv_ctrl_del_dev_async(dev); |
| else |
| ret = ublksrv_ctrl_del_dev(dev); |
| if (ret < 0) { |
| fprintf(stderr, "delete dev %d failed %d\n", number, ret); |
| goto fail; |
| } |
| |
| fail: |
| ublksrv_ctrl_deinit(dev); |
| return ret; |
| } |
| |
| static int cmd_dev_del(int argc, char *argv[]) |
| { |
| static const struct option longopts[] = { |
| { "number", 1, NULL, 'n' }, |
| { "all", 0, NULL, 'a' }, |
| { "async", 0, NULL, 0 }, |
| { NULL } |
| }; |
| int number = -1; |
| int opt, ret, i; |
| unsigned async = 0; |
| int option_index = 0; |
| |
| while ((opt = getopt_long(argc, argv, "n:a", |
| longopts, &option_index)) != -1) { |
| switch (opt) { |
| case 'a': |
| break; |
| |
| case 'n': |
| number = strtol(optarg, NULL, 10); |
| break; |
| case 0: |
| if (!strcmp(longopts[option_index].name, "async")) |
| async = 1; |
| } |
| } |
| |
| if (number >= 0) |
| return __cmd_dev_del(number, true, async); |
| |
| for (i = 0; i < MAX_NR_UBLK_DEVS; i++) |
| ret = __cmd_dev_del(i, false, async); |
| |
| return ret; |
| } |
| |
| static void cmd_dev_del_usage(const char *cmd) |
| { |
| printf("%s del -n DEV_ID [-a | --all]\n", cmd); |
| } |
| |
| static int list_one_dev(int number, bool log, bool verbose) |
| { |
| struct ublksrv_dev_data data = { |
| .dev_id = number, |
| .run_dir = UBLKSRV_PID_DIR, |
| }; |
| struct ublksrv_ctrl_dev *dev = ublksrv_ctrl_init(&data); |
| int ret; |
| |
| ret = ublksrv_ctrl_get_info(dev); |
| if (ret < 0) { |
| if (log) |
| fprintf(stderr, "can't get dev info from %d: %d\n", number, ret); |
| } else { |
| const char *buf = ublksrv_tgt_get_dev_data(dev); |
| |
| if (verbose) |
| ublksrv_json_dump(buf); |
| else |
| ublksrv_ctrl_dump(dev, buf); |
| } |
| |
| ublksrv_ctrl_deinit(dev); |
| |
| return ret; |
| } |
| |
| static int cmd_list_dev_info(int argc, char *argv[]) |
| { |
| static const struct option longopts[] = { |
| { "number", 0, NULL, 'n' }, |
| { "verbose", 0, NULL, 'v' }, |
| { NULL } |
| }; |
| int number = -1; |
| int opt, i; |
| bool verbose = false; |
| |
| while ((opt = getopt_long(argc, argv, "n:v", |
| longopts, NULL)) != -1) { |
| switch (opt) { |
| case 'n': |
| number = strtol(optarg, NULL, 10); |
| break; |
| case 'v': |
| verbose = 1; |
| break; |
| } |
| } |
| |
| if (number >= 0) |
| return list_one_dev(number, true, verbose); |
| |
| for (i = 0; i < MAX_NR_UBLK_DEVS; i++) |
| list_one_dev(i, false, verbose); |
| |
| return 0; |
| } |
| |
| static void cmd_dev_list_usage(const char *cmd) |
| { |
| printf("%s list [-n DEV_ID]\n", cmd); |
| } |
| |
| #define const_ilog2(x) (63 - __builtin_clzll(x)) |
| |
| static int cmd_dev_get_features(int argc, char *argv[]) |
| { |
| struct ublksrv_dev_data data = { |
| .dev_id = -1, |
| .run_dir = UBLKSRV_PID_DIR, |
| }; |
| struct ublksrv_ctrl_dev *dev = ublksrv_ctrl_init(&data); |
| __u64 features = 0; |
| int ret; |
| static const char *feat_map[] = { |
| [const_ilog2(UBLK_F_SUPPORT_ZERO_COPY)] = "ZERO_COPY", |
| [const_ilog2(UBLK_F_URING_CMD_COMP_IN_TASK)] = "COMP_IN_TASK", |
| [const_ilog2(UBLK_F_NEED_GET_DATA)] = "GET_DATA", |
| [const_ilog2(UBLK_F_USER_RECOVERY)] = "USER_RECOVERY", |
| [const_ilog2(UBLK_F_USER_RECOVERY_REISSUE)] = "RECOVERY_REISSUE", |
| [const_ilog2(UBLK_F_UNPRIVILEGED_DEV)] = "UNPRIVILEGED_DEV", |
| [const_ilog2(UBLK_F_CMD_IOCTL_ENCODE)] = "CMD_IOCTL_ENCODE", |
| }; |
| |
| ret = ublksrv_ctrl_get_features(dev, &features); |
| if (!ret) { |
| int i; |
| |
| printf("ublk_drv features: 0x%llx\n", features); |
| |
| for (i = 0; i < sizeof(features); i++) { |
| const char *feat; |
| |
| if (!((1ULL << i) & features)) |
| continue; |
| if (i < sizeof(feat_map) / sizeof(feat_map[0])) |
| feat = feat_map[i]; |
| else |
| feat = "unknown"; |
| printf("\t%-20s: 0x%llx\n", feat, 1ULL << i); |
| } |
| } |
| |
| return ret; |
| } |
| |
| static void cmd_dev_get_features_help(const char *cmd) |
| { |
| printf("%s features\n", cmd); |
| } |
| |
| static int __cmd_dev_user_recover(int number, bool verbose) |
| { |
| const struct ublksrv_tgt_type *tgt_type; |
| struct ublksrv_dev_data data = { |
| .dev_id = number, |
| .run_dir = UBLKSRV_PID_DIR, |
| }; |
| struct ublksrv_ctrl_dev_info dev_info; |
| struct ublksrv_ctrl_dev *dev; |
| struct ublksrv_tgt_base_json tgt_json = {0}; |
| char *buf = NULL; |
| char pid_file[64]; |
| int ret; |
| unsigned elapsed = 0; |
| |
| dev = ublksrv_ctrl_init(&data); |
| if (!dev) { |
| fprintf(stderr, "ublksrv_ctrl_init failure dev %d\n", number); |
| return -ENOMEM; |
| } |
| |
| ret = ublksrv_ctrl_get_info(dev); |
| if (ret < 0) { |
| fprintf(stderr, "can't get dev info from %d\n", number); |
| goto fail; |
| } |
| |
| while (elapsed < 30000000) { |
| unsigned unit = 100000; |
| ret = ublksrv_ctrl_start_recovery(dev); |
| if (ret < 0 && ret != -EBUSY) { |
| fprintf(stderr, "can't start recovery for %d ret %d\n", |
| number, ret); |
| goto fail; |
| } |
| if (ret >= 0) |
| break; |
| usleep(unit); |
| elapsed += unit; |
| } |
| |
| buf = ublksrv_tgt_get_dev_data(dev); |
| if (!buf) { |
| fprintf(stderr, "get dev %d data failed\n", number); |
| ret = -1; |
| goto fail; |
| } |
| |
| ret = ublksrv_json_read_dev_info(buf, &dev_info); |
| if (ret < 0) { |
| fprintf(stderr, "can't read dev info for %d\n", number); |
| goto fail; |
| } |
| |
| if (dev_info.dev_id != number) { |
| fprintf(stderr, "dev id doesn't match read %d for dev %d\n", |
| dev_info.dev_id, number); |
| goto fail; |
| } |
| |
| ret = ublksrv_json_read_target_base_info(buf, &tgt_json); |
| if (ret < 0) { |
| fprintf(stderr, "can't read dev info for %d\n", number); |
| goto fail; |
| } |
| |
| snprintf(pid_file, 64, "%s/%d.pid", data.run_dir, number); |
| ret = unlink(pid_file); |
| if (ret < 0) { |
| fprintf(stderr, "can't delete old pid_file for %d, error:%s\n", |
| number, strerror(errno)); |
| goto fail; |
| } |
| |
| tgt_type = ublksrv_find_tgt_type(tgt_json.name); |
| if (!tgt_type) { |
| fprintf(stderr, "can't find target type %s\n", tgt_json.name); |
| goto fail; |
| } |
| |
| ublksrv_ctrl_prep_recovery(dev, tgt_json.name, tgt_type, buf); |
| |
| ret = ublksrv_start_daemon(dev); |
| if (ret < 0) { |
| fprintf(stderr, "start daemon %d failed\n", number); |
| goto fail; |
| } |
| |
| ret = ublksrv_ctrl_end_recovery(dev, ret); |
| if (ret < 0) { |
| fprintf(stderr, "end recovery for %d failed\n", number); |
| goto fail; |
| } |
| |
| ret = ublksrv_ctrl_get_info(dev); |
| if (ret < 0) { |
| fprintf(stderr, "can't get dev info from %d\n", number); |
| goto fail; |
| } |
| |
| if (verbose) { |
| free(buf); |
| buf = ublksrv_tgt_get_dev_data(dev); |
| ublksrv_ctrl_dump(dev, buf); |
| } |
| |
| fail: |
| free(buf); |
| ublksrv_ctrl_deinit(dev); |
| return ret; |
| } |
| |
| static int cmd_dev_user_recover(int argc, char *argv[]) |
| { |
| static const struct option longopts[] = { |
| { "number", 0, NULL, 'n' }, |
| { "verbose", 0, NULL, 'v' }, |
| { NULL } |
| }; |
| int number = -1; |
| int opt; |
| bool verbose = false; |
| |
| while ((opt = getopt_long(argc, argv, "n:v", |
| longopts, NULL)) != -1) { |
| switch (opt) { |
| case 'n': |
| number = strtol(optarg, NULL, 10); |
| break; |
| case 'v': |
| verbose = true; |
| break; |
| } |
| } |
| |
| return __cmd_dev_user_recover(number, verbose); |
| } |
| |
| static void cmd_dev_recover_usage(const char *cmd) |
| { |
| printf("%s recover [-n DEV_ID]\n", cmd); |
| } |
| |
| static void cmd_usage(const char *cmd) |
| { |
| cmd_dev_add_usage(cmd); |
| cmd_dev_del_usage(cmd); |
| cmd_dev_list_usage(cmd); |
| cmd_dev_recover_usage(cmd); |
| cmd_dev_get_features_help(cmd); |
| |
| printf("%s -v [--version]\n", cmd); |
| printf("%s -h [--help]\n", cmd); |
| } |
| |
| int main(int argc, char *argv[]) |
| { |
| const char *prog_name = "ublk"; |
| |
| char *cmd; |
| int ret; |
| char exe[PATH_MAX]; |
| |
| full_cmd = argv[0]; |
| strncpy(exe, full_cmd, PATH_MAX - 1); |
| |
| setvbuf(stdout, NULL, _IOLBF, 0); |
| |
| cmd = pop_cmd(&argc, argv); |
| if (cmd == NULL) { |
| printf("%s: missing command\n", argv[0]); |
| cmd_usage(prog_name); |
| return EXIT_FAILURE; |
| } |
| |
| if (!strcmp(cmd, "add")) |
| ret = cmd_dev_add(argc, argv); |
| else if (!strcmp(cmd, "del")) |
| ret = cmd_dev_del(argc, argv); |
| else if (!strcmp(cmd, "list")) |
| ret = cmd_list_dev_info(argc, argv); |
| else if (!strcmp(cmd, "recover")) |
| ret = cmd_dev_user_recover(argc, argv); |
| else if (!strcmp(cmd, "features")) |
| ret = cmd_dev_get_features(argc, argv); |
| else if (!strcmp(cmd, "help") || !strcmp(cmd, "-h") || !strcmp(cmd, "--help")) { |
| cmd_usage(prog_name); |
| ret = EXIT_SUCCESS; |
| } else if (!strcmp(cmd, "-v") || !strcmp(cmd, "--version")) { |
| fprintf(stdout, "%s\n", PACKAGE_STRING); |
| ret = EXIT_SUCCESS; |
| } else { |
| fprintf(stderr, "unknown command: %s\n", cmd); |
| cmd_usage(prog_name); |
| ret = EXIT_FAILURE; |
| } |
| |
| ublk_ctrl_dbg(UBLK_DBG_CTRL_CMD, "cmd %s: result %d\n", cmd, ret); |
| |
| return ret; |
| } |