24namespace ex = ublk::detail::ex;
28 uint16_t nr_queues = 1;
29 uint16_t queue_depth = 32;
30 uint32_t max_io_buf_bytes = 0;
46 bool try_stop =
false;
55struct StartRecoveryCmd {
71using Cmd = std::variant<AddCmd, DelCmd, StartCmd, StopCmd, ListCmd, GetCmd,
72 StartRecoveryCmd, RecoverCmd, FeaturesCmd, QuiesceCmd>;
74const char *state_str(uint32_t state)
noexcept {
80 case UBLK_S_DEV_QUIESCED:
82 case UBLK_S_DEV_FAIL_IO:
92 const char *short_name;
94 {UBLK_F_SUPPORT_ZERO_COPY,
"SUPPORT_ZERO_COPY",
"ZERO_COPY"},
95 {UBLK_F_URING_CMD_COMP_IN_TASK,
"URING_CMD_COMP_IN_TASK",
"COMP_IN_TASK"},
96 {UBLK_F_NEED_GET_DATA,
"NEED_GET_DATA",
"GET_DATA"},
97 {UBLK_F_USER_RECOVERY,
"USER_RECOVERY",
"USER_RECOVERY"},
98 {UBLK_F_USER_RECOVERY_REISSUE,
"USER_RECOVERY_REISSUE",
"RECOVERY_REISSUE"},
99 {UBLK_F_UNPRIVILEGED_DEV,
"UNPRIVILEGED_DEV",
"UNPRIVILEGED_DEV"},
100 {UBLK_F_CMD_IOCTL_ENCODE,
"CMD_IOCTL_ENCODE",
"CMD_IOCTL_ENCODE"},
101 {UBLK_F_USER_COPY,
"USER_COPY",
"USER_COPY"},
102 {UBLK_F_ZONED,
"ZONED",
"ZONED"},
103 {UBLK_F_USER_RECOVERY_FAIL_IO,
"USER_RECOVERY_FAIL_IO",
"RECOVERY_FAIL_IO"},
104 {UBLK_F_UPDATE_SIZE,
"UPDATE_SIZE",
"UPDATE_SIZE"},
105 {UBLK_F_AUTO_BUF_REG,
"AUTO_BUF_REG",
"AUTO_ZC"},
106 {UBLK_F_QUIESCE,
"QUIESCE",
"QUIESCE"},
107 {UBLK_F_PER_IO_DAEMON,
"PER_IO_DAEMON",
"PER_IO_DAEMON"},
108 {UBLK_F_BUF_REG_OFF_DAEMON,
"BUF_REG_OFF_DAEMON",
"BUF_REG_OFF_DAEMON"},
109 {UBLK_F_BATCH_IO,
"BATCH_IO",
"BATCH_IO"},
110 {UBLK_F_INTEGRITY,
"INTEGRITY",
"INTEGRITY"},
111 {UBLK_F_SAFE_STOP_DEV,
"SAFE_STOP_DEV",
"SAFE_STOP_DEV"},
112 {UBLK_F_NO_AUTO_PART_SCAN,
"NO_AUTO_PART_SCAN",
"NO_AUTO_PART_SCAN"},
113 {UBLK_F_SHMEM_ZC,
"SHMEM_ZC",
"SHMEM_ZC"},
116std::string flags_str(uint64_t flags)
noexcept {
118 for (
const auto &e : FLAG_TABLE) {
119 if (flags & e.flag) {
129void dump_dev_info(uint32_t dev_id,
const ublksrv_ctrl_dev_info &info,
130 const ublk_params ¶ms) {
131 std::cout << std::format(
"dev id {}: nr_hw_queues {} queue_depth {} block "
132 "size {} dev_capacity {}\n",
133 dev_id, info.nr_hw_queues, info.queue_depth,
134 1U << params.basic.logical_bs_shift,
135 params.basic.dev_sectors);
136 std::cout << std::format(
"\tmax rq size {} daemon pid {} state {}\n",
137 info.max_io_buf_bytes, info.ublksrv_pid,
138 state_str(info.state));
139 std::cout << std::format(
"\tflags 0x{:x} [{}]\n", info.flags,
140 flags_str(info.flags));
141 std::cout << std::format(
"\tublkc: {}:{} ublkb: {}:{} owner: {}:{}\n",
142 params.devt.char_major, params.devt.char_minor,
143 params.devt.disk_major, params.devt.disk_minor,
144 info.owner_uid, info.owner_gid);
147std::string cpu_str(
const cpu_set_t &cpuset)
noexcept {
150 while (i < CPU_SETSIZE) {
151 if (!CPU_ISSET(i, &cpuset)) {
158 }
while (i < CPU_SETSIZE && CPU_ISSET(i, &cpuset));
163 s += std::to_string(start);
166 s += std::to_string(end);
172ex::task<void> dump_queue_affinity(
int ctrl_fd,
173 const ublksrv_ctrl_dev_info &info) {
174 for (uint16_t q_id = 0; q_id < info.nr_hw_queues; q_id++) {
177 std::cout << std::format(
"\tqueue {}: affinity({})\n", q_id,
182ex::task<void> run_cmd(
int ctrl_fd,
const AddCmd &cmd) {
183 ublksrv_ctrl_dev_info info = {};
184 info.dev_id = cmd.dev_id;
185 info.nr_hw_queues = cmd.nr_queues;
186 info.queue_depth = cmd.queue_depth;
187 info.max_io_buf_bytes = cmd.max_io_buf_bytes;
188 info.flags = cmd.flags;
190 ublk_params params = {};
191 params.len =
sizeof(params);
193 dump_dev_info(info.dev_id, info, params);
194 co_await dump_queue_affinity(ctrl_fd, info);
197ex::task<void> run_cmd(
int ctrl_fd,
const DelCmd &cmd) {
205ex::task<void> run_cmd(
int ctrl_fd,
const StartCmd &cmd) {
209ex::task<void> run_cmd(
int ctrl_fd,
const StopCmd &cmd) {
217ex::task<void> run_cmd(
int ctrl_fd,
const ListCmd &) {
219 std::filesystem::directory_iterator(
"/sys/class/ublk-char")) {
220 const std::string name = entry.path().filename().string();
221 if (!name.starts_with(
"ublkc"))
225 std::from_chars(name.data() + 5, name.data() + name.size(),
id);
226 if (ec != std::errc{} || ptr != name.data() + name.size())
228 ublksrv_ctrl_dev_info info = {};
230 ublk_params params = {};
231 params.len =
sizeof(params);
233 dump_dev_info(
id, info, params);
234 co_await dump_queue_affinity(ctrl_fd, info);
238ex::task<void> run_cmd(
int ctrl_fd,
const GetCmd &cmd) {
239 ublksrv_ctrl_dev_info info = {};
241 ublk_params params = {};
242 params.len =
sizeof(params);
244 dump_dev_info(cmd.dev_id, info, params);
245 co_await dump_queue_affinity(ctrl_fd, info);
248ex::task<void> run_cmd(
int ctrl_fd,
const StartRecoveryCmd &cmd) {
252ex::task<void> run_cmd(
int ctrl_fd,
const RecoverCmd &cmd) {
256ex::task<void> run_cmd(
int ctrl_fd,
const FeaturesCmd &) {
257 uint64_t features = 0;
260 std::cout << std::format(
"ublk_drv features: 0x{:x}\n", features);
261 for (
const auto &e : FLAG_TABLE) {
262 if (features & e.flag)
263 std::cout << std::format(
"\t{:<20s}: 0x{:x}\n", e.short_name,
268ex::task<void> run_cmd(
int ctrl_fd,
const QuiesceCmd &cmd) {
272Cmd parse_args(
int argc,
char *argv[]) {
273 CLI::App app{
"ublk userspace block device control tool"};
280 StartRecoveryCmd start_recovery_opts;
281 RecoverCmd recover_opts;
282 QuiesceCmd quiesce_opts;
284 app.require_subcommand();
286 auto *add_cmd = app.add_subcommand(
"add",
"add a device");
287 add_cmd->add_option(
"-n", add_opts.dev_id,
"device id (default: auto)");
288 add_cmd->add_option(
"-q", add_opts.nr_queues,
"number of queues");
289 add_cmd->add_option(
"-d", add_opts.queue_depth,
"queue depth");
290 add_cmd->add_option(
"-b", add_opts.max_io_buf_bytes,
"max io buf bytes");
291 add_cmd->add_flag_callback(
"--zerocopy", [&add_opts]() {
292 add_opts.flags |= UBLK_F_SUPPORT_ZERO_COPY;
294 add_cmd->add_flag_callback(
295 "--batch", [&add_opts]() { add_opts.flags |= UBLK_F_BATCH_IO; });
296 add_cmd->add_flag_callback(
"--user-recovery", [&add_opts]() {
297 add_opts.flags |= UBLK_F_USER_RECOVERY;
299 add_cmd->add_flag_callback(
"--user-recovery-fail-io", [&add_opts]() {
300 add_opts.flags |= UBLK_F_USER_RECOVERY_FAIL_IO;
302 add_cmd->add_flag_callback(
"--user-recovery-reissue", [&add_opts]() {
303 add_opts.flags |= UBLK_F_USER_RECOVERY_REISSUE;
305 add_cmd->add_flag_callback(
"--unprivileged", [&add_opts]() {
306 add_opts.flags |= UBLK_F_UNPRIVILEGED_DEV;
308 add_cmd->add_flag_callback(
309 "--usercopy", [&add_opts]() { add_opts.flags |= UBLK_F_USER_COPY; });
311 auto *del_cmd = app.add_subcommand(
"del",
"delete a device");
312 del_cmd->add_option(
"-n", del_opts.dev_id,
"device id")->required();
313 del_cmd->add_flag(
"--async", del_opts.async,
"delete asynchronously");
315 auto *start_cmd = app.add_subcommand(
"start",
"start a device");
316 start_cmd->add_option(
"-n", start_opts.dev_id,
"device id")->required();
317 start_cmd->add_option(
"--pid", start_opts.daemon_pid,
"daemon pid")
320 auto *stop_cmd = app.add_subcommand(
"stop",
"stop a device");
321 stop_cmd->add_option(
"-n", stop_opts.dev_id,
"device id")->required();
322 stop_cmd->add_flag(
"--try", stop_opts.try_stop,
"try stop");
324 app.add_subcommand(
"list",
"list devices");
326 auto *get_cmd = app.add_subcommand(
"get",
"get device info");
327 get_cmd->add_option(
"-n", get_opts.dev_id,
"device id")->required();
329 auto *start_recovery_cmd =
330 app.add_subcommand(
"start-recovery",
"start user recovery");
332 ->add_option(
"-n", start_recovery_opts.dev_id,
"device id")
335 auto *recover_cmd = app.add_subcommand(
"recover",
"recover a device");
336 recover_cmd->add_option(
"-n", recover_opts.dev_id,
"device id")->required();
337 recover_cmd->add_option(
"--pid", recover_opts.daemon_pid,
"daemon pid")
340 app.add_subcommand(
"features",
"get device features");
342 auto *quiesce_cmd = app.add_subcommand(
"quiesce",
"quiesce a device");
343 quiesce_cmd->add_option(
"-n", quiesce_opts.dev_id,
"device id")->required();
345 ->add_option(
"timeout_ms", quiesce_opts.timeout_ms,
"timeout in ms")
349 app.parse(argc, argv);
350 }
catch (
const CLI::ParseError &e) {
351 std::exit(app.exit(e));
354 if (app.got_subcommand(
"add")) {
356 }
else if (app.got_subcommand(
"del")) {
358 }
else if (app.got_subcommand(
"start")) {
360 }
else if (app.got_subcommand(
"stop")) {
362 }
else if (app.got_subcommand(
"list")) {
364 }
else if (app.got_subcommand(
"get")) {
366 }
else if (app.got_subcommand(
"start-recovery")) {
367 return start_recovery_opts;
368 }
else if (app.got_subcommand(
"recover")) {
370 }
else if (app.got_subcommand(
"features")) {
371 return FeaturesCmd{};
372 }
else if (app.got_subcommand(
"quiesce")) {
375 throw std::runtime_error(
"unhandled subcommand");
380int main(
int argc,
char *argv[])
noexcept(
false) {
382 auto cmd = parse_args(argc, argv);
384 int ctrl_fd = open(
"/dev/ublk-control", O_RDWR);
386 throw std::system_error(errno, std::generic_category(),
387 "open /dev/ublk-control");
389 auto d = ublk::detail::defer([&]()
noexcept { close(ctrl_fd); });
391 ublk::detail::IoLoop loop(condy::RuntimeOptions().enable_sqe128(), 0, 0,
393 ex::sync_wait(ex::starts_on(
394 loop.get_scheduler(),
396 std::visit([&](
const auto &c) { return run_cmd(ctrl_fd, c); },
398 }
catch (
const std::system_error &e) {
399 std::cerr << std::format(
"ublkctl: {}\n", e.what());
400 return e.code().value();
401 }
catch (
const std::exception &e) {
402 std::cerr << std::format(
"ublkctl: {}\n", e.what());
auto start(int control_fd, uint32_t dev_id, int32_t daemon_pid) noexcept
Start the device, served by this daemon, if possible.
auto stop_dev(int control_fd, uint32_t dev_id) noexcept
Stop a ublk device.
auto end_user_recovery(int control_fd, uint32_t dev_id, int32_t daemon_pid) noexcept
End user recovery for a ublk device.
auto get_features(int control_fd, uint64_t *features) noexcept
Get the feature flags supported by the ublk driver.
auto get_params(int control_fd, uint32_t dev_id, ublk_params *params) noexcept
Get the parameters of a ublk device.
auto quiesce_dev(int control_fd, uint32_t dev_id, uint64_t timeout_ms) noexcept
Quiesce a ublk device (UBLK_F_QUIESCE).
auto start_dev(int control_fd, uint32_t dev_id, int32_t daemon_pid) noexcept
Start a ublk device.
auto del_dev_async(int control_fd, uint32_t dev_id) noexcept
Delete a ublk device asynchronously.
auto get_dev_info(int control_fd, uint32_t dev_id, ublksrv_ctrl_dev_info *info) noexcept
Get the device information for a specific ublk device.
auto try_stop_dev(int control_fd, uint32_t dev_id) noexcept
Try to stop a ublk device if it has no openers (UBLK_F_SAFE_STOP_DEV).
auto start_user_recovery(int control_fd, uint32_t dev_id) noexcept
Start user recovery for a ublk device.
auto add_dev(int control_fd, ublksrv_ctrl_dev_info *info) noexcept
Add a new ublk device.
auto get_queue_affinity(int control_fd, uint32_t dev_id, uint16_t q_id, cpu_set_t *cpuset) noexcept
Get the queue affinity for a specific ublk queue.
auto del_dev(int control_fd, uint32_t dev_id) noexcept
Delete a ublk device.
Main include file for the ublk-cpp library.