22#include <sys/signalfd.h>
24#include <system_error>
29namespace ex = ublk::detail::ex;
33constexpr size_t SECTOR_SIZE = 512;
34constexpr uint16_t DEFAULT_QUEUE_DEPTH = 64;
35constexpr uint32_t DEFAULT_IO_BUF_BYTES = 512u << 10;
39 uint8_t logical_bs_shift = 9;
40 uint8_t physical_bs_shift = 12;
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);
50void lo_file_size(
int fd, FileInfo &info) {
52 if (fstat(fd, &st) < 0) {
53 throw std::system_error(errno, std::generic_category(),
54 "fstat backing file");
57 if (S_ISBLK(st.st_mode)) {
61 if (ioctl(fd, BLKGETSIZE64, &size) < 0) {
62 throw std::system_error(errno, std::generic_category(),
65 if (ioctl(fd, BLKSSZGET, &lsz) < 0) {
66 throw std::system_error(errno, std::generic_category(),
69 if (ioctl(fd, BLKPBSZGET, &psz) < 0) {
70 throw std::system_error(errno, std::generic_category(),
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));
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;
86 throw std::runtime_error(
"unsupported backing file type");
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 {
94 info.nr_hw_queues = nr_queues;
95 info.queue_depth = depth;
96 info.max_io_buf_bytes = buf_bytes;
100void prep_params(ublk_params ¶ms,
const ublksrv_ctrl_dev_info &info,
101 uint64_t dev_sectors, uint8_t logical_bs_shift,
102 uint8_t physical_bs_shift)
noexcept {
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;
116 static constexpr int BACKING_FD = 1;
120 auto init_queue(uint16_t )
noexcept {
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);
126 throw std::system_error(
127 -r, std::generic_category(),
128 "ublk-loop: register backing file");
133 void destroy_queue(uint16_t )
noexcept {
135 auto &fd_table = condy::current_runtime().fd_table();
136 fd_table.update(BACKING_FD, &fd, 1);
139 ex::task<int32_t> handle_io(
const ublk::IoData &data)
noexcept {
140 uint8_t op = ublksrv_get_op(data.iod);
142 static_cast<uint64_t
>(data.iod->start_sector) * SECTOR_SIZE;
143 uint32_t bytes = data.iod->nr_sectors * SECTOR_SIZE;
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);
161static_assert(ublk::IoHandler<LoopHandler>);
162static_assert(ublk::QueueHandler<LoopHandler>);
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",
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)};
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);
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 "
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 "
189 std::cout <<
" -q <queues> nr_hw_queues (default: 1)\n";
190 std::cout <<
" -d <depth> queue depth, max in-flight io commands "
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";
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;
210 while ((opt = getopt(argc, argv,
"f:n:q:d:b:ph")) != -1) {
213 backing_path = optarg;
216 dev_id = std::stoul(optarg);
219 nr_queues = std::stoul(optarg);
222 queue_depth = std::stoul(optarg);
225 buf_bytes = std::stoul(optarg);
232 print_usage(argv[0]);
233 return opt ==
'h' ? 0 : 1;
237 if (backing_path.empty()) {
238 print_usage(argv[0]);
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");
249 ublk::detail::defer([&]()
noexcept { close(backing_fd); });
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 "
256 std::strerror(errno));
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);
270 sigaddset(&mask, SIGINT);
271 sigaddset(&mask, SIGTERM);
272 sigprocmask(SIG_BLOCK, &mask,
nullptr);
274 int signal_fd = signalfd(-1, &mask, SFD_NONBLOCK);
276 throw std::system_error(errno, std::generic_category(),
"signalfd");
278 auto d = ublk::detail::defer([&]()
noexcept { close(signal_fd); });
280 int ctrl_fd = open(
"/dev/ublk-control", O_RDWR | O_CLOEXEC);
282 throw std::system_error(errno, std::generic_category(),
283 "open /dev/ublk-control");
285 auto d2 = ublk::detail::defer([&]()
noexcept { close(ctrl_fd); });
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);
295 ublksrv_ctrl_dev_info info;
297 LoopHandler handler{backing_fd};
298 ex::inplace_stop_source stop_source;
302 ex::let_value([&]()
noexcept {
304 if (features & UBLK_F_USER_RECOVERY) {
305 flags |= UBLK_F_USER_RECOVERY;
308 flags |= UBLK_F_UNPRIVILEGED_DEV;
310 prep_dev_info(info, dev_id, nr_queues, queue_depth, buf_bytes,
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);
320 ex::let_value([&](
bool)
noexcept {
321 ex::sender
auto daemon =
324 ex::then([&]()
noexcept { stop_source.request_stop(); });
325 ex::sender
auto start =
327 ex::let_value([&]() {
328 return wait_signal(ctrl_fd, signal_fd, info.dev_id,
331 return ex::when_all(daemon, start);
334 [&]()
noexcept {
return ublk::del_dev(ctrl_fd, info.dev_id); });
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());
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.
auto start(int control_fd, uint32_t dev_id, int32_t daemon_pid) noexcept
Start the device, served by this daemon, if possible.
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.
auto run(int control_fd, uint32_t dev_id, Handler &handler, Options options) noexcept
Run the daemon serving a device until it stops.
auto stop_dev(int control_fd, uint32_t dev_id) noexcept
Stop a ublk device.
auto get_features(int control_fd, uint64_t *features) noexcept
Get the feature flags supported by the ublk driver.
auto del_dev(int control_fd, uint32_t dev_id) noexcept
Delete a ublk device.
Main include file for the ublk-cpp library.