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