ublk 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/ext/ublk/server.c ADDED
@@ -0,0 +1,413 @@
1
+ #include "internal.h"
2
+
3
+ #include <stdatomic.h>
4
+ #include <poll.h>
5
+ #include <sys/eventfd.h>
6
+ #include <sys/mman.h>
7
+
8
+ #define UBLK_STOP_DATA UINT64_MAX
9
+
10
+ typedef struct {
11
+ struct io_uring ring;
12
+ struct ublksrv_io_desc *descriptors;
13
+ size_t map_size;
14
+ int event_fd;
15
+ int ready;
16
+ } ublk_queue;
17
+
18
+ typedef struct {
19
+ int fd;
20
+ pid_t pid;
21
+ unsigned queues;
22
+ unsigned depth;
23
+ atomic_int closed;
24
+ ublk_queue *queue;
25
+ } ublk_server;
26
+
27
+ static VALUE cServer;
28
+ static ID id_read, id_write, id_flush, id_discard, id_write_zeroes, id_push, id_errno;
29
+ static int submit_ring(struct io_uring *ring);
30
+
31
+ static void queue_close(ublk_queue *queue)
32
+ {
33
+ if (!queue->ready) return;
34
+ io_uring_queue_exit(&queue->ring);
35
+ munmap(queue->descriptors, queue->map_size);
36
+ close(queue->event_fd);
37
+ memset(queue, 0, sizeof(*queue));
38
+ queue->event_fd = -1;
39
+ }
40
+
41
+ static void server_release_resources(ublk_server *server)
42
+ {
43
+ unsigned index;
44
+ for (index = 0; index < server->queues; index++) queue_close(&server->queue[index]);
45
+ if (server->fd >= 0) close(server->fd);
46
+ server->fd = -1;
47
+ }
48
+
49
+ static void server_free(void *pointer)
50
+ {
51
+ ublk_server *server = pointer;
52
+ atomic_store(&server->closed, 1);
53
+ server_release_resources(server);
54
+ xfree(server->queue);
55
+ xfree(server);
56
+ }
57
+
58
+ static size_t server_size(const void *pointer)
59
+ {
60
+ const ublk_server *server = pointer;
61
+ return sizeof(*server) + server->queues * sizeof(ublk_queue);
62
+ }
63
+
64
+ static const rb_data_type_t server_type = {
65
+ "UBLK::Native::Server",
66
+ {NULL, server_free, server_size, NULL, {NULL}},
67
+ NULL, NULL, RUBY_TYPED_FREE_IMMEDIATELY
68
+ };
69
+
70
+ static VALUE server_alloc(VALUE klass)
71
+ {
72
+ ublk_server *server;
73
+ VALUE object = TypedData_Make_Struct(klass, ublk_server, &server_type, server);
74
+ memset(server, 0, sizeof(*server));
75
+ server->fd = -1;
76
+ return object;
77
+ }
78
+
79
+ static VALUE server_initialize(VALUE self, VALUE id, VALUE queues, VALUE depth)
80
+ {
81
+ ublk_server *server;
82
+ char path[64];
83
+ unsigned index;
84
+
85
+ TypedData_Get_Struct(self, ublk_server, &server_type, server);
86
+ server->queues = NUM2UINT(queues);
87
+ server->depth = NUM2UINT(depth);
88
+ if (server->queues == 0 || server->queues > UBLK_MAX_NR_QUEUES) rb_raise(rb_eArgError, "invalid queue count");
89
+ if (server->depth == 0 || server->depth > UBLK_MAX_QUEUE_DEPTH) rb_raise(rb_eArgError, "invalid queue depth");
90
+
91
+ snprintf(path, sizeof(path), "/dev/ublkc%u", NUM2UINT(id));
92
+ server->fd = open(path, O_RDWR | O_CLOEXEC);
93
+ if (server->fd < 0) rb_sys_fail(path);
94
+ server->pid = getpid();
95
+ server->queue = ALLOC_N(ublk_queue, server->queues);
96
+ memset(server->queue, 0, server->queues * sizeof(ublk_queue));
97
+ for (index = 0; index < server->queues; index++) server->queue[index].event_fd = -1;
98
+ return self;
99
+ }
100
+
101
+ static ublk_server *get_server(VALUE self)
102
+ {
103
+ ublk_server *server;
104
+ TypedData_Get_Struct(self, ublk_server, &server_type, server);
105
+ ublk_check_pid(server->pid);
106
+ if (server->fd < 0) rb_raise(rb_eRuntimeError, "closed ublk server");
107
+ return server;
108
+ }
109
+
110
+ static size_t round_up(size_t value, size_t alignment)
111
+ {
112
+ return (value + alignment - 1) / alignment * alignment;
113
+ }
114
+
115
+ static void prep_io_command(ublk_server *server, ublk_queue *queue, unsigned qid,
116
+ unsigned tag, unsigned operation, int result)
117
+ {
118
+ struct io_uring_sqe *sqe = io_uring_get_sqe(&queue->ring);
119
+ struct ublksrv_io_cmd *command;
120
+ if (!sqe) rb_raise(rb_eRuntimeError, "data io_uring is full");
121
+
122
+ ublk_prep_cmd(sqe, server->fd, UBLK_RB_U_IO_CMD(operation));
123
+ command = (struct ublksrv_io_cmd *)&sqe->addr3;
124
+ command->q_id = qid;
125
+ command->tag = tag;
126
+ command->result = result;
127
+ command->addr = 0;
128
+ io_uring_sqe_set_data64(sqe, tag + 1);
129
+ }
130
+
131
+ static void queue_setup(ublk_server *server, unsigned qid)
132
+ {
133
+ ublk_queue *queue = &server->queue[qid];
134
+ struct io_uring_params params;
135
+ struct io_uring_sqe *sqe;
136
+ size_t page_size = (size_t)sysconf(_SC_PAGESIZE);
137
+ size_t max_map_size = round_up(UBLK_MAX_QUEUE_DEPTH * sizeof(struct ublksrv_io_desc), page_size);
138
+ off_t offset = UBLKSRV_CMD_BUF_OFFSET + (off_t)qid * max_map_size;
139
+ unsigned tag;
140
+ int result;
141
+
142
+ queue->map_size = round_up(server->depth * sizeof(struct ublksrv_io_desc), page_size);
143
+ queue->descriptors = mmap(NULL, queue->map_size, PROT_READ, MAP_SHARED, server->fd, offset);
144
+ if (queue->descriptors == MAP_FAILED) rb_sys_fail("mmap(ublk io descriptors)");
145
+
146
+ memset(&params, 0, sizeof(params));
147
+ params.flags = IORING_SETUP_SQE128 | IORING_SETUP_CQSIZE;
148
+ params.cq_entries = server->depth + 1;
149
+ result = io_uring_queue_init_params(server->depth + 1, &queue->ring, &params);
150
+ if (result < 0) {
151
+ munmap(queue->descriptors, queue->map_size);
152
+ rb_syserr_fail(-result, "io_uring_queue_init_params(data)");
153
+ }
154
+
155
+ queue->event_fd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);
156
+ if (queue->event_fd < 0) {
157
+ io_uring_queue_exit(&queue->ring);
158
+ munmap(queue->descriptors, queue->map_size);
159
+ rb_sys_fail("eventfd");
160
+ }
161
+ queue->ready = 1;
162
+
163
+ for (tag = 0; tag < server->depth; tag++)
164
+ prep_io_command(server, queue, qid, tag, UBLK_IO_FETCH_REQ, 0);
165
+ sqe = io_uring_get_sqe(&queue->ring);
166
+ if (!sqe) rb_raise(rb_eRuntimeError, "data io_uring has no stop slot");
167
+ io_uring_prep_poll_add(sqe, queue->event_fd, POLLIN);
168
+ io_uring_sqe_set_data64(sqe, UBLK_STOP_DATA);
169
+
170
+ result = submit_ring(&queue->ring);
171
+ if (result < 0) rb_syserr_fail(-result, "io_uring_submit(fetch)");
172
+ }
173
+
174
+ struct wait_args {
175
+ struct io_uring *ring;
176
+ struct io_uring_cqe *cqe;
177
+ int result;
178
+ };
179
+
180
+ struct submit_args {
181
+ struct io_uring *ring;
182
+ int result;
183
+ };
184
+
185
+ static void *submit_without_gvl(void *pointer)
186
+ {
187
+ struct submit_args *args = pointer;
188
+ do {
189
+ args->result = io_uring_submit(args->ring);
190
+ } while (args->result == -EINTR);
191
+ return NULL;
192
+ }
193
+
194
+ static int submit_ring(struct io_uring *ring)
195
+ {
196
+ struct submit_args args = {ring, 0};
197
+ rb_thread_call_without_gvl(submit_without_gvl, &args, RUBY_UBF_IO, NULL);
198
+ return args.result;
199
+ }
200
+
201
+ static void *wait_without_gvl(void *pointer)
202
+ {
203
+ struct wait_args *args = pointer;
204
+ do {
205
+ args->result = io_uring_wait_cqe(args->ring, &args->cqe);
206
+ } while (args->result == -EINTR);
207
+ return NULL;
208
+ }
209
+
210
+ struct rw_args {
211
+ int fd;
212
+ void *buffer;
213
+ size_t length;
214
+ off_t offset;
215
+ int write;
216
+ ssize_t result;
217
+ int error;
218
+ };
219
+
220
+ static void *rw_without_gvl(void *pointer)
221
+ {
222
+ struct rw_args *args = pointer;
223
+ do {
224
+ args->result = args->write
225
+ ? pwrite(args->fd, args->buffer, args->length, args->offset)
226
+ : pread(args->fd, args->buffer, args->length, args->offset);
227
+ } while (args->result < 0 && errno == EINTR);
228
+ args->error = args->result < 0 ? errno : 0;
229
+ return NULL;
230
+ }
231
+
232
+ struct callback_args {
233
+ VALUE target;
234
+ ID method;
235
+ VALUE first;
236
+ VALUE second;
237
+ int arguments;
238
+ };
239
+
240
+ static VALUE call_target(VALUE pointer)
241
+ {
242
+ struct callback_args *args = (struct callback_args *)pointer;
243
+ VALUE values[2] = {args->first, args->second};
244
+ return rb_funcallv(args->target, args->method, args->arguments, values);
245
+ }
246
+
247
+ static VALUE normalize_integer(VALUE value)
248
+ {
249
+ return INT2NUM(NUM2INT(value));
250
+ }
251
+
252
+ static int exception_result(void)
253
+ {
254
+ VALUE exception = rb_errinfo();
255
+ int result = -EIO;
256
+ if (rb_obj_is_kind_of(exception, rb_eSystemCallError))
257
+ result = -NUM2INT(rb_funcall(exception, id_errno, 0));
258
+ rb_set_errinfo(Qnil);
259
+ return result;
260
+ }
261
+
262
+ static off_t user_copy_offset(unsigned qid, unsigned tag)
263
+ {
264
+ return UBLKSRV_IO_BUF_OFFSET + ((off_t)qid << UBLK_QID_OFF) + ((off_t)tag << UBLK_TAG_OFF);
265
+ }
266
+
267
+ static int process_request(ublk_server *server, unsigned qid, unsigned tag, VALUE target)
268
+ {
269
+ const struct ublksrv_io_desc *descriptor = &server->queue[qid].descriptors[tag];
270
+ unsigned operation = descriptor->op_flags & 0xff;
271
+ size_t length = (size_t)descriptor->nr_sectors << 9;
272
+ uint64_t offset = descriptor->start_sector << 9;
273
+ struct callback_args callback = {target, 0, ULL2NUM(offset), Qnil, 0};
274
+ struct rw_args transfer = {server->fd, NULL, length, user_copy_offset(qid, tag), 0, 0, 0};
275
+ VALUE value;
276
+ int state = 0;
277
+
278
+ if (operation == UBLK_IO_OP_WRITE) {
279
+ callback.method = id_write;
280
+ callback.second = rb_str_new(NULL, length);
281
+ callback.arguments = 2;
282
+ transfer.buffer = RSTRING_PTR(callback.second);
283
+ rb_thread_call_without_gvl(rw_without_gvl, &transfer, RUBY_UBF_IO, NULL);
284
+ if (transfer.result != (ssize_t)length) return transfer.error ? -transfer.error : -EIO;
285
+ } else if (operation == UBLK_IO_OP_READ) {
286
+ callback.method = id_read;
287
+ callback.second = SIZET2NUM(length);
288
+ callback.arguments = 2;
289
+ } else if (operation == UBLK_IO_OP_FLUSH) {
290
+ callback.method = id_flush;
291
+ } else if (operation == UBLK_IO_OP_DISCARD) {
292
+ callback.method = id_discard;
293
+ callback.second = SIZET2NUM(length);
294
+ callback.arguments = 2;
295
+ } else if (operation == UBLK_IO_OP_WRITE_ZEROES) {
296
+ callback.method = id_write_zeroes;
297
+ callback.second = SIZET2NUM(length);
298
+ callback.arguments = 2;
299
+ } else {
300
+ return -EOPNOTSUPP;
301
+ }
302
+
303
+ value = rb_protect(call_target, (VALUE)&callback, &state);
304
+ if (state) return exception_result();
305
+
306
+ if (operation == UBLK_IO_OP_READ) {
307
+ if (!RB_TYPE_P(value, T_STRING) || (size_t)RSTRING_LEN(value) != length) return -EIO;
308
+ value = rb_str_new(RSTRING_PTR(value), RSTRING_LEN(value));
309
+ transfer.buffer = RSTRING_PTR(value);
310
+ transfer.write = 1;
311
+ rb_thread_call_without_gvl(rw_without_gvl, &transfer, RUBY_UBF_IO, NULL);
312
+ RB_GC_GUARD(value);
313
+ return transfer.result == (ssize_t)length ? (int)length : (transfer.error ? -transfer.error : -EIO);
314
+ }
315
+
316
+ if (!RB_INTEGER_TYPE_P(value)) return -EIO;
317
+ if (operation == UBLK_IO_OP_WRITE && !RTEST(rb_equal(value, SIZET2NUM(length)))) return -EIO;
318
+ if (operation == UBLK_IO_OP_WRITE) return (int)length;
319
+ value = rb_protect(normalize_integer, value, &state);
320
+ if (state) return exception_result();
321
+ return NUM2INT(value);
322
+ }
323
+
324
+ static VALUE server_run(VALUE self, VALUE queue_id, VALUE target, VALUE ready_queue)
325
+ {
326
+ ublk_server *server = get_server(self);
327
+ unsigned qid = NUM2UINT(queue_id);
328
+ ublk_queue *queue;
329
+
330
+ if (qid >= server->queues) rb_raise(rb_eArgError, "invalid queue id");
331
+ queue = &server->queue[qid];
332
+ if (queue->ready) rb_raise(rb_eRuntimeError, "queue is already running");
333
+ queue_setup(server, qid);
334
+ rb_funcall(ready_queue, id_push, 1, queue_id);
335
+
336
+ while (!atomic_load(&server->closed)) {
337
+ struct wait_args wait = {&queue->ring, NULL, 0};
338
+ uint64_t data;
339
+ unsigned tag;
340
+ int result;
341
+
342
+ rb_thread_call_without_gvl(wait_without_gvl, &wait, RUBY_UBF_IO, NULL);
343
+ if (wait.result < 0) rb_syserr_fail(-wait.result, "io_uring_wait_cqe");
344
+ data = io_uring_cqe_get_data64(wait.cqe);
345
+ result = wait.cqe->res;
346
+ io_uring_cqe_seen(&queue->ring, wait.cqe);
347
+ if (data == UBLK_STOP_DATA || result == -ENODEV) break;
348
+ if (result < 0) rb_syserr_fail(-result, "ublk fetch request");
349
+
350
+ tag = (unsigned)data - 1;
351
+ if (tag >= server->depth) rb_raise(rb_eRuntimeError, "kernel returned an invalid ublk tag");
352
+ result = process_request(server, qid, tag, target);
353
+ prep_io_command(server, queue, qid, tag, UBLK_IO_COMMIT_AND_FETCH_REQ, result);
354
+ result = submit_ring(&queue->ring);
355
+ if (result < 0) rb_syserr_fail(-result, "io_uring_submit(commit)");
356
+ }
357
+ return Qnil;
358
+ }
359
+
360
+ static VALUE server_close(VALUE self)
361
+ {
362
+ ublk_server *server;
363
+ unsigned index;
364
+ uint64_t value = 1;
365
+ TypedData_Get_Struct(self, ublk_server, &server_type, server);
366
+ if (atomic_exchange(&server->closed, 1)) return Qnil;
367
+ for (index = 0; index < server->queues; index++) {
368
+ if (server->queue[index].event_fd >= 0 &&
369
+ write(server->queue[index].event_fd, &value, sizeof(value)) < 0 && errno != EAGAIN)
370
+ rb_sys_fail("eventfd write");
371
+ }
372
+ return Qnil;
373
+ }
374
+
375
+ static VALUE server_release(VALUE self)
376
+ {
377
+ ublk_server *server;
378
+ TypedData_Get_Struct(self, ublk_server, &server_type, server);
379
+ ublk_check_pid(server->pid);
380
+ if (!atomic_load(&server->closed)) rb_raise(rb_eRuntimeError, "close ublk server before releasing it");
381
+ server_release_resources(server);
382
+ return Qnil;
383
+ }
384
+
385
+ static VALUE native_lock_memory(VALUE self)
386
+ {
387
+ (void)self;
388
+ #ifdef HAVE_MLOCKALL
389
+ if (mlockall(MCL_CURRENT | MCL_FUTURE) < 0) rb_sys_fail("mlockall");
390
+ return Qtrue;
391
+ #else
392
+ rb_raise(rb_eNotImpError, "mlockall is unavailable");
393
+ #endif
394
+ }
395
+
396
+ void ublk_init_server(void)
397
+ {
398
+ id_read = rb_intern("read");
399
+ id_write = rb_intern("write");
400
+ id_flush = rb_intern("flush");
401
+ id_discard = rb_intern("discard");
402
+ id_write_zeroes = rb_intern("write_zeroes");
403
+ id_push = rb_intern("push");
404
+ id_errno = rb_intern("errno");
405
+
406
+ cServer = rb_define_class_under(ublk_native_module, "Server", rb_cObject);
407
+ rb_define_alloc_func(cServer, server_alloc);
408
+ rb_define_method(cServer, "initialize", server_initialize, 3);
409
+ rb_define_method(cServer, "run", server_run, 3);
410
+ rb_define_method(cServer, "close", server_close, 0);
411
+ rb_define_method(cServer, "release", server_release, 0);
412
+ rb_define_singleton_method(ublk_native_module, "lock_memory!", native_lock_memory, 0);
413
+ }
data/ext/ublk/ublk.c ADDED
@@ -0,0 +1,34 @@
1
+ #include "internal.h"
2
+
3
+ #ifdef __linux__
4
+ #include <stddef.h>
5
+ #include <unistd.h>
6
+ #endif
7
+
8
+ VALUE ublk_module;
9
+ VALUE ublk_native_module;
10
+
11
+ static VALUE native_layout(VALUE self)
12
+ {
13
+ VALUE result = rb_hash_new();
14
+ (void)self;
15
+ #ifdef __linux__
16
+ rb_hash_aset(result, ID2SYM(rb_intern("ctrl_cmd_size")), SIZET2NUM(sizeof(struct ublk_rb_ctrl_cmd)));
17
+ rb_hash_aset(result, ID2SYM(rb_intern("dev_info_size")), SIZET2NUM(sizeof(struct ublk_rb_dev_info)));
18
+ rb_hash_aset(result, ID2SYM(rb_intern("params_size")), SIZET2NUM(sizeof(struct ublk_rb_params)));
19
+ rb_hash_aset(result, ID2SYM(rb_intern("io_desc_size")), SIZET2NUM(sizeof(struct ublksrv_io_desc)));
20
+ rb_hash_aset(result, ID2SYM(rb_intern("io_desc_start_sector_offset")), SIZET2NUM(offsetof(struct ublksrv_io_desc, start_sector)));
21
+ #endif
22
+ return result;
23
+ }
24
+
25
+ void Init_ublk(void)
26
+ {
27
+ ublk_module = rb_define_module("UBLK");
28
+ ublk_native_module = rb_define_module_under(ublk_module, "Native");
29
+ rb_define_singleton_method(ublk_native_module, "supported?", ublk_native_supported, 0);
30
+ rb_define_singleton_method(ublk_native_module, "layout", native_layout, 0);
31
+ ublk_init_constants();
32
+ ublk_init_control();
33
+ ublk_init_server();
34
+ }
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module UBLK
4
+ class Control
5
+ SYSFS = "/sys/class/ublk-char"
6
+
7
+ def initialize
8
+ UBLK.ensure_supported!
9
+ @native = Native::Control.new
10
+ end
11
+
12
+ def add_dev(id: nil, queues: 1, depth: 128, max_io_bytes: 512 * 1024, recovery: false)
13
+ raise ArgumentError, "id must be nil or a non-negative Integer" unless id.nil? || (id.is_a?(Integer) && id >= 0)
14
+ raise ArgumentError, "queues must be between 1 and 4096" unless queues.is_a?(Integer) && queues.between?(1, 4096)
15
+ raise ArgumentError, "depth must be between 1 and 4096" unless depth.is_a?(Integer) && depth.between?(1, 4096)
16
+ raise ArgumentError, "max_io_bytes must be a positive multiple of 512" unless max_io_bytes.is_a?(Integer) && max_io_bytes.positive? && (max_io_bytes % 512).zero?
17
+ raise ArgumentError, "max_io_bytes must not exceed 32 MiB" if max_io_bytes > 32 * 1024 * 1024
18
+
19
+ DeviceInfo.from_native(@native.add_dev(id, queues, depth, max_io_bytes, recovery))
20
+ end
21
+
22
+ def set_params(id, params)
23
+ @native.set_params(id, params.size, params.logical_block_size,
24
+ params.physical_block_size, params.max_io_bytes,
25
+ params.read_only, params.rotational, params.discard)
26
+ params
27
+ end
28
+
29
+ def start_dev(id, pid: Process.pid) = @native.start_dev(id, pid)
30
+ def stop_dev(id) = @native.stop_dev(id)
31
+ def del_dev(id) = @native.del_dev(id)
32
+ def start_user_recovery(id) = @native.start_user_recovery(id)
33
+ def end_user_recovery(id, pid: Process.pid) = @native.end_user_recovery(id, pid)
34
+ def get_dev_info(id) = DeviceInfo.from_native(@native.get_dev_info(id))
35
+ def close = @native.close
36
+
37
+ def list
38
+ Dir.glob("#{SYSFS}/ublkc*").filter_map do |path|
39
+ id = File.basename(path).delete_prefix("ublkc")
40
+ get_dev_info(Integer(id, 10))
41
+ rescue SystemCallError, ArgumentError
42
+ nil
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,182 @@
1
+ # frozen_string_literal: true
2
+
3
+ module UBLK
4
+ class Device
5
+ @owned = []
6
+
7
+ at_exit do
8
+ @owned&.dup&.each do |device|
9
+ device.delete
10
+ rescue Exception
11
+ nil
12
+ end
13
+ end
14
+
15
+ class << self
16
+ attr_reader :owned
17
+ end
18
+
19
+ attr_reader :target, :info, :params
20
+
21
+ def self.create(target, id: nil, queues: target.queues, depth: target.depth,
22
+ max_io_bytes: 512 * 1024, recovery: false, mlock: true)
23
+ control = Control.new
24
+ info = control.add_dev(id:, queues:, depth:, max_io_bytes:, recovery:)
25
+ params = Params.from_target(target, max_io_bytes:)
26
+ control.set_params(info.id, params)
27
+ new(target, control, info, params, mlock:).tap { |device| owned << device }
28
+ rescue Exception
29
+ begin
30
+ control&.del_dev(info.id) if info
31
+ rescue SystemCallError, Error
32
+ nil
33
+ end
34
+ control&.close
35
+ raise
36
+ end
37
+
38
+ def self.list
39
+ control = Control.new
40
+ control.list
41
+ ensure
42
+ control&.close
43
+ end
44
+
45
+ def self.recover(target, id:, mlock: true)
46
+ control = Control.new
47
+ deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + 10
48
+ begin
49
+ control.start_user_recovery(id)
50
+ rescue Errno::EBUSY
51
+ raise if Process.clock_gettime(Process::CLOCK_MONOTONIC) >= deadline
52
+
53
+ sleep 0.01
54
+ retry
55
+ end
56
+ info = control.get_dev_info(id)
57
+ params = Params.from_target(target, max_io_bytes: info.max_io_bytes)
58
+ new(target, control, info, params, mlock:, recovering: true).tap { |device| owned << device }
59
+ rescue Exception
60
+ control&.close
61
+ raise
62
+ end
63
+
64
+ def self.delete_all!
65
+ control = Control.new
66
+ control.list.each { |item| control.del_dev(item.id) }
67
+ ensure
68
+ control&.close
69
+ end
70
+
71
+ def initialize(target, control, info, params, mlock: true, recovering: false)
72
+ @target = target
73
+ @control = control
74
+ @info = info
75
+ @params = params
76
+ @mlock = mlock
77
+ @recovering = recovering
78
+ @started = false
79
+ @deleted = false
80
+ end
81
+
82
+ def id = info.id
83
+ def path = info.path
84
+ def char_path = info.char_path
85
+
86
+ def start(threads: :auto)
87
+ raise DeviceError, "device has been deleted" if @deleted
88
+ return self if @started
89
+
90
+ count = threads == :auto ? info.queues : Integer(threads)
91
+ raise ArgumentError, "threads must equal the hardware queue count" unless count == info.queues
92
+
93
+ Native.lock_memory! if @mlock
94
+ @server = Native::Server.new(id, info.queues, info.depth)
95
+ ready = Queue.new
96
+ @workers = count.times.map do |queue|
97
+ Thread.new do
98
+ @server.run(queue, target, ready)
99
+ rescue Exception => error
100
+ ready << error
101
+ raise
102
+ end.tap { |worker| worker.report_on_exception = false }
103
+ end
104
+ count.times do
105
+ result = ready.pop
106
+ raise result if result.is_a?(Exception)
107
+ end
108
+ if @recovering
109
+ @control.end_user_recovery(id)
110
+ @recovering = false
111
+ else
112
+ @control.start_dev(id)
113
+ end
114
+ @started = true
115
+ self
116
+ rescue Exception
117
+ @server&.close
118
+ join_workers(@workers, suppress: true)
119
+ @server&.release
120
+ @server = @workers = nil
121
+ raise
122
+ end
123
+
124
+ def stop
125
+ return self unless @started || @server
126
+
127
+ @control.stop_dev(id) if @started
128
+ self
129
+ ensure
130
+ @started = false
131
+ server, workers = @server, @workers
132
+ @server = @workers = nil
133
+ server&.close
134
+ begin
135
+ join_workers(workers)
136
+ ensure
137
+ server&.release
138
+ end
139
+ end
140
+
141
+ def delete
142
+ return self if @deleted
143
+
144
+ worker_error = nil
145
+ begin
146
+ stop
147
+ rescue Exception => error
148
+ worker_error = error
149
+ end
150
+ @control.del_dev(id)
151
+ @deleted = true
152
+ self.class.owned.delete(self)
153
+ raise worker_error if worker_error
154
+
155
+ self
156
+ ensure
157
+ @control.close if @deleted
158
+ end
159
+
160
+ def run(threads: :auto)
161
+ raise UnsupportedError, "data plane is unavailable in this build" unless Native.const_defined?(:Server)
162
+
163
+ start(threads:)
164
+ @workers.each(&:join)
165
+ self
166
+ ensure
167
+ stop if @started
168
+ end
169
+
170
+ private
171
+
172
+ def join_workers(workers, suppress: false)
173
+ error = nil
174
+ workers&.each do |worker|
175
+ worker.join unless worker == Thread.current
176
+ rescue Exception => caught
177
+ error ||= caught
178
+ end
179
+ raise error if error && !suppress
180
+ end
181
+ end
182
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module UBLK
4
+ DeviceInfo = Data.define(
5
+ :id, :queues, :depth, :state, :max_io_bytes, :pid, :flags, :owner_uid, :owner_gid
6
+ ) do
7
+ def path = "/dev/ublkb#{id}"
8
+ def char_path = "/dev/ublkc#{id}"
9
+
10
+ def self.from_native(values)
11
+ new(*values)
12
+ end
13
+ end
14
+ end