ublk-cpp v0.1
A ublk library for std::execution.
Loading...
Searching...
No Matches
ublkctl.cpp
Go to the documentation of this file.
1
5
6#include <CLI11.hpp>
7#include <cerrno>
8#include <charconv>
9#include <condy.hpp>
10#include <cstdint>
11#include <cstdlib>
12#include <fcntl.h>
13#include <filesystem>
14#include <format>
15#include <iostream>
16#include <sched.h>
17#include <stdexcept>
18#include <string>
19#include <ublk.hpp>
20#include <unistd.h>
21#include <utility>
22#include <variant>
23
24namespace ex = ublk::detail::ex;
25
26struct AddCmd {
27 uint32_t dev_id = -1;
28 uint16_t nr_queues = 1;
29 uint16_t queue_depth = 32;
30 uint32_t max_io_buf_bytes = 0;
31 uint64_t flags = 0;
32};
33
34struct DelCmd {
35 uint32_t dev_id;
36 bool async = false;
37};
38
39struct StartCmd {
40 uint32_t dev_id;
41 int32_t daemon_pid;
42};
43
44struct StopCmd {
45 uint32_t dev_id;
46 bool try_stop = false;
47};
48
49struct ListCmd {};
50
51struct GetCmd {
52 uint32_t dev_id;
53};
54
55struct StartRecoveryCmd {
56 uint32_t dev_id;
57};
58
59struct RecoverCmd {
60 uint32_t dev_id;
61 int32_t daemon_pid;
62};
63
64struct FeaturesCmd {};
65
66struct QuiesceCmd {
67 uint32_t dev_id;
68 uint64_t timeout_ms;
69};
70
71using Cmd = std::variant<AddCmd, DelCmd, StartCmd, StopCmd, ListCmd, GetCmd,
72 StartRecoveryCmd, RecoverCmd, FeaturesCmd, QuiesceCmd>;
73
74const char *state_str(uint32_t state) noexcept {
75 switch (state) {
76 case UBLK_S_DEV_DEAD:
77 return "DEAD";
78 case UBLK_S_DEV_LIVE:
79 return "LIVE";
80 case UBLK_S_DEV_QUIESCED:
81 return "QUIESCED";
82 case UBLK_S_DEV_FAIL_IO:
83 return "FAIL_IO";
84 default:
85 return "UNKNOWN";
86 }
87}
88
89constexpr struct {
90 uint64_t flag;
91 const char *name;
92 const char *short_name;
93} FLAG_TABLE[] = {
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"},
114};
115
116std::string flags_str(uint64_t flags) noexcept {
117 std::string s;
118 for (const auto &e : FLAG_TABLE) {
119 if (flags & e.flag) {
120 if (!s.empty()) {
121 s += ' ';
122 }
123 s += e.name;
124 }
125 }
126 return s;
127}
128
129void dump_dev_info(uint32_t dev_id, const ublksrv_ctrl_dev_info &info,
130 const ublk_params &params) {
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);
145}
146
147std::string cpu_str(const cpu_set_t &cpuset) noexcept {
148 std::string s;
149 size_t i = 0;
150 while (i < CPU_SETSIZE) {
151 if (!CPU_ISSET(i, &cpuset)) {
152 i++;
153 continue;
154 }
155 size_t start = i;
156 do {
157 i++;
158 } while (i < CPU_SETSIZE && CPU_ISSET(i, &cpuset));
159 size_t end = i - 1;
160 if (!s.empty()) {
161 s += ' ';
162 }
163 s += std::to_string(start);
164 if (end > start) {
165 s += '-';
166 s += std::to_string(end);
167 }
168 }
169 return s;
170}
171
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++) {
175 cpu_set_t cpuset;
176 co_await ublk::get_queue_affinity(ctrl_fd, info.dev_id, q_id, &cpuset);
177 std::cout << std::format("\tqueue {}: affinity({})\n", q_id,
178 cpu_str(cpuset));
179 }
180}
181
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;
189 co_await ublk::add_dev(ctrl_fd, &info);
190 ublk_params params = {};
191 params.len = sizeof(params);
192 co_await ublk::get_params(ctrl_fd, info.dev_id, &params);
193 dump_dev_info(info.dev_id, info, params);
194 co_await dump_queue_affinity(ctrl_fd, info);
195}
196
197ex::task<void> run_cmd(int ctrl_fd, const DelCmd &cmd) {
198 if (cmd.async) {
199 co_await ublk::del_dev_async(ctrl_fd, cmd.dev_id);
200 } else {
201 co_await ublk::del_dev(ctrl_fd, cmd.dev_id);
202 }
203}
204
205ex::task<void> run_cmd(int ctrl_fd, const StartCmd &cmd) {
206 co_await ublk::start_dev(ctrl_fd, cmd.dev_id, cmd.daemon_pid);
207}
208
209ex::task<void> run_cmd(int ctrl_fd, const StopCmd &cmd) {
210 if (cmd.try_stop) {
211 co_await ublk::try_stop_dev(ctrl_fd, cmd.dev_id);
212 } else {
213 co_await ublk::stop_dev(ctrl_fd, cmd.dev_id);
214 }
215}
216
217ex::task<void> run_cmd(int ctrl_fd, const ListCmd &) {
218 for (auto &entry :
219 std::filesystem::directory_iterator("/sys/class/ublk-char")) {
220 const std::string name = entry.path().filename().string();
221 if (!name.starts_with("ublkc"))
222 continue;
223 size_t id = 0;
224 auto [ptr, ec] =
225 std::from_chars(name.data() + 5, name.data() + name.size(), id);
226 if (ec != std::errc{} || ptr != name.data() + name.size())
227 continue;
228 ublksrv_ctrl_dev_info info = {};
229 co_await ublk::get_dev_info(ctrl_fd, id, &info);
230 ublk_params params = {};
231 params.len = sizeof(params);
232 co_await ublk::get_params(ctrl_fd, id, &params);
233 dump_dev_info(id, info, params);
234 co_await dump_queue_affinity(ctrl_fd, info);
235 }
236}
237
238ex::task<void> run_cmd(int ctrl_fd, const GetCmd &cmd) {
239 ublksrv_ctrl_dev_info info = {};
240 co_await ublk::get_dev_info(ctrl_fd, cmd.dev_id, &info);
241 ublk_params params = {};
242 params.len = sizeof(params);
243 co_await ublk::get_params(ctrl_fd, cmd.dev_id, &params);
244 dump_dev_info(cmd.dev_id, info, params);
245 co_await dump_queue_affinity(ctrl_fd, info);
246}
247
248ex::task<void> run_cmd(int ctrl_fd, const StartRecoveryCmd &cmd) {
249 co_await ublk::start_user_recovery(ctrl_fd, cmd.dev_id);
250}
251
252ex::task<void> run_cmd(int ctrl_fd, const RecoverCmd &cmd) {
253 co_await ublk::end_user_recovery(ctrl_fd, cmd.dev_id, cmd.daemon_pid);
254}
255
256ex::task<void> run_cmd(int ctrl_fd, const FeaturesCmd &) {
257 uint64_t features = 0;
258 co_await ublk::get_features(ctrl_fd, &features);
259
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,
264 e.flag);
265 }
266}
267
268ex::task<void> run_cmd(int ctrl_fd, const QuiesceCmd &cmd) {
269 co_await ublk::quiesce_dev(ctrl_fd, cmd.dev_id, cmd.timeout_ms);
270}
271
272Cmd parse_args(int argc, char *argv[]) {
273 CLI::App app{"ublk userspace block device control tool"};
274
275 AddCmd add_opts;
276 DelCmd del_opts;
277 StartCmd start_opts;
278 StopCmd stop_opts;
279 GetCmd get_opts;
280 StartRecoveryCmd start_recovery_opts;
281 RecoverCmd recover_opts;
282 QuiesceCmd quiesce_opts;
283
284 app.require_subcommand();
285
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;
293 });
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;
298 });
299 add_cmd->add_flag_callback("--user-recovery-fail-io", [&add_opts]() {
300 add_opts.flags |= UBLK_F_USER_RECOVERY_FAIL_IO;
301 });
302 add_cmd->add_flag_callback("--user-recovery-reissue", [&add_opts]() {
303 add_opts.flags |= UBLK_F_USER_RECOVERY_REISSUE;
304 });
305 add_cmd->add_flag_callback("--unprivileged", [&add_opts]() {
306 add_opts.flags |= UBLK_F_UNPRIVILEGED_DEV;
307 });
308 add_cmd->add_flag_callback(
309 "--usercopy", [&add_opts]() { add_opts.flags |= UBLK_F_USER_COPY; });
310
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");
314
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")
318 ->required();
319
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");
323
324 app.add_subcommand("list", "list devices");
325
326 auto *get_cmd = app.add_subcommand("get", "get device info");
327 get_cmd->add_option("-n", get_opts.dev_id, "device id")->required();
328
329 auto *start_recovery_cmd =
330 app.add_subcommand("start-recovery", "start user recovery");
331 start_recovery_cmd
332 ->add_option("-n", start_recovery_opts.dev_id, "device id")
333 ->required();
334
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")
338 ->required();
339
340 app.add_subcommand("features", "get device features");
341
342 auto *quiesce_cmd = app.add_subcommand("quiesce", "quiesce a device");
343 quiesce_cmd->add_option("-n", quiesce_opts.dev_id, "device id")->required();
344 quiesce_cmd
345 ->add_option("timeout_ms", quiesce_opts.timeout_ms, "timeout in ms")
346 ->required();
347
348 try {
349 app.parse(argc, argv);
350 } catch (const CLI::ParseError &e) {
351 std::exit(app.exit(e));
352 }
353
354 if (app.got_subcommand("add")) {
355 return add_opts;
356 } else if (app.got_subcommand("del")) {
357 return del_opts;
358 } else if (app.got_subcommand("start")) {
359 return start_opts;
360 } else if (app.got_subcommand("stop")) {
361 return stop_opts;
362 } else if (app.got_subcommand("list")) {
363 return ListCmd{};
364 } else if (app.got_subcommand("get")) {
365 return get_opts;
366 } else if (app.got_subcommand("start-recovery")) {
367 return start_recovery_opts;
368 } else if (app.got_subcommand("recover")) {
369 return recover_opts;
370 } else if (app.got_subcommand("features")) {
371 return FeaturesCmd{};
372 } else if (app.got_subcommand("quiesce")) {
373 return quiesce_opts;
374 } else {
375 throw std::runtime_error("unhandled subcommand");
376 }
377}
378
379// NOLINTNEXTLINE(bugprone-unsafe-to-allow-exceptions)
380int main(int argc, char *argv[]) noexcept(false) {
381 try {
382 auto cmd = parse_args(argc, argv);
383
384 int ctrl_fd = open("/dev/ublk-control", O_RDWR);
385 if (ctrl_fd < 0) {
386 throw std::system_error(errno, std::generic_category(),
387 "open /dev/ublk-control");
388 }
389 auto d = ublk::detail::defer([&]() noexcept { close(ctrl_fd); });
390
391 ublk::detail::IoLoop loop(condy::RuntimeOptions().enable_sqe128(), 0, 0,
392 {});
393 ex::sync_wait(ex::starts_on(
394 loop.get_scheduler(),
395 // NOLINTNEXTLINE(clang-analyzer-core.CallAndMessage)
396 std::visit([&](const auto &c) { return run_cmd(ctrl_fd, c); },
397 cmd)));
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());
403 return 1;
404 }
405 return 0;
406}
auto start(int control_fd, uint32_t dev_id, int32_t daemon_pid) noexcept
Start the device, served by this daemon, if possible.
Definition daemon.hpp:55
auto stop_dev(int control_fd, uint32_t dev_id) noexcept
Stop a ublk device.
Definition control.hpp:81
auto end_user_recovery(int control_fd, uint32_t dev_id, int32_t daemon_pid) noexcept
End user recovery for a ublk device.
Definition control.hpp:130
auto get_features(int control_fd, uint64_t *features) noexcept
Get the feature flags supported by the ublk driver.
Definition control.hpp:142
auto get_params(int control_fd, uint32_t dev_id, ublk_params *params) noexcept
Get the parameters of a ublk device.
Definition control.hpp:106
auto quiesce_dev(int control_fd, uint32_t dev_id, uint64_t timeout_ms) noexcept
Quiesce a ublk device (UBLK_F_QUIESCE).
Definition control.hpp:179
auto start_dev(int control_fd, uint32_t dev_id, int32_t daemon_pid) noexcept
Start a ublk device.
Definition control.hpp:69
auto del_dev_async(int control_fd, uint32_t dev_id) noexcept
Delete a ublk device asynchronously.
Definition control.hpp:153
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.
Definition control.hpp:34
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).
Definition control.hpp:191
auto start_user_recovery(int control_fd, uint32_t dev_id) noexcept
Start user recovery for a ublk device.
Definition control.hpp:118
auto add_dev(int control_fd, ublksrv_ctrl_dev_info *info) noexcept
Add a new ublk device.
Definition control.hpp:47
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.
Definition control.hpp:21
auto del_dev(int control_fd, uint32_t dev_id) noexcept
Delete a ublk device.
Definition control.hpp:58
Main include file for the ublk-cpp library.