23namespace ex = condy::detail::ex;
27 uint16_t nr_queues = 1;
28 uint16_t queue_depth = 32;
29 uint32_t max_io_buf_bytes = 0;
45 bool try_stop =
false;
54struct StartRecoveryCmd {
70using Cmd = std::variant<AddCmd, DelCmd, StartCmd, StopCmd, ListCmd, GetCmd,
71 StartRecoveryCmd, RecoverCmd, FeaturesCmd, QuiesceCmd>;
73const char *state_str(uint32_t state)
noexcept {
79 case UBLK_S_DEV_QUIESCED:
81 case UBLK_S_DEV_FAIL_IO:
91 const char *short_name;
93 {UBLK_F_SUPPORT_ZERO_COPY,
"SUPPORT_ZERO_COPY",
"ZERO_COPY"},
94 {UBLK_F_URING_CMD_COMP_IN_TASK,
"URING_CMD_COMP_IN_TASK",
"COMP_IN_TASK"},
95 {UBLK_F_NEED_GET_DATA,
"NEED_GET_DATA",
"GET_DATA"},
96 {UBLK_F_USER_RECOVERY,
"USER_RECOVERY",
"USER_RECOVERY"},
97 {UBLK_F_USER_RECOVERY_REISSUE,
"USER_RECOVERY_REISSUE",
"RECOVERY_REISSUE"},
98 {UBLK_F_UNPRIVILEGED_DEV,
"UNPRIVILEGED_DEV",
"UNPRIVILEGED_DEV"},
99 {UBLK_F_CMD_IOCTL_ENCODE,
"CMD_IOCTL_ENCODE",
"CMD_IOCTL_ENCODE"},
100 {UBLK_F_USER_COPY,
"USER_COPY",
"USER_COPY"},
101 {UBLK_F_ZONED,
"ZONED",
"ZONED"},
102 {UBLK_F_USER_RECOVERY_FAIL_IO,
"USER_RECOVERY_FAIL_IO",
"RECOVERY_FAIL_IO"},
103 {UBLK_F_UPDATE_SIZE,
"UPDATE_SIZE",
"UPDATE_SIZE"},
104 {UBLK_F_AUTO_BUF_REG,
"AUTO_BUF_REG",
"AUTO_ZC"},
105 {UBLK_F_QUIESCE,
"QUIESCE",
"QUIESCE"},
106 {UBLK_F_PER_IO_DAEMON,
"PER_IO_DAEMON",
"PER_IO_DAEMON"},
107 {UBLK_F_BUF_REG_OFF_DAEMON,
"BUF_REG_OFF_DAEMON",
"BUF_REG_OFF_DAEMON"},
108 {UBLK_F_BATCH_IO,
"BATCH_IO",
"BATCH_IO"},
109 {UBLK_F_INTEGRITY,
"INTEGRITY",
"INTEGRITY"},
110 {UBLK_F_SAFE_STOP_DEV,
"SAFE_STOP_DEV",
"SAFE_STOP_DEV"},
111 {UBLK_F_NO_AUTO_PART_SCAN,
"NO_AUTO_PART_SCAN",
"NO_AUTO_PART_SCAN"},
112 {UBLK_F_SHMEM_ZC,
"SHMEM_ZC",
"SHMEM_ZC"},
115std::string flags_str(uint64_t flags)
noexcept {
117 for (
const auto &e : FLAG_TABLE) {
118 if (flags & e.flag) {
128void dump_dev_info(uint32_t dev_id,
const ublksrv_ctrl_dev_info &info,
129 const ublk_params ¶ms) {
130 std::println(
"dev id {}: nr_hw_queues {} queue_depth {} block size {} "
132 dev_id, info.nr_hw_queues, info.queue_depth,
133 1U << params.basic.logical_bs_shift, params.basic.dev_sectors);
134 std::println(
"\tmax rq size {} daemon pid {} state {}",
135 info.max_io_buf_bytes, info.ublksrv_pid,
136 state_str(info.state));
137 std::println(
"\tflags 0x{:x} [{}]", info.flags, flags_str(info.flags));
138 std::println(
"\tublkc: {}:{} ublkb: {}:{} owner: {}:{}",
139 params.devt.char_major, params.devt.char_minor,
140 params.devt.disk_major, params.devt.disk_minor, info.owner_uid,
144std::string cpu_str(
const cpu_set_t &cpuset)
noexcept {
147 while (i < CPU_SETSIZE) {
148 if (!CPU_ISSET(i, &cpuset)) {
155 }
while (i < CPU_SETSIZE && CPU_ISSET(i, &cpuset));
160 s += std::to_string(start);
163 s += std::to_string(end);
169ex::task<void> dump_queue_affinity(
int ctrl_fd,
170 const ublksrv_ctrl_dev_info &info) {
171 for (uint16_t q_id = 0; q_id < info.nr_hw_queues; q_id++) {
174 std::println(
"\tqueue {}: affinity({})", q_id, cpu_str(cpuset));
178ex::task<void> run_cmd(
int ctrl_fd,
const AddCmd &cmd) {
179 ublksrv_ctrl_dev_info info = {};
180 info.dev_id = cmd.dev_id;
181 info.nr_hw_queues = cmd.nr_queues;
182 info.queue_depth = cmd.queue_depth;
183 info.max_io_buf_bytes = cmd.max_io_buf_bytes;
184 info.flags = cmd.flags;
186 ublk_params params = {};
187 params.len =
sizeof(params);
189 dump_dev_info(info.dev_id, info, params);
190 co_await dump_queue_affinity(ctrl_fd, info);
193ex::task<void> run_cmd(
int ctrl_fd,
const DelCmd &cmd) {
201ex::task<void> run_cmd(
int ctrl_fd,
const StartCmd &cmd) {
205ex::task<void> run_cmd(
int ctrl_fd,
const StopCmd &cmd) {
213ex::task<void> run_cmd(
int ctrl_fd,
const ListCmd &) {
215 std::filesystem::directory_iterator(
"/sys/class/ublk-char")) {
216 const std::string name = entry.path().filename().string();
217 if (!name.starts_with(
"ublkc"))
221 std::from_chars(name.data() + 5, name.data() + name.size(),
id);
222 if (ec != std::errc{} || ptr != name.data() + name.size())
224 ublksrv_ctrl_dev_info info = {};
226 ublk_params params = {};
227 params.len =
sizeof(params);
229 dump_dev_info(
id, info, params);
230 co_await dump_queue_affinity(ctrl_fd, info);
234ex::task<void> run_cmd(
int ctrl_fd,
const GetCmd &cmd) {
235 ublksrv_ctrl_dev_info info = {};
237 ublk_params params = {};
238 params.len =
sizeof(params);
240 dump_dev_info(cmd.dev_id, info, params);
241 co_await dump_queue_affinity(ctrl_fd, info);
244ex::task<void> run_cmd(
int ctrl_fd,
const StartRecoveryCmd &cmd) {
248ex::task<void> run_cmd(
int ctrl_fd,
const RecoverCmd &cmd) {
252ex::task<void> run_cmd(
int ctrl_fd,
const FeaturesCmd &) {
253 uint64_t features = 0;
256 std::println(
"ublk_drv features: 0x{:x}", features);
257 for (
const auto &e : FLAG_TABLE) {
258 if (features & e.flag)
259 std::println(
"\t{:<20s}: 0x{:x}", e.short_name, e.flag);
263ex::task<void> run_cmd(
int ctrl_fd,
const QuiesceCmd &cmd) {
267Cmd parse_args(
int argc,
char *argv[]) {
268 CLI::App app{
"ublk userspace block device control tool"};
275 StartRecoveryCmd start_recovery_opts;
276 RecoverCmd recover_opts;
277 QuiesceCmd quiesce_opts;
279 app.require_subcommand();
281 auto *add_cmd = app.add_subcommand(
"add",
"add a device");
282 add_cmd->add_option(
"-n", add_opts.dev_id,
"device id (default: auto)");
283 add_cmd->add_option(
"-q", add_opts.nr_queues,
"number of queues");
284 add_cmd->add_option(
"-d", add_opts.queue_depth,
"queue depth");
285 add_cmd->add_option(
"-b", add_opts.max_io_buf_bytes,
"max io buf bytes");
286 add_cmd->add_flag_callback(
"--zerocopy", [&add_opts]() {
287 add_opts.flags |= UBLK_F_SUPPORT_ZERO_COPY;
289 add_cmd->add_flag_callback(
290 "--batch", [&add_opts]() { add_opts.flags |= UBLK_F_BATCH_IO; });
291 add_cmd->add_flag_callback(
"--user-recovery", [&add_opts]() {
292 add_opts.flags |= UBLK_F_USER_RECOVERY;
294 add_cmd->add_flag_callback(
"--user-recovery-fail-io", [&add_opts]() {
295 add_opts.flags |= UBLK_F_USER_RECOVERY_FAIL_IO;
297 add_cmd->add_flag_callback(
"--user-recovery-reissue", [&add_opts]() {
298 add_opts.flags |= UBLK_F_USER_RECOVERY_REISSUE;
300 add_cmd->add_flag_callback(
"--unprivileged", [&add_opts]() {
301 add_opts.flags |= UBLK_F_UNPRIVILEGED_DEV;
303 add_cmd->add_flag_callback(
304 "--usercopy", [&add_opts]() { add_opts.flags |= UBLK_F_USER_COPY; });
306 auto *del_cmd = app.add_subcommand(
"del",
"delete a device");
307 del_cmd->add_option(
"-n", del_opts.dev_id,
"device id")->required();
308 del_cmd->add_flag(
"--async", del_opts.async,
"delete asynchronously");
310 auto *start_cmd = app.add_subcommand(
"start",
"start a device");
311 start_cmd->add_option(
"-n", start_opts.dev_id,
"device id")->required();
312 start_cmd->add_option(
"--pid", start_opts.daemon_pid,
"daemon pid")
315 auto *stop_cmd = app.add_subcommand(
"stop",
"stop a device");
316 stop_cmd->add_option(
"-n", stop_opts.dev_id,
"device id")->required();
317 stop_cmd->add_flag(
"--try", stop_opts.try_stop,
"try stop");
319 app.add_subcommand(
"list",
"list devices");
321 auto *get_cmd = app.add_subcommand(
"get",
"get device info");
322 get_cmd->add_option(
"-n", get_opts.dev_id,
"device id")->required();
324 auto *start_recovery_cmd =
325 app.add_subcommand(
"start-recovery",
"start user recovery");
327 ->add_option(
"-n", start_recovery_opts.dev_id,
"device id")
330 auto *recover_cmd = app.add_subcommand(
"recover",
"recover a device");
331 recover_cmd->add_option(
"-n", recover_opts.dev_id,
"device id")->required();
332 recover_cmd->add_option(
"--pid", recover_opts.daemon_pid,
"daemon pid")
335 app.add_subcommand(
"features",
"get device features");
337 auto *quiesce_cmd = app.add_subcommand(
"quiesce",
"quiesce a device");
338 quiesce_cmd->add_option(
"-n", quiesce_opts.dev_id,
"device id")->required();
340 ->add_option(
"timeout_ms", quiesce_opts.timeout_ms,
"timeout in ms")
344 app.parse(argc, argv);
345 }
catch (
const CLI::ParseError &e) {
346 std::exit(app.exit(e));
349 if (app.got_subcommand(
"add")) {
351 }
else if (app.got_subcommand(
"del")) {
353 }
else if (app.got_subcommand(
"start")) {
355 }
else if (app.got_subcommand(
"stop")) {
357 }
else if (app.got_subcommand(
"list")) {
359 }
else if (app.got_subcommand(
"get")) {
361 }
else if (app.got_subcommand(
"start-recovery")) {
362 return start_recovery_opts;
363 }
else if (app.got_subcommand(
"recover")) {
365 }
else if (app.got_subcommand(
"features")) {
366 return FeaturesCmd{};
367 }
else if (app.got_subcommand(
"quiesce")) {
375int main(
int argc,
char *argv[])
noexcept(
false) {
377 auto cmd = parse_args(argc, argv);
379 int ctrl_fd = open(
"/dev/ublk-control", O_RDWR);
381 throw std::system_error(errno, std::generic_category(),
382 "open /dev/ublk-control");
384 auto d = ublk::detail::defer([&]()
noexcept { close(ctrl_fd); });
386 ublk::detail::IoLoop loop(condy::RuntimeOptions().enable_sqe128(), 0, 0,
388 ex::sync_wait(ex::starts_on(
389 loop.get_scheduler(),
390 std::visit([&](
const auto &c) { return run_cmd(ctrl_fd, c); },
392 }
catch (
const std::system_error &e) {
393 std::println(std::cerr,
"ublkctl: {}", e.what());
394 return e.code().value();
395 }
catch (
const std::exception &e) {
396 std::println(std::cerr,
"ublkctl: {}", 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.