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.
- checksums.yaml +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +148 -0
- data/Rakefile +42 -0
- data/examples/encrypted.rb +44 -0
- data/examples/fault_injection.rb +31 -0
- data/examples/http_disk.rb +26 -0
- data/examples/loop.rb +21 -0
- data/examples/ramdisk.rb +26 -0
- data/examples/tracing.rb +25 -0
- data/ext/ublk/compat.h +188 -0
- data/ext/ublk/constants.c +18 -0
- data/ext/ublk/control.c +292 -0
- data/ext/ublk/extconf.rb +16 -0
- data/ext/ublk/internal.h +43 -0
- data/ext/ublk/server.c +413 -0
- data/ext/ublk/ublk.c +34 -0
- data/lib/ublk/control.rb +46 -0
- data/lib/ublk/device.rb +182 -0
- data/lib/ublk/device_info.rb +14 -0
- data/lib/ublk/params.rb +35 -0
- data/lib/ublk/target.rb +42 -0
- data/lib/ublk/version.rb +5 -0
- data/lib/ublk.rb +36 -0
- data/sig/ublk.rbs +96 -0
- data/tools/bench.rb +34 -0
- data/tools/dump_constants.c +19 -0
- data/tools/vm/Makefile +5 -0
- data/tools/vm/config-fragment +4 -0
- data/tools/vm/guest-test.sh +6 -0
- data/tools/vm/run.sh +5 -0
- metadata +75 -0
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(¶ms, 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, ¶ms);
|
|
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
|
+
}
|
data/lib/ublk/control.rb
ADDED
|
@@ -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
|
data/lib/ublk/device.rb
ADDED
|
@@ -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
|