ublk-cpp v0.1
A ublk library for std::execution.
Loading...
Searching...
No Matches
loop.cpp
Go to the documentation of this file.
1
7
8#include <bit>
9#include <cerrno>
10#include <condy.hpp>
11#include <csignal>
12#include <cstdint>
13#include <cstring>
14#include <exception>
15#include <fcntl.h>
16#include <format>
17#include <getopt.h>
18#include <iostream>
19#include <linux/fs.h>
20#include <string>
21#include <sys/ioctl.h>
22#include <sys/signalfd.h>
23#include <sys/stat.h>
24#include <system_error>
25#include <thread>
26#include <ublk.hpp>
27#include <unistd.h>
28
29namespace ex = ublk::detail::ex;
30
31namespace {
32
33constexpr size_t SECTOR_SIZE = 512;
34constexpr uint16_t DEFAULT_QUEUE_DEPTH = 64;
35constexpr uint32_t DEFAULT_IO_BUF_BYTES = 512u << 10;
36
37struct FileInfo {
38 uint64_t size = 0;
39 uint8_t logical_bs_shift = 9;
40 uint8_t physical_bs_shift = 12;
41};
42
43uint8_t bs_shift_of(uint64_t v) noexcept {
44 if (v > 0 && (v & (v - 1)) == 0) {
45 return static_cast<uint8_t>(std::bit_width(v) - 1);
46 }
47 return 9;
48}
49
50void lo_file_size(int fd, FileInfo &info) {
51 struct stat st;
52 if (fstat(fd, &st) < 0) {
53 throw std::system_error(errno, std::generic_category(),
54 "fstat backing file");
55 }
56
57 if (S_ISBLK(st.st_mode)) {
58 uint64_t size = 0;
59 int lsz = 0;
60 int psz = 0;
61 if (ioctl(fd, BLKGETSIZE64, &size) < 0) {
62 throw std::system_error(errno, std::generic_category(),
63 "BLKGETSIZE64");
64 }
65 if (ioctl(fd, BLKSSZGET, &lsz) < 0) {
66 throw std::system_error(errno, std::generic_category(),
67 "BLKSSZGET");
68 }
69 if (ioctl(fd, BLKPBSZGET, &psz) < 0) {
70 throw std::system_error(errno, std::generic_category(),
71 "BLKPBSZGET");
72 }
73 info.size = size;
74 info.logical_bs_shift = bs_shift_of(static_cast<uint64_t>(lsz));
75 info.physical_bs_shift = bs_shift_of(static_cast<uint64_t>(psz));
76 return;
77 }
78
79 if (S_ISREG(st.st_mode)) {
80 info.size = static_cast<uint64_t>(st.st_size);
81 info.logical_bs_shift = 9;
82 info.physical_bs_shift = 12;
83 return;
84 }
85
86 throw std::runtime_error("unsupported backing file type");
87}
88
89void prep_dev_info(ublksrv_ctrl_dev_info &info, uint32_t dev_id,
90 uint16_t nr_queues, uint16_t depth, uint32_t buf_bytes,
91 uint64_t flags) noexcept {
92 info = {};
93 info.dev_id = dev_id;
94 info.nr_hw_queues = nr_queues;
95 info.queue_depth = depth;
96 info.max_io_buf_bytes = buf_bytes;
97 info.flags = flags;
98}
99
100void prep_params(ublk_params &params, const ublksrv_ctrl_dev_info &info,
101 uint64_t dev_sectors, uint8_t logical_bs_shift,
102 uint8_t physical_bs_shift) noexcept {
103 params = {};
104 params.len = sizeof(params);
105 params.types = UBLK_PARAM_TYPE_BASIC;
106 params.basic.attrs = UBLK_ATTR_VOLATILE_CACHE;
107 params.basic.logical_bs_shift = logical_bs_shift;
108 params.basic.physical_bs_shift = physical_bs_shift;
109 params.basic.io_opt_shift = 12;
110 params.basic.io_min_shift = 9;
111 params.basic.max_sectors = info.max_io_buf_bytes >> 9;
112 params.basic.dev_sectors = dev_sectors;
113}
114
115struct LoopHandler {
116 static constexpr int BACKING_FD = 1;
117
118 int backing_fd = -1;
119
120 auto init_queue(uint16_t /*q_id*/) noexcept {
121 int fd = backing_fd;
122 return ex::just() | ex::then([fd]() {
123 auto &fd_table = condy::current_runtime().fd_table();
124 int r = fd_table.update(BACKING_FD, &fd, 1);
125 if (r != 1) {
126 throw std::system_error(
127 -r, std::generic_category(),
128 "ublk-loop: register backing file");
129 }
130 });
131 }
132
133 void destroy_queue(uint16_t /*q_id*/) noexcept {
134 int fd = -1;
135 auto &fd_table = condy::current_runtime().fd_table();
136 fd_table.update(BACKING_FD, &fd, 1);
137 }
138
139 ex::task<int32_t> handle_io(const ublk::IoData &data) noexcept {
140 uint8_t op = ublksrv_get_op(data.iod);
141 uint64_t off =
142 static_cast<uint64_t>(data.iod->start_sector) * SECTOR_SIZE;
143 uint32_t bytes = data.iod->nr_sectors * SECTOR_SIZE;
144
145 switch (op) {
146 case UBLK_IO_OP_READ:
147 co_return co_await condy::async_read(
148 condy::fixed(BACKING_FD), condy::buffer(data.buf, bytes), off);
149 case UBLK_IO_OP_WRITE:
150 co_return co_await condy::async_write(
151 condy::fixed(BACKING_FD), condy::buffer(data.buf, bytes), off);
152 case UBLK_IO_OP_FLUSH:
153 co_return co_await condy::async_fsync(condy::fixed(BACKING_FD),
154 IORING_FSYNC_DATASYNC);
155 default:
156 co_return -EINVAL;
157 }
158 }
159};
160
161static_assert(ublk::IoHandler<LoopHandler>);
162static_assert(ublk::QueueHandler<LoopHandler>);
163
164ex::task<void> wait_signal(int ctrl_fd, int signal_fd, uint32_t dev_id,
165 ex::inplace_stop_source &source) {
166 std::cout << std::format("ublk-loop: ublk device {} is running...\n",
167 dev_id);
168 auto parent_token = co_await ex::read_env(ex::get_stop_token);
169 auto stop_request = [&]() noexcept { source.request_stop(); };
170 ex::inplace_stop_callback<decltype(stop_request)> cb{
171 parent_token, std::move(stop_request)};
172 signalfd_siginfo si;
173 co_await (condy::async_read(signal_fd, condy::buffer(&si, sizeof(si)), 0) |
174 ex::write_env(ex::prop{ex::get_stop_token, source.get_token()}));
175 std::cout << std::format(
176 "ublk-loop: received signal {}, shutting down...\n", si.ssi_signo);
177 co_await ublk::stop_dev(ctrl_fd, dev_id);
178}
179
180void print_usage(const char *prog) {
181 std::cout << std::format("Usage: {} [OPTIONS] -f <backing_file>\n", prog);
182 std::cout << "Run a ublk loop device backed by a regular file or block "
183 "device.\n";
184 std::cout << '\n';
185 std::cout << "Options:\n";
186 std::cout << " -f <file> backing file or block device (required)\n";
187 std::cout << " -n <dev_id> ublk device id, -1: auto-allocation "
188 "(default)\n";
189 std::cout << " -q <queues> nr_hw_queues (default: 1)\n";
190 std::cout << " -d <depth> queue depth, max in-flight io commands "
191 "(default: 64)\n";
192 std::cout << " -b <bytes> io buffer size (default: 524288)\n";
193 std::cout << " -p enable UBLK_F_UNPRIVILEGED_DEV "
194 "(unprivileged mode)\n";
195 std::cout << " -h show this help and exit\n";
196}
197
198} // namespace
199
200// NOLINTNEXTLINE(bugprone-unsafe-to-allow-exceptions)
201int main(int argc, char *argv[]) noexcept(false) {
202 uint32_t dev_id = -1;
203 uint16_t nr_queues = 1;
204 uint16_t queue_depth = DEFAULT_QUEUE_DEPTH;
205 uint32_t buf_bytes = DEFAULT_IO_BUF_BYTES;
206 bool unprivileged = false;
207 std::string backing_path;
208
209 int opt;
210 while ((opt = getopt(argc, argv, "f:n:q:d:b:ph")) != -1) {
211 switch (opt) {
212 case 'f':
213 backing_path = optarg;
214 break;
215 case 'n':
216 dev_id = std::stoul(optarg);
217 break;
218 case 'q':
219 nr_queues = std::stoul(optarg);
220 break;
221 case 'd':
222 queue_depth = std::stoul(optarg);
223 break;
224 case 'b':
225 buf_bytes = std::stoul(optarg);
226 break;
227 case 'p':
228 unprivileged = true;
229 break;
230 case 'h':
231 default:
232 print_usage(argv[0]);
233 return opt == 'h' ? 0 : 1;
234 }
235 }
236
237 if (backing_path.empty()) {
238 print_usage(argv[0]);
239 return 1;
240 }
241
242 try {
243 int backing_fd = open(backing_path.c_str(), O_RDWR);
244 if (backing_fd < 0) {
245 throw std::system_error(errno, std::generic_category(),
246 "open backing file");
247 }
248 auto d_backing =
249 ublk::detail::defer([&]() noexcept { close(backing_fd); });
250
251 int flags = fcntl(backing_fd, F_GETFL);
252 if (flags >= 0 && fcntl(backing_fd, F_SETFL, flags | O_DIRECT) < 0) {
253 std::cerr << std::format(
254 "ublk-loop: failed to set O_DIRECT on backing "
255 "file: {}\n",
256 std::strerror(errno));
257 }
258
259 FileInfo file_info;
260 lo_file_size(backing_fd, file_info);
261 uint64_t dev_sectors = file_info.size / SECTOR_SIZE;
262 std::cout << std::format("ublk-loop: backing file {} ({} bytes, {}"
263 " sectors, logical {}B, physical {}B)\n",
264 backing_path, file_info.size, dev_sectors,
265 1u << file_info.logical_bs_shift,
266 1u << file_info.physical_bs_shift);
267
268 sigset_t mask;
269 sigemptyset(&mask);
270 sigaddset(&mask, SIGINT);
271 sigaddset(&mask, SIGTERM);
272 sigprocmask(SIG_BLOCK, &mask, nullptr);
273
274 int signal_fd = signalfd(-1, &mask, SFD_NONBLOCK);
275 if (signal_fd < 0) {
276 throw std::system_error(errno, std::generic_category(), "signalfd");
277 }
278 auto d = ublk::detail::defer([&]() noexcept { close(signal_fd); });
279
280 int ctrl_fd = open("/dev/ublk-control", O_RDWR | O_CLOEXEC);
281 if (ctrl_fd < 0) {
282 throw std::system_error(errno, std::generic_category(),
283 "open /dev/ublk-control");
284 }
285 auto d2 = ublk::detail::defer([&]() noexcept { close(ctrl_fd); });
286
287 condy::RuntimeOptions options;
288 options.enable_sqe128();
289 condy::Runtime runtime(options);
290 std::jthread loop([&]() { runtime.run(); });
291 auto d3 = ublk::detail::defer([&]() noexcept { runtime.allow_exit(); });
292 ex::scheduler auto sched = condy::get_scheduler(runtime);
293
294 uint64_t features;
295 ublksrv_ctrl_dev_info info;
296 ublk_params params;
297 LoopHandler handler{backing_fd};
298 ex::inplace_stop_source stop_source;
299
300 ex::sender auto s =
301 ublk::get_features(ctrl_fd, &features) |
302 ex::let_value([&]() noexcept {
303 uint64_t flags = 0;
304 if (features & UBLK_F_USER_RECOVERY) {
305 flags |= UBLK_F_USER_RECOVERY;
306 }
307 if (unprivileged) {
308 flags |= UBLK_F_UNPRIVILEGED_DEV;
309 }
310 prep_dev_info(info, dev_id, nr_queues, queue_depth, buf_bytes,
311 flags);
312 return ublk::daemon::setup(ctrl_fd, &info);
313 }) |
314 ex::let_value([&](bool) noexcept {
315 prep_params(params, info, dev_sectors,
316 file_info.logical_bs_shift,
317 file_info.physical_bs_shift);
318 return ublk::daemon::configure(ctrl_fd, info.dev_id, &params);
319 }) |
320 ex::let_value([&](bool) noexcept {
321 ex::sender auto daemon =
322 ublk::daemon::run(ctrl_fd, info.dev_id, handler,
323 {.nr_files = 2}) |
324 ex::then([&]() noexcept { stop_source.request_stop(); });
325 ex::sender auto start =
326 ublk::daemon::start(ctrl_fd, info.dev_id, getpid()) |
327 ex::let_value([&]() {
328 return wait_signal(ctrl_fd, signal_fd, info.dev_id,
329 stop_source);
330 });
331 return ex::when_all(daemon, start);
332 }) |
333 ex::let_value(
334 [&]() noexcept { return ublk::del_dev(ctrl_fd, info.dev_id); });
335
336 ex::sync_wait(ex::starts_on(sched, s));
337 } catch (const std::system_error &e) {
338 std::cerr << std::format("ublk-loop: {}\n", e.what());
339 return e.code().value();
340 } catch (const std::exception &e) {
341 std::cerr << std::format("ublk-loop: {}\n", e.what());
342 return 1;
343 }
344
345 return 0;
346}
auto setup(int control_fd, ublksrv_ctrl_dev_info *info) noexcept
Ensure the device described by info has been created and return its current information.
Definition daemon.hpp:27
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 configure(int control_fd, uint32_t dev_id, ublk_params *params) noexcept
Ensure the device has been configured and return its current configuration in params.
Definition daemon.hpp:42
auto run(int control_fd, uint32_t dev_id, Handler &handler, Options options) noexcept
Run the daemon serving a device until it stops.
Definition daemon.hpp:82
auto stop_dev(int control_fd, uint32_t dev_id) noexcept
Stop a ublk device.
Definition control.hpp:81
auto get_features(int control_fd, uint64_t *features) noexcept
Get the feature flags supported by the ublk driver.
Definition control.hpp:142
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.