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/control.c
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
#include "internal.h"
|
|
2
|
+
|
|
3
|
+
#include <limits.h>
|
|
4
|
+
|
|
5
|
+
typedef struct {
|
|
6
|
+
int fd;
|
|
7
|
+
pid_t pid;
|
|
8
|
+
int ring_ready;
|
|
9
|
+
struct io_uring ring;
|
|
10
|
+
} ublk_control;
|
|
11
|
+
|
|
12
|
+
static VALUE cControl;
|
|
13
|
+
|
|
14
|
+
static void control_free(void *pointer)
|
|
15
|
+
{
|
|
16
|
+
ublk_control *control = pointer;
|
|
17
|
+
if (control->ring_ready) io_uring_queue_exit(&control->ring);
|
|
18
|
+
if (control->fd >= 0) close(control->fd);
|
|
19
|
+
xfree(control);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
static size_t control_size(const void *pointer)
|
|
23
|
+
{
|
|
24
|
+
(void)pointer;
|
|
25
|
+
return sizeof(ublk_control);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
static const rb_data_type_t control_type = {
|
|
29
|
+
"UBLK::Native::Control",
|
|
30
|
+
{NULL, control_free, control_size, NULL, {NULL}},
|
|
31
|
+
NULL, NULL, RUBY_TYPED_FREE_IMMEDIATELY
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
static VALUE control_alloc(VALUE klass)
|
|
35
|
+
{
|
|
36
|
+
ublk_control *control;
|
|
37
|
+
VALUE object = TypedData_Make_Struct(klass, ublk_control, &control_type, control);
|
|
38
|
+
memset(control, 0, sizeof(*control));
|
|
39
|
+
control->fd = -1;
|
|
40
|
+
return object;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
static VALUE control_initialize(VALUE self)
|
|
44
|
+
{
|
|
45
|
+
ublk_control *control;
|
|
46
|
+
struct io_uring_params params;
|
|
47
|
+
int result;
|
|
48
|
+
|
|
49
|
+
TypedData_Get_Struct(self, ublk_control, &control_type, control);
|
|
50
|
+
control->fd = open("/dev/ublk-control", O_RDWR | O_CLOEXEC);
|
|
51
|
+
if (control->fd < 0) rb_sys_fail("open(/dev/ublk-control)");
|
|
52
|
+
|
|
53
|
+
memset(¶ms, 0, sizeof(params));
|
|
54
|
+
params.flags = IORING_SETUP_SQE128;
|
|
55
|
+
result = io_uring_queue_init_params(8, &control->ring, ¶ms);
|
|
56
|
+
if (result < 0) {
|
|
57
|
+
close(control->fd);
|
|
58
|
+
control->fd = -1;
|
|
59
|
+
rb_syserr_fail(-result, "io_uring_queue_init_params");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
control->ring_ready = 1;
|
|
63
|
+
control->pid = getpid();
|
|
64
|
+
return self;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
struct control_submit_args {
|
|
68
|
+
struct io_uring *ring;
|
|
69
|
+
int result;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
static void *control_submit_without_gvl(void *pointer)
|
|
73
|
+
{
|
|
74
|
+
struct control_submit_args *args = pointer;
|
|
75
|
+
struct io_uring_cqe *cqe;
|
|
76
|
+
|
|
77
|
+
do {
|
|
78
|
+
args->result = io_uring_submit_and_wait(args->ring, 1);
|
|
79
|
+
} while (args->result == -EINTR);
|
|
80
|
+
if (args->result < 0) return NULL;
|
|
81
|
+
|
|
82
|
+
args->result = io_uring_peek_cqe(args->ring, &cqe);
|
|
83
|
+
if (args->result == 0) {
|
|
84
|
+
args->result = cqe->res;
|
|
85
|
+
io_uring_cqe_seen(args->ring, cqe);
|
|
86
|
+
}
|
|
87
|
+
return NULL;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
static int control_submit(ublk_control *control, unsigned op, uint32_t id,
|
|
91
|
+
void *buffer, uint16_t length, uint64_t data)
|
|
92
|
+
{
|
|
93
|
+
struct io_uring_sqe *sqe = io_uring_get_sqe(&control->ring);
|
|
94
|
+
struct ublk_rb_ctrl_cmd *command;
|
|
95
|
+
struct control_submit_args args = {&control->ring, 0};
|
|
96
|
+
|
|
97
|
+
if (!sqe) rb_raise(rb_eRuntimeError, "control io_uring is full");
|
|
98
|
+
ublk_prep_cmd(sqe, control->fd, op);
|
|
99
|
+
command = (struct ublk_rb_ctrl_cmd *)&sqe->addr3;
|
|
100
|
+
command->dev_id = id;
|
|
101
|
+
command->queue_id = UINT16_MAX;
|
|
102
|
+
command->addr = (uint64_t)(uintptr_t)buffer;
|
|
103
|
+
command->len = length;
|
|
104
|
+
command->data[0] = data;
|
|
105
|
+
io_uring_sqe_set_data64(sqe, 1);
|
|
106
|
+
|
|
107
|
+
rb_thread_call_without_gvl(control_submit_without_gvl, &args, RUBY_UBF_IO, NULL);
|
|
108
|
+
return args.result;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
VALUE ublk_native_supported(VALUE self)
|
|
112
|
+
{
|
|
113
|
+
ublk_control control;
|
|
114
|
+
struct io_uring_params params;
|
|
115
|
+
uint64_t features = 0;
|
|
116
|
+
int result;
|
|
117
|
+
(void)self;
|
|
118
|
+
|
|
119
|
+
memset(&control, 0, sizeof(control));
|
|
120
|
+
control.fd = open("/dev/ublk-control", O_RDWR | O_CLOEXEC);
|
|
121
|
+
if (control.fd < 0) return Qfalse;
|
|
122
|
+
memset(¶ms, 0, sizeof(params));
|
|
123
|
+
params.flags = IORING_SETUP_SQE128;
|
|
124
|
+
result = io_uring_queue_init_params(2, &control.ring, ¶ms);
|
|
125
|
+
if (result < 0) {
|
|
126
|
+
close(control.fd);
|
|
127
|
+
return Qfalse;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
result = control_submit(&control, UBLK_RB_U_READ_CMD(UBLK_CMD_GET_FEATURES), 0,
|
|
131
|
+
&features, sizeof(features), 0);
|
|
132
|
+
io_uring_queue_exit(&control.ring);
|
|
133
|
+
close(control.fd);
|
|
134
|
+
return result >= 0 && (features & UBLK_F_USER_COPY) ? Qtrue : Qfalse;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
static ublk_control *get_control(VALUE self)
|
|
138
|
+
{
|
|
139
|
+
ublk_control *control;
|
|
140
|
+
TypedData_Get_Struct(self, ublk_control, &control_type, control);
|
|
141
|
+
ublk_check_pid(control->pid);
|
|
142
|
+
if (control->fd < 0) rb_raise(rb_eRuntimeError, "closed ublk control");
|
|
143
|
+
return control;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
static VALUE control_close(VALUE self)
|
|
147
|
+
{
|
|
148
|
+
ublk_control *control;
|
|
149
|
+
TypedData_Get_Struct(self, ublk_control, &control_type, control);
|
|
150
|
+
ublk_check_pid(control->pid);
|
|
151
|
+
if (control->ring_ready) {
|
|
152
|
+
io_uring_queue_exit(&control->ring);
|
|
153
|
+
control->ring_ready = 0;
|
|
154
|
+
}
|
|
155
|
+
if (control->fd >= 0) {
|
|
156
|
+
close(control->fd);
|
|
157
|
+
control->fd = -1;
|
|
158
|
+
}
|
|
159
|
+
return Qnil;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
static VALUE info_to_array(const struct ublk_rb_dev_info *info)
|
|
163
|
+
{
|
|
164
|
+
return rb_ary_new_from_args(9,
|
|
165
|
+
UINT2NUM(info->dev_id), UINT2NUM(info->nr_hw_queues), UINT2NUM(info->queue_depth),
|
|
166
|
+
UINT2NUM(info->state), UINT2NUM(info->max_io_buf_bytes), INT2NUM(info->ublksrv_pid),
|
|
167
|
+
ULL2NUM(info->flags), UINT2NUM(info->owner_uid), UINT2NUM(info->owner_gid));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
static VALUE control_add_dev(VALUE self, VALUE id, VALUE queues, VALUE depth,
|
|
171
|
+
VALUE max_io_bytes, VALUE recovery)
|
|
172
|
+
{
|
|
173
|
+
ublk_control *control = get_control(self);
|
|
174
|
+
struct ublk_rb_dev_info info;
|
|
175
|
+
int result;
|
|
176
|
+
|
|
177
|
+
memset(&info, 0, sizeof(info));
|
|
178
|
+
info.dev_id = NIL_P(id) ? UINT32_MAX : NUM2UINT(id);
|
|
179
|
+
info.nr_hw_queues = NUM2UINT(queues);
|
|
180
|
+
info.queue_depth = NUM2UINT(depth);
|
|
181
|
+
info.max_io_buf_bytes = NUM2UINT(max_io_bytes);
|
|
182
|
+
info.flags = UBLK_F_USER_COPY | UBLK_F_CMD_IOCTL_ENCODE;
|
|
183
|
+
if (RTEST(recovery)) info.flags |= UBLK_F_USER_RECOVERY;
|
|
184
|
+
|
|
185
|
+
result = control_submit(control, UBLK_RB_U_RDWR_CMD(UBLK_CMD_ADD_DEV), info.dev_id,
|
|
186
|
+
&info, sizeof(info), 0);
|
|
187
|
+
ublk_raise_result(result, "UBLK_U_CMD_ADD_DEV");
|
|
188
|
+
return info_to_array(&info);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
static unsigned block_shift(VALUE value)
|
|
192
|
+
{
|
|
193
|
+
unsigned size = NUM2UINT(value);
|
|
194
|
+
unsigned shift = 0;
|
|
195
|
+
while ((1U << shift) < size) shift++;
|
|
196
|
+
return shift;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
static VALUE control_set_params(VALUE self, VALUE id, VALUE size,
|
|
200
|
+
VALUE logical, VALUE physical, VALUE max_io,
|
|
201
|
+
VALUE read_only, VALUE rotational, VALUE discard)
|
|
202
|
+
{
|
|
203
|
+
ublk_control *control = get_control(self);
|
|
204
|
+
struct ublk_rb_params params;
|
|
205
|
+
int result;
|
|
206
|
+
|
|
207
|
+
memset(¶ms, 0, sizeof(params));
|
|
208
|
+
params.len = sizeof(params);
|
|
209
|
+
params.types = UBLK_PARAM_TYPE_BASIC;
|
|
210
|
+
params.basic.logical_bs_shift = block_shift(logical);
|
|
211
|
+
params.basic.physical_bs_shift = block_shift(physical);
|
|
212
|
+
params.basic.io_min_shift = params.basic.logical_bs_shift;
|
|
213
|
+
params.basic.max_sectors = NUM2ULL(max_io) >> 9;
|
|
214
|
+
params.basic.dev_sectors = NUM2ULL(size) >> 9;
|
|
215
|
+
params.basic.attrs = UBLK_ATTR_VOLATILE_CACHE;
|
|
216
|
+
if (RTEST(read_only)) params.basic.attrs |= UBLK_ATTR_READ_ONLY;
|
|
217
|
+
if (RTEST(rotational)) params.basic.attrs |= UBLK_ATTR_ROTATIONAL;
|
|
218
|
+
if (RTEST(discard)) {
|
|
219
|
+
params.types |= UBLK_PARAM_TYPE_DISCARD;
|
|
220
|
+
params.discard.discard_granularity = NUM2UINT(logical);
|
|
221
|
+
params.discard.max_discard_sectors = params.basic.max_sectors;
|
|
222
|
+
params.discard.max_write_zeroes_sectors = params.basic.max_sectors;
|
|
223
|
+
params.discard.max_discard_segments = 1;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
result = control_submit(control, UBLK_RB_U_RDWR_CMD(UBLK_CMD_SET_PARAMS), NUM2UINT(id),
|
|
227
|
+
¶ms, sizeof(params), 0);
|
|
228
|
+
ublk_raise_result(result, "UBLK_U_CMD_SET_PARAMS");
|
|
229
|
+
return Qtrue;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
static VALUE control_simple(VALUE self, VALUE id, unsigned op, const char *name, uint64_t data)
|
|
233
|
+
{
|
|
234
|
+
int result = control_submit(get_control(self), UBLK_RB_U_RDWR_CMD(op), NUM2UINT(id), NULL, 0, data);
|
|
235
|
+
ublk_raise_result(result, name);
|
|
236
|
+
return Qtrue;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
static VALUE control_start_dev(VALUE self, VALUE id, VALUE pid)
|
|
240
|
+
{
|
|
241
|
+
return control_simple(self, id, UBLK_CMD_START_DEV, "UBLK_U_CMD_START_DEV", NUM2UINT(pid));
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
static VALUE control_stop_dev(VALUE self, VALUE id)
|
|
245
|
+
{
|
|
246
|
+
return control_simple(self, id, UBLK_CMD_STOP_DEV, "UBLK_U_CMD_STOP_DEV", 0);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
static VALUE control_del_dev(VALUE self, VALUE id)
|
|
250
|
+
{
|
|
251
|
+
return control_simple(self, id, UBLK_CMD_DEL_DEV, "UBLK_U_CMD_DEL_DEV", 0);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
static VALUE control_start_recovery(VALUE self, VALUE id)
|
|
255
|
+
{
|
|
256
|
+
return control_simple(self, id, UBLK_CMD_START_USER_RECOVERY, "UBLK_U_CMD_START_USER_RECOVERY", 0);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
static VALUE control_end_recovery(VALUE self, VALUE id, VALUE pid)
|
|
260
|
+
{
|
|
261
|
+
return control_simple(self, id, UBLK_CMD_END_USER_RECOVERY, "UBLK_U_CMD_END_USER_RECOVERY", NUM2UINT(pid));
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
static VALUE control_get_info(VALUE self, VALUE id)
|
|
265
|
+
{
|
|
266
|
+
ublk_control *control = get_control(self);
|
|
267
|
+
struct ublk_rb_dev_info info;
|
|
268
|
+
int result;
|
|
269
|
+
|
|
270
|
+
memset(&info, 0, sizeof(info));
|
|
271
|
+
info.dev_id = NUM2UINT(id);
|
|
272
|
+
result = control_submit(control, UBLK_RB_U_READ_CMD(UBLK_CMD_GET_DEV_INFO), info.dev_id,
|
|
273
|
+
&info, sizeof(info), 0);
|
|
274
|
+
ublk_raise_result(result, "UBLK_U_CMD_GET_DEV_INFO");
|
|
275
|
+
return info_to_array(&info);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
void ublk_init_control(void)
|
|
279
|
+
{
|
|
280
|
+
cControl = rb_define_class_under(ublk_native_module, "Control", rb_cObject);
|
|
281
|
+
rb_define_alloc_func(cControl, control_alloc);
|
|
282
|
+
rb_define_method(cControl, "initialize", control_initialize, 0);
|
|
283
|
+
rb_define_method(cControl, "close", control_close, 0);
|
|
284
|
+
rb_define_method(cControl, "add_dev", control_add_dev, 5);
|
|
285
|
+
rb_define_method(cControl, "set_params", control_set_params, 8);
|
|
286
|
+
rb_define_method(cControl, "start_dev", control_start_dev, 2);
|
|
287
|
+
rb_define_method(cControl, "stop_dev", control_stop_dev, 1);
|
|
288
|
+
rb_define_method(cControl, "del_dev", control_del_dev, 1);
|
|
289
|
+
rb_define_method(cControl, "start_user_recovery", control_start_recovery, 1);
|
|
290
|
+
rb_define_method(cControl, "end_user_recovery", control_end_recovery, 2);
|
|
291
|
+
rb_define_method(cControl, "get_dev_info", control_get_info, 1);
|
|
292
|
+
}
|
data/ext/ublk/extconf.rb
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "mkmf"
|
|
4
|
+
|
|
5
|
+
abort "ublk requires Linux" unless RbConfig::CONFIG["host_os"].include?("linux")
|
|
6
|
+
abort "install liburing-dev (Debian/Ubuntu) or liburing-devel (Fedora)" unless have_header("liburing.h") && have_library("uring", "io_uring_queue_init_params")
|
|
7
|
+
|
|
8
|
+
have_header("linux/ublk_cmd.h")
|
|
9
|
+
have_header("sys/eventfd.h")
|
|
10
|
+
have_func("mlockall", "sys/mman.h")
|
|
11
|
+
have_const("UBLK_F_USER_COPY", "linux/ublk_cmd.h")
|
|
12
|
+
have_const("UBLK_F_CMD_IOCTL_ENCODE", "linux/ublk_cmd.h")
|
|
13
|
+
|
|
14
|
+
$CFLAGS << " -std=c11 -Wall -Wextra -Werror=implicit-function-declaration"
|
|
15
|
+
create_header
|
|
16
|
+
create_makefile("ublk/ublk")
|
data/ext/ublk/internal.h
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#ifndef UBLK_INTERNAL_H
|
|
2
|
+
#define UBLK_INTERNAL_H
|
|
3
|
+
|
|
4
|
+
#include "ruby.h"
|
|
5
|
+
#include "ruby/thread.h"
|
|
6
|
+
#include "extconf.h"
|
|
7
|
+
|
|
8
|
+
#include <errno.h>
|
|
9
|
+
#include <fcntl.h>
|
|
10
|
+
#include <liburing.h>
|
|
11
|
+
#include <stdint.h>
|
|
12
|
+
#include <string.h>
|
|
13
|
+
#include <unistd.h>
|
|
14
|
+
|
|
15
|
+
#include "compat.h"
|
|
16
|
+
|
|
17
|
+
extern VALUE ublk_module;
|
|
18
|
+
extern VALUE ublk_native_module;
|
|
19
|
+
|
|
20
|
+
void ublk_init_control(void);
|
|
21
|
+
void ublk_init_server(void);
|
|
22
|
+
void ublk_init_constants(void);
|
|
23
|
+
VALUE ublk_native_supported(VALUE self);
|
|
24
|
+
|
|
25
|
+
static inline void ublk_check_pid(pid_t owner)
|
|
26
|
+
{
|
|
27
|
+
if (owner != getpid()) rb_raise(rb_eRuntimeError, "ublk handle cannot be used after fork");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
static inline void ublk_raise_result(int result, const char *operation)
|
|
31
|
+
{
|
|
32
|
+
if (result < 0) rb_syserr_fail(-result, operation);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
static inline void ublk_prep_cmd(struct io_uring_sqe *sqe, int fd, unsigned op)
|
|
36
|
+
{
|
|
37
|
+
memset(sqe, 0, sizeof(*sqe) * 2);
|
|
38
|
+
sqe->opcode = IORING_OP_URING_CMD;
|
|
39
|
+
sqe->fd = fd;
|
|
40
|
+
sqe->cmd_op = op;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
#endif
|