userfaultfd 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 +142 -0
- data/docs/gvl-and-page-faults.md +58 -0
- data/examples/dirty_tracking.rb +30 -0
- data/examples/lazy_mmap.rb +19 -0
- data/examples/oom_canary.rb +26 -0
- data/examples/post_copy_migration.rb +29 -0
- data/ext/userfaultfd/compat.h +237 -0
- data/ext/userfaultfd/constants.c +102 -0
- data/ext/userfaultfd/extconf.rb +12 -0
- data/ext/userfaultfd/userfaultfd.c +2010 -0
- data/lib/userfaultfd/handler.rb +76 -0
- data/lib/userfaultfd/version.rb +5 -0
- data/lib/userfaultfd.rb +58 -0
- data/sig/userfaultfd.rbs +93 -0
- metadata +59 -0
|
@@ -0,0 +1,2010 @@
|
|
|
1
|
+
#include "ruby.h"
|
|
2
|
+
#include "ruby/thread.h"
|
|
3
|
+
#include "extconf.h"
|
|
4
|
+
|
|
5
|
+
#include <errno.h>
|
|
6
|
+
#include <fcntl.h>
|
|
7
|
+
#include <stdint.h>
|
|
8
|
+
#include <stdlib.h>
|
|
9
|
+
#include <string.h>
|
|
10
|
+
#include <sys/mman.h>
|
|
11
|
+
#include <poll.h>
|
|
12
|
+
#include <pthread.h>
|
|
13
|
+
#include <sys/types.h>
|
|
14
|
+
#include <unistd.h>
|
|
15
|
+
|
|
16
|
+
#ifdef __linux__
|
|
17
|
+
# include <sys/ioctl.h>
|
|
18
|
+
# include <sys/syscall.h>
|
|
19
|
+
# include "compat.h"
|
|
20
|
+
# if !defined(SYS_userfaultfd) && defined(__NR_userfaultfd)
|
|
21
|
+
# define SYS_userfaultfd __NR_userfaultfd
|
|
22
|
+
# endif
|
|
23
|
+
#endif
|
|
24
|
+
|
|
25
|
+
typedef struct {
|
|
26
|
+
int fd;
|
|
27
|
+
pid_t pid;
|
|
28
|
+
uint64_t features;
|
|
29
|
+
uint64_t enabled_features;
|
|
30
|
+
uint64_t ioctls;
|
|
31
|
+
int backend;
|
|
32
|
+
} uffd_data_t;
|
|
33
|
+
|
|
34
|
+
typedef struct {
|
|
35
|
+
void *address;
|
|
36
|
+
size_t size;
|
|
37
|
+
size_t page_size;
|
|
38
|
+
int busy;
|
|
39
|
+
int shared;
|
|
40
|
+
} region_data_t;
|
|
41
|
+
|
|
42
|
+
typedef struct {
|
|
43
|
+
VALUE owner;
|
|
44
|
+
VALUE source;
|
|
45
|
+
pid_t pid;
|
|
46
|
+
pthread_t thread;
|
|
47
|
+
int stop_pipe[2];
|
|
48
|
+
int source_fd;
|
|
49
|
+
int started;
|
|
50
|
+
volatile int running;
|
|
51
|
+
int joined;
|
|
52
|
+
volatile int error;
|
|
53
|
+
int mode;
|
|
54
|
+
int uffd_fd;
|
|
55
|
+
uintptr_t start;
|
|
56
|
+
size_t length;
|
|
57
|
+
size_t page_size;
|
|
58
|
+
const void *source_address;
|
|
59
|
+
region_data_t *registered_region;
|
|
60
|
+
region_data_t *source_region;
|
|
61
|
+
} native_handler_data_t;
|
|
62
|
+
|
|
63
|
+
#define EVENT_READER_MAX_FDS 64
|
|
64
|
+
typedef struct {
|
|
65
|
+
VALUE owner;
|
|
66
|
+
pid_t pid;
|
|
67
|
+
pthread_t thread;
|
|
68
|
+
int stop_pipe[2];
|
|
69
|
+
int output_pipe[2];
|
|
70
|
+
int fds[EVENT_READER_MAX_FDS];
|
|
71
|
+
unsigned char owned[EVENT_READER_MAX_FDS];
|
|
72
|
+
size_t fd_count;
|
|
73
|
+
int started;
|
|
74
|
+
int joined;
|
|
75
|
+
volatile int running;
|
|
76
|
+
volatile int error;
|
|
77
|
+
} event_reader_data_t;
|
|
78
|
+
|
|
79
|
+
static VALUE cUserfaultFD;
|
|
80
|
+
static VALUE cRegion;
|
|
81
|
+
static VALUE cFault;
|
|
82
|
+
static VALUE cForkEvent;
|
|
83
|
+
static VALUE cRemapEvent;
|
|
84
|
+
static VALUE cRemoveEvent;
|
|
85
|
+
static VALUE cUnmapEvent;
|
|
86
|
+
static VALUE cNativeHandler;
|
|
87
|
+
static VALUE cEventReader;
|
|
88
|
+
static VALUE eError;
|
|
89
|
+
static VALUE eUnsupportedError;
|
|
90
|
+
|
|
91
|
+
static ID id_size;
|
|
92
|
+
static ID id_shared;
|
|
93
|
+
static ID id_huge;
|
|
94
|
+
static ID id_features;
|
|
95
|
+
static ID id_user_mode_only;
|
|
96
|
+
static ID id_mode;
|
|
97
|
+
static ID id_enabled;
|
|
98
|
+
static ID id_wake;
|
|
99
|
+
static ID id_offset;
|
|
100
|
+
|
|
101
|
+
void userfaultfd_define_constants(VALUE klass);
|
|
102
|
+
|
|
103
|
+
static void native_handler_stop_and_join(native_handler_data_t *data);
|
|
104
|
+
static void event_reader_stop_and_join(event_reader_data_t *data);
|
|
105
|
+
|
|
106
|
+
static void
|
|
107
|
+
uffd_free(void *ptr)
|
|
108
|
+
{
|
|
109
|
+
uffd_data_t *data = ptr;
|
|
110
|
+
if (data->fd >= 0) close(data->fd);
|
|
111
|
+
xfree(data);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
static size_t
|
|
115
|
+
uffd_memsize(const void *ptr)
|
|
116
|
+
{
|
|
117
|
+
return ptr ? sizeof(uffd_data_t) : 0;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
static const rb_data_type_t uffd_type = {
|
|
121
|
+
"UserfaultFD",
|
|
122
|
+
{NULL, uffd_free, uffd_memsize, NULL},
|
|
123
|
+
NULL, NULL, RUBY_TYPED_FREE_IMMEDIATELY
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
static void
|
|
127
|
+
region_free(void *ptr)
|
|
128
|
+
{
|
|
129
|
+
region_data_t *data = ptr;
|
|
130
|
+
if (data->address != MAP_FAILED) munmap(data->address, data->size);
|
|
131
|
+
xfree(data);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
static size_t
|
|
135
|
+
region_memsize(const void *ptr)
|
|
136
|
+
{
|
|
137
|
+
return ptr ? sizeof(region_data_t) : 0;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
static const rb_data_type_t region_type = {
|
|
141
|
+
"UserfaultFD::Region",
|
|
142
|
+
{NULL, region_free, region_memsize, NULL},
|
|
143
|
+
NULL, NULL, RUBY_TYPED_FREE_IMMEDIATELY
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
static void
|
|
147
|
+
native_handler_mark(void *ptr)
|
|
148
|
+
{
|
|
149
|
+
native_handler_data_t *data = ptr;
|
|
150
|
+
rb_gc_mark(data->owner);
|
|
151
|
+
rb_gc_mark(data->source);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
static void
|
|
155
|
+
native_handler_free(void *ptr)
|
|
156
|
+
{
|
|
157
|
+
native_handler_data_t *data = ptr;
|
|
158
|
+
native_handler_stop_and_join(data);
|
|
159
|
+
if (data->stop_pipe[0] >= 0) close(data->stop_pipe[0]);
|
|
160
|
+
if (data->stop_pipe[1] >= 0) close(data->stop_pipe[1]);
|
|
161
|
+
if (data->source_fd >= 0) close(data->source_fd);
|
|
162
|
+
xfree(data);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
static size_t
|
|
166
|
+
native_handler_memsize(const void *ptr)
|
|
167
|
+
{
|
|
168
|
+
return ptr ? sizeof(native_handler_data_t) : 0;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
static const rb_data_type_t native_handler_type = {
|
|
172
|
+
"UserfaultFD::NativeHandler",
|
|
173
|
+
{native_handler_mark, native_handler_free, native_handler_memsize, NULL},
|
|
174
|
+
NULL, NULL, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
static VALUE
|
|
178
|
+
native_handler_alloc(VALUE klass)
|
|
179
|
+
{
|
|
180
|
+
native_handler_data_t *data;
|
|
181
|
+
VALUE object = TypedData_Make_Struct(
|
|
182
|
+
klass, native_handler_data_t, &native_handler_type, data
|
|
183
|
+
);
|
|
184
|
+
data->owner = Qnil;
|
|
185
|
+
data->source = Qnil;
|
|
186
|
+
data->pid = getpid();
|
|
187
|
+
data->stop_pipe[0] = -1;
|
|
188
|
+
data->stop_pipe[1] = -1;
|
|
189
|
+
data->source_fd = -1;
|
|
190
|
+
return object;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
static void
|
|
194
|
+
event_reader_mark(void *ptr)
|
|
195
|
+
{
|
|
196
|
+
event_reader_data_t *data = ptr;
|
|
197
|
+
rb_gc_mark(data->owner);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
static void
|
|
201
|
+
event_reader_free(void *ptr)
|
|
202
|
+
{
|
|
203
|
+
event_reader_data_t *data = ptr;
|
|
204
|
+
size_t i;
|
|
205
|
+
event_reader_stop_and_join(data);
|
|
206
|
+
for (i = 0; i < data->fd_count; i++) {
|
|
207
|
+
if (data->owned[i] && data->fds[i] >= 0) close(data->fds[i]);
|
|
208
|
+
}
|
|
209
|
+
if (data->stop_pipe[0] >= 0) close(data->stop_pipe[0]);
|
|
210
|
+
if (data->stop_pipe[1] >= 0) close(data->stop_pipe[1]);
|
|
211
|
+
if (data->output_pipe[0] >= 0) close(data->output_pipe[0]);
|
|
212
|
+
if (data->output_pipe[1] >= 0) close(data->output_pipe[1]);
|
|
213
|
+
xfree(data);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
static void
|
|
217
|
+
event_reader_close_owned(event_reader_data_t *data)
|
|
218
|
+
{
|
|
219
|
+
size_t i;
|
|
220
|
+
for (i = 0; i < data->fd_count; i++) {
|
|
221
|
+
if (data->owned[i] && data->fds[i] >= 0) {
|
|
222
|
+
close(data->fds[i]);
|
|
223
|
+
data->fds[i] = -1;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
static size_t
|
|
229
|
+
event_reader_memsize(const void *ptr)
|
|
230
|
+
{
|
|
231
|
+
return ptr ? sizeof(event_reader_data_t) : 0;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
static const rb_data_type_t event_reader_type = {
|
|
235
|
+
"UserfaultFD::EventReader",
|
|
236
|
+
{event_reader_mark, event_reader_free, event_reader_memsize, NULL},
|
|
237
|
+
NULL, NULL, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
static VALUE
|
|
241
|
+
event_reader_alloc(VALUE klass)
|
|
242
|
+
{
|
|
243
|
+
event_reader_data_t *data;
|
|
244
|
+
VALUE object = TypedData_Make_Struct(
|
|
245
|
+
klass, event_reader_data_t, &event_reader_type, data
|
|
246
|
+
);
|
|
247
|
+
size_t i;
|
|
248
|
+
data->owner = Qnil;
|
|
249
|
+
data->pid = getpid();
|
|
250
|
+
data->stop_pipe[0] = data->stop_pipe[1] = -1;
|
|
251
|
+
data->output_pipe[0] = data->output_pipe[1] = -1;
|
|
252
|
+
for (i = 0; i < EVENT_READER_MAX_FDS; i++) data->fds[i] = -1;
|
|
253
|
+
return object;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
static VALUE
|
|
257
|
+
uffd_alloc(VALUE klass)
|
|
258
|
+
{
|
|
259
|
+
uffd_data_t *data;
|
|
260
|
+
VALUE object = TypedData_Make_Struct(klass, uffd_data_t, &uffd_type, data);
|
|
261
|
+
data->fd = -1;
|
|
262
|
+
data->pid = getpid();
|
|
263
|
+
return object;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
static VALUE
|
|
267
|
+
region_alloc(VALUE klass)
|
|
268
|
+
{
|
|
269
|
+
region_data_t *data;
|
|
270
|
+
VALUE object = TypedData_Make_Struct(klass, region_data_t, ®ion_type, data);
|
|
271
|
+
data->address = MAP_FAILED;
|
|
272
|
+
data->page_size = (size_t)sysconf(_SC_PAGESIZE);
|
|
273
|
+
return object;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
static uffd_data_t *
|
|
277
|
+
get_uffd(VALUE self)
|
|
278
|
+
{
|
|
279
|
+
uffd_data_t *data;
|
|
280
|
+
TypedData_Get_Struct(self, uffd_data_t, &uffd_type, data);
|
|
281
|
+
if (data->fd < 0) rb_raise(eError, "closed userfaultfd");
|
|
282
|
+
if (data->pid != getpid()) rb_raise(eError, "userfaultfd cannot be used after fork");
|
|
283
|
+
return data;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
static region_data_t *
|
|
287
|
+
get_region(VALUE object)
|
|
288
|
+
{
|
|
289
|
+
region_data_t *data;
|
|
290
|
+
TypedData_Get_Struct(object, region_data_t, ®ion_type, data);
|
|
291
|
+
if (data->address == MAP_FAILED) rb_raise(eError, "unmapped region");
|
|
292
|
+
return data;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
static void raise_unsupported(const char *message) __attribute__((noreturn));
|
|
296
|
+
|
|
297
|
+
static void
|
|
298
|
+
raise_unsupported(const char *message)
|
|
299
|
+
{
|
|
300
|
+
rb_raise(eUnsupportedError, "%s", message);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
#ifdef __linux__
|
|
304
|
+
static int
|
|
305
|
+
open_uffd(int *backend, int user_mode_only)
|
|
306
|
+
{
|
|
307
|
+
int flags = O_CLOEXEC | O_NONBLOCK;
|
|
308
|
+
if (user_mode_only) flags |= UFFD_USER_MODE_ONLY;
|
|
309
|
+
int dev = open("/dev/userfaultfd", O_RDWR | O_CLOEXEC);
|
|
310
|
+
if (dev >= 0) {
|
|
311
|
+
int fd = ioctl(dev, USERFAULTFD_IOC_NEW, flags);
|
|
312
|
+
int saved_errno = errno;
|
|
313
|
+
close(dev);
|
|
314
|
+
if (fd >= 0) {
|
|
315
|
+
*backend = 1;
|
|
316
|
+
return fd;
|
|
317
|
+
}
|
|
318
|
+
errno = saved_errno;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
# ifdef SYS_userfaultfd
|
|
322
|
+
{
|
|
323
|
+
int fd = (int)syscall(SYS_userfaultfd, flags);
|
|
324
|
+
if (fd >= 0) {
|
|
325
|
+
*backend = 2;
|
|
326
|
+
return fd;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
# else
|
|
330
|
+
errno = ENOSYS;
|
|
331
|
+
# endif
|
|
332
|
+
return -1;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
static int
|
|
336
|
+
query_api(int fd, uint64_t requested, uint64_t *features, uint64_t *ioctls)
|
|
337
|
+
{
|
|
338
|
+
struct uffdio_api api;
|
|
339
|
+
memset(&api, 0, sizeof(api));
|
|
340
|
+
api.api = UFFD_API;
|
|
341
|
+
api.features = requested;
|
|
342
|
+
if (ioctl(fd, UFFDIO_API, &api) < 0) return -1;
|
|
343
|
+
*features = api.features;
|
|
344
|
+
*ioctls = api.ioctls;
|
|
345
|
+
return 0;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
struct feature_name {
|
|
349
|
+
const char *name;
|
|
350
|
+
uint64_t value;
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
static const struct feature_name feature_names[] = {
|
|
354
|
+
#ifdef UFFD_FEATURE_PAGEFAULT_FLAG_WP
|
|
355
|
+
{"pagefault_flag_wp", UFFD_FEATURE_PAGEFAULT_FLAG_WP},
|
|
356
|
+
#endif
|
|
357
|
+
#ifdef UFFD_FEATURE_EVENT_FORK
|
|
358
|
+
{"event_fork", UFFD_FEATURE_EVENT_FORK},
|
|
359
|
+
#endif
|
|
360
|
+
#ifdef UFFD_FEATURE_EVENT_REMAP
|
|
361
|
+
{"event_remap", UFFD_FEATURE_EVENT_REMAP},
|
|
362
|
+
#endif
|
|
363
|
+
#ifdef UFFD_FEATURE_EVENT_REMOVE
|
|
364
|
+
{"event_remove", UFFD_FEATURE_EVENT_REMOVE},
|
|
365
|
+
#endif
|
|
366
|
+
#ifdef UFFD_FEATURE_EVENT_UNMAP
|
|
367
|
+
{"event_unmap", UFFD_FEATURE_EVENT_UNMAP},
|
|
368
|
+
#endif
|
|
369
|
+
#ifdef UFFD_FEATURE_MISSING_HUGETLBFS
|
|
370
|
+
{"missing_hugetlbfs", UFFD_FEATURE_MISSING_HUGETLBFS},
|
|
371
|
+
#endif
|
|
372
|
+
#ifdef UFFD_FEATURE_MISSING_SHMEM
|
|
373
|
+
{"missing_shmem", UFFD_FEATURE_MISSING_SHMEM},
|
|
374
|
+
#endif
|
|
375
|
+
#ifdef UFFD_FEATURE_SIGBUS
|
|
376
|
+
{"sigbus", UFFD_FEATURE_SIGBUS},
|
|
377
|
+
#endif
|
|
378
|
+
#ifdef UFFD_FEATURE_THREAD_ID
|
|
379
|
+
{"thread_id", UFFD_FEATURE_THREAD_ID},
|
|
380
|
+
#endif
|
|
381
|
+
#ifdef UFFD_FEATURE_MINOR_HUGETLBFS
|
|
382
|
+
{"minor_hugetlbfs", UFFD_FEATURE_MINOR_HUGETLBFS},
|
|
383
|
+
#endif
|
|
384
|
+
#ifdef UFFD_FEATURE_MINOR_SHMEM
|
|
385
|
+
{"minor_shmem", UFFD_FEATURE_MINOR_SHMEM},
|
|
386
|
+
#endif
|
|
387
|
+
#ifdef UFFD_FEATURE_EXACT_ADDRESS
|
|
388
|
+
{"exact_address", UFFD_FEATURE_EXACT_ADDRESS},
|
|
389
|
+
#endif
|
|
390
|
+
#ifdef UFFD_FEATURE_WP_HUGETLBFS_SHMEM
|
|
391
|
+
{"wp_hugetlbfs_shmem", UFFD_FEATURE_WP_HUGETLBFS_SHMEM},
|
|
392
|
+
#endif
|
|
393
|
+
#ifdef UFFD_FEATURE_WP_UNPOPULATED
|
|
394
|
+
{"wp_unpopulated", UFFD_FEATURE_WP_UNPOPULATED},
|
|
395
|
+
#endif
|
|
396
|
+
#ifdef UFFD_FEATURE_WP_ASYNC
|
|
397
|
+
{"wp_async", UFFD_FEATURE_WP_ASYNC},
|
|
398
|
+
#endif
|
|
399
|
+
#ifdef UFFD_FEATURE_POISON
|
|
400
|
+
{"poison", UFFD_FEATURE_POISON},
|
|
401
|
+
#endif
|
|
402
|
+
#ifdef UFFD_FEATURE_MOVE
|
|
403
|
+
{"move", UFFD_FEATURE_MOVE},
|
|
404
|
+
#endif
|
|
405
|
+
{NULL, 0}
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
static uint64_t
|
|
409
|
+
feature_mask(VALUE names)
|
|
410
|
+
{
|
|
411
|
+
uint64_t mask = 0;
|
|
412
|
+
long i;
|
|
413
|
+
Check_Type(names, T_ARRAY);
|
|
414
|
+
for (i = 0; i < RARRAY_LEN(names); i++) {
|
|
415
|
+
VALUE name = rb_ary_entry(names, i);
|
|
416
|
+
const char *value;
|
|
417
|
+
const struct feature_name *entry;
|
|
418
|
+
if (!SYMBOL_P(name)) rb_raise(rb_eArgError, "features must contain symbols");
|
|
419
|
+
value = rb_id2name(SYM2ID(name));
|
|
420
|
+
for (entry = feature_names; entry->name; entry++) {
|
|
421
|
+
if (strcmp(value, entry->name) == 0) {
|
|
422
|
+
mask |= entry->value;
|
|
423
|
+
break;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
if (!entry->name) rb_raise(rb_eArgError, "unknown feature: %s", value);
|
|
427
|
+
}
|
|
428
|
+
return mask;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
static VALUE
|
|
432
|
+
features_to_array(uint64_t mask)
|
|
433
|
+
{
|
|
434
|
+
VALUE result = rb_ary_new();
|
|
435
|
+
const struct feature_name *entry;
|
|
436
|
+
for (entry = feature_names; entry->name; entry++) {
|
|
437
|
+
if ((mask & entry->value) != 0)
|
|
438
|
+
rb_ary_push(result, ID2SYM(rb_intern(entry->name)));
|
|
439
|
+
}
|
|
440
|
+
return result;
|
|
441
|
+
}
|
|
442
|
+
#endif
|
|
443
|
+
|
|
444
|
+
static VALUE
|
|
445
|
+
uffd_initialize(int argc, VALUE *argv, VALUE self)
|
|
446
|
+
{
|
|
447
|
+
VALUE kwargs = Qnil;
|
|
448
|
+
VALUE values[2] = {Qundef, Qundef};
|
|
449
|
+
uint64_t requested = 0;
|
|
450
|
+
int features_given = 0;
|
|
451
|
+
int user_mode_only = 1;
|
|
452
|
+
uffd_data_t *data;
|
|
453
|
+
|
|
454
|
+
rb_scan_args(argc, argv, "0:", &kwargs);
|
|
455
|
+
if (!NIL_P(kwargs)) {
|
|
456
|
+
ID keys[] = {id_features, id_user_mode_only};
|
|
457
|
+
rb_get_kwargs(kwargs, keys, 0, 2, values);
|
|
458
|
+
if (values[0] != Qundef && !NIL_P(values[0])) {
|
|
459
|
+
features_given = 1;
|
|
460
|
+
#ifdef __linux__
|
|
461
|
+
requested = feature_mask(values[0]);
|
|
462
|
+
#else
|
|
463
|
+
(void)values;
|
|
464
|
+
#endif
|
|
465
|
+
}
|
|
466
|
+
if (values[1] != Qundef) {
|
|
467
|
+
if (values[1] != Qtrue && values[1] != Qfalse)
|
|
468
|
+
rb_raise(rb_eArgError, "user_mode_only must be true or false");
|
|
469
|
+
user_mode_only = values[1] == Qtrue;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
TypedData_Get_Struct(self, uffd_data_t, &uffd_type, data);
|
|
473
|
+
|
|
474
|
+
#ifdef __linux__
|
|
475
|
+
{
|
|
476
|
+
int probe_backend = 0;
|
|
477
|
+
int probe_fd = open_uffd(&probe_backend, user_mode_only);
|
|
478
|
+
uint64_t available = 0, ignored_ioctls = 0;
|
|
479
|
+
int saved_errno;
|
|
480
|
+
if (probe_fd < 0) {
|
|
481
|
+
if (errno == ENOSYS || errno == ENODEV || errno == EOPNOTSUPP)
|
|
482
|
+
raise_unsupported("userfaultfd is not supported by this kernel");
|
|
483
|
+
rb_syserr_fail(errno, "userfaultfd");
|
|
484
|
+
}
|
|
485
|
+
if (query_api(probe_fd, 0, &available, &ignored_ioctls) < 0) {
|
|
486
|
+
saved_errno = errno;
|
|
487
|
+
close(probe_fd);
|
|
488
|
+
if (saved_errno == EINVAL || saved_errno == ENOTTY)
|
|
489
|
+
raise_unsupported("UFFDIO_API is not supported by this kernel");
|
|
490
|
+
rb_syserr_fail(saved_errno, "UFFDIO_API");
|
|
491
|
+
}
|
|
492
|
+
close(probe_fd);
|
|
493
|
+
#ifdef UFFD_FEATURE_EVENT_FORK
|
|
494
|
+
if (!features_given) requested = available & UFFD_FEATURE_EVENT_FORK;
|
|
495
|
+
#else
|
|
496
|
+
(void)features_given;
|
|
497
|
+
#endif
|
|
498
|
+
if ((requested & available) != requested)
|
|
499
|
+
raise_unsupported("requested userfaultfd features are unavailable");
|
|
500
|
+
|
|
501
|
+
open_production_fd:
|
|
502
|
+
data->fd = open_uffd(&data->backend, user_mode_only);
|
|
503
|
+
if (data->fd < 0) rb_syserr_fail(errno, "userfaultfd");
|
|
504
|
+
if (query_api(data->fd, requested, &data->features, &data->ioctls) < 0) {
|
|
505
|
+
saved_errno = errno;
|
|
506
|
+
close(data->fd);
|
|
507
|
+
data->fd = -1;
|
|
508
|
+
if (!features_given && requested && saved_errno == EPERM) {
|
|
509
|
+
requested = 0;
|
|
510
|
+
goto open_production_fd;
|
|
511
|
+
}
|
|
512
|
+
if (saved_errno == EINVAL)
|
|
513
|
+
raise_unsupported("requested userfaultfd features were rejected");
|
|
514
|
+
rb_syserr_fail(saved_errno, "UFFDIO_API");
|
|
515
|
+
}
|
|
516
|
+
data->enabled_features = requested;
|
|
517
|
+
}
|
|
518
|
+
#else
|
|
519
|
+
(void)requested;
|
|
520
|
+
(void)features_given;
|
|
521
|
+
(void)user_mode_only;
|
|
522
|
+
raise_unsupported("userfaultfd is only available on Linux");
|
|
523
|
+
#endif
|
|
524
|
+
return self;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
static int
|
|
528
|
+
user_mode_only_option(int argc, VALUE *argv)
|
|
529
|
+
{
|
|
530
|
+
VALUE kwargs = Qnil;
|
|
531
|
+
VALUE value = Qundef;
|
|
532
|
+
ID key = id_user_mode_only;
|
|
533
|
+
rb_scan_args(argc, argv, "0:", &kwargs);
|
|
534
|
+
if (!NIL_P(kwargs)) rb_get_kwargs(kwargs, &key, 0, 1, &value);
|
|
535
|
+
if (value != Qundef && value != Qtrue && value != Qfalse)
|
|
536
|
+
rb_raise(rb_eArgError, "user_mode_only must be true or false");
|
|
537
|
+
return value == Qundef || value == Qtrue;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
static VALUE
|
|
541
|
+
uffd_supported(int argc, VALUE *argv, VALUE klass)
|
|
542
|
+
{
|
|
543
|
+
int user_mode_only = user_mode_only_option(argc, argv);
|
|
544
|
+
(void)klass;
|
|
545
|
+
#ifdef __linux__
|
|
546
|
+
{
|
|
547
|
+
int backend = 0;
|
|
548
|
+
int fd = open_uffd(&backend, user_mode_only);
|
|
549
|
+
uint64_t features, ioctls;
|
|
550
|
+
if (fd < 0) return Qfalse;
|
|
551
|
+
if (query_api(fd, 0, &features, &ioctls) < 0) {
|
|
552
|
+
close(fd);
|
|
553
|
+
return Qfalse;
|
|
554
|
+
}
|
|
555
|
+
close(fd);
|
|
556
|
+
return Qtrue;
|
|
557
|
+
}
|
|
558
|
+
#else
|
|
559
|
+
(void)user_mode_only;
|
|
560
|
+
return Qfalse;
|
|
561
|
+
#endif
|
|
562
|
+
return Qnil;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
static VALUE
|
|
566
|
+
uffd_features(int argc, VALUE *argv, VALUE klass)
|
|
567
|
+
{
|
|
568
|
+
int user_mode_only = user_mode_only_option(argc, argv);
|
|
569
|
+
(void)klass;
|
|
570
|
+
#ifdef __linux__
|
|
571
|
+
{
|
|
572
|
+
int backend = 0;
|
|
573
|
+
int fd = open_uffd(&backend, user_mode_only);
|
|
574
|
+
uint64_t features, ioctls;
|
|
575
|
+
if (fd < 0) return rb_ary_new();
|
|
576
|
+
if (query_api(fd, 0, &features, &ioctls) < 0) {
|
|
577
|
+
close(fd);
|
|
578
|
+
return rb_ary_new();
|
|
579
|
+
}
|
|
580
|
+
close(fd);
|
|
581
|
+
return features_to_array(features);
|
|
582
|
+
}
|
|
583
|
+
#else
|
|
584
|
+
(void)user_mode_only;
|
|
585
|
+
return rb_ary_new();
|
|
586
|
+
#endif
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
static VALUE
|
|
590
|
+
uffd_instance_features(VALUE self)
|
|
591
|
+
{
|
|
592
|
+
uffd_data_t *data = get_uffd(self);
|
|
593
|
+
#ifdef __linux__
|
|
594
|
+
return features_to_array(data->features);
|
|
595
|
+
#else
|
|
596
|
+
(void)data;
|
|
597
|
+
return rb_ary_new();
|
|
598
|
+
#endif
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
static VALUE
|
|
602
|
+
uffd_enabled_features(VALUE self)
|
|
603
|
+
{
|
|
604
|
+
uffd_data_t *data = get_uffd(self);
|
|
605
|
+
#ifdef __linux__
|
|
606
|
+
return features_to_array(data->enabled_features);
|
|
607
|
+
#else
|
|
608
|
+
(void)data;
|
|
609
|
+
return rb_ary_new();
|
|
610
|
+
#endif
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
static VALUE
|
|
614
|
+
uffd_backend(VALUE self)
|
|
615
|
+
{
|
|
616
|
+
uffd_data_t *data = get_uffd(self);
|
|
617
|
+
return ID2SYM(rb_intern(data->backend == 1 ? "device" : "syscall"));
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
static VALUE
|
|
621
|
+
uffd_fileno(VALUE self)
|
|
622
|
+
{
|
|
623
|
+
return INT2NUM(get_uffd(self)->fd);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
static VALUE
|
|
627
|
+
uffd_close(VALUE self)
|
|
628
|
+
{
|
|
629
|
+
uffd_data_t *data;
|
|
630
|
+
TypedData_Get_Struct(self, uffd_data_t, &uffd_type, data);
|
|
631
|
+
if (data->fd >= 0) {
|
|
632
|
+
close(data->fd);
|
|
633
|
+
data->fd = -1;
|
|
634
|
+
rb_ivar_set(self, rb_intern("@registered_regions"), Qnil);
|
|
635
|
+
}
|
|
636
|
+
return Qnil;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
static VALUE
|
|
640
|
+
uffd_closed_p(VALUE self)
|
|
641
|
+
{
|
|
642
|
+
uffd_data_t *data;
|
|
643
|
+
TypedData_Get_Struct(self, uffd_data_t, &uffd_type, data);
|
|
644
|
+
return data->fd < 0 ? Qtrue : Qfalse;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
static VALUE
|
|
648
|
+
region_initialize(int argc, VALUE *argv, VALUE self)
|
|
649
|
+
{
|
|
650
|
+
VALUE kwargs;
|
|
651
|
+
VALUE values[3] = {Qundef, Qundef, Qundef};
|
|
652
|
+
ID keys[] = {id_size, id_shared, id_huge};
|
|
653
|
+
region_data_t *data;
|
|
654
|
+
size_t size;
|
|
655
|
+
int flags;
|
|
656
|
+
|
|
657
|
+
rb_scan_args(argc, argv, "0:", &kwargs);
|
|
658
|
+
rb_get_kwargs(kwargs, keys, 1, 2, values);
|
|
659
|
+
TypedData_Get_Struct(self, region_data_t, ®ion_type, data);
|
|
660
|
+
size = NUM2SIZET(values[0]);
|
|
661
|
+
if (size == 0 || size % data->page_size != 0)
|
|
662
|
+
rb_raise(rb_eArgError, "size must be a positive multiple of page_size");
|
|
663
|
+
|
|
664
|
+
flags = (values[1] == Qfalse ? MAP_PRIVATE : MAP_SHARED) | MAP_ANONYMOUS;
|
|
665
|
+
data->shared = values[1] != Qfalse;
|
|
666
|
+
if (values[2] == Qtrue) {
|
|
667
|
+
#ifdef MAP_HUGETLB
|
|
668
|
+
flags |= MAP_HUGETLB;
|
|
669
|
+
#else
|
|
670
|
+
rb_raise(eUnsupportedError, "huge pages are unavailable on this platform");
|
|
671
|
+
#endif
|
|
672
|
+
}
|
|
673
|
+
data->address = mmap(NULL, size, PROT_READ | PROT_WRITE, flags, -1, 0);
|
|
674
|
+
if (data->address == MAP_FAILED) rb_syserr_fail(errno, "mmap");
|
|
675
|
+
data->size = size;
|
|
676
|
+
return self;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
static VALUE
|
|
680
|
+
region_address(VALUE self)
|
|
681
|
+
{
|
|
682
|
+
return ULL2NUM((uintptr_t)get_region(self)->address);
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
static VALUE
|
|
686
|
+
region_size(VALUE self)
|
|
687
|
+
{
|
|
688
|
+
return SIZET2NUM(get_region(self)->size);
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
static VALUE
|
|
692
|
+
region_page_size(VALUE self)
|
|
693
|
+
{
|
|
694
|
+
region_data_t *data;
|
|
695
|
+
TypedData_Get_Struct(self, region_data_t, ®ion_type, data);
|
|
696
|
+
return SIZET2NUM(data->page_size);
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
static VALUE
|
|
700
|
+
region_unmap(VALUE self)
|
|
701
|
+
{
|
|
702
|
+
region_data_t *data;
|
|
703
|
+
TypedData_Get_Struct(self, region_data_t, ®ion_type, data);
|
|
704
|
+
if (data->address == MAP_FAILED) return Qnil;
|
|
705
|
+
if (data->busy) rb_raise(eError, "region is in use");
|
|
706
|
+
if (munmap(data->address, data->size) < 0) rb_syserr_fail(errno, "munmap");
|
|
707
|
+
data->address = MAP_FAILED;
|
|
708
|
+
return Qnil;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
static VALUE
|
|
712
|
+
region_unmapped_p(VALUE self)
|
|
713
|
+
{
|
|
714
|
+
region_data_t *data;
|
|
715
|
+
TypedData_Get_Struct(self, region_data_t, ®ion_type, data);
|
|
716
|
+
return data->address == MAP_FAILED ? Qtrue : Qfalse;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
struct copy_args {
|
|
720
|
+
void *destination;
|
|
721
|
+
const void *source;
|
|
722
|
+
size_t length;
|
|
723
|
+
};
|
|
724
|
+
|
|
725
|
+
static void *
|
|
726
|
+
without_gvl_memcpy(void *ptr)
|
|
727
|
+
{
|
|
728
|
+
struct copy_args *args = ptr;
|
|
729
|
+
memcpy(args->destination, args->source, args->length);
|
|
730
|
+
return NULL;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
static void
|
|
734
|
+
validate_range(region_data_t *data, size_t offset, size_t length)
|
|
735
|
+
{
|
|
736
|
+
if (offset > data->size || length > data->size - offset)
|
|
737
|
+
rb_raise(rb_eRangeError, "range is outside region");
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
static VALUE
|
|
741
|
+
region_read(VALUE self, VALUE offset_value, VALUE length_value)
|
|
742
|
+
{
|
|
743
|
+
region_data_t *data = get_region(self);
|
|
744
|
+
size_t offset = NUM2SIZET(offset_value);
|
|
745
|
+
size_t length = NUM2SIZET(length_value);
|
|
746
|
+
char *buffer;
|
|
747
|
+
VALUE result;
|
|
748
|
+
struct copy_args args;
|
|
749
|
+
validate_range(data, offset, length);
|
|
750
|
+
buffer = xmalloc(length ? length : 1);
|
|
751
|
+
args.destination = buffer;
|
|
752
|
+
args.source = (char *)data->address + offset;
|
|
753
|
+
args.length = length;
|
|
754
|
+
data->busy++;
|
|
755
|
+
rb_thread_call_without_gvl(without_gvl_memcpy, &args, RUBY_UBF_IO, NULL);
|
|
756
|
+
data->busy--;
|
|
757
|
+
result = rb_str_new(buffer, (long)length);
|
|
758
|
+
xfree(buffer);
|
|
759
|
+
return result;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
static VALUE
|
|
763
|
+
region_write(VALUE self, VALUE offset_value, VALUE string)
|
|
764
|
+
{
|
|
765
|
+
region_data_t *data = get_region(self);
|
|
766
|
+
size_t offset = NUM2SIZET(offset_value);
|
|
767
|
+
size_t length;
|
|
768
|
+
char *buffer;
|
|
769
|
+
struct copy_args args;
|
|
770
|
+
StringValue(string);
|
|
771
|
+
length = (size_t)RSTRING_LEN(string);
|
|
772
|
+
validate_range(data, offset, length);
|
|
773
|
+
buffer = xmalloc(length ? length : 1);
|
|
774
|
+
memcpy(buffer, RSTRING_PTR(string), length);
|
|
775
|
+
args.destination = (char *)data->address + offset;
|
|
776
|
+
args.source = buffer;
|
|
777
|
+
args.length = length;
|
|
778
|
+
data->busy++;
|
|
779
|
+
rb_thread_call_without_gvl(without_gvl_memcpy, &args, RUBY_UBF_IO, NULL);
|
|
780
|
+
data->busy--;
|
|
781
|
+
xfree(buffer);
|
|
782
|
+
return SIZET2NUM(length);
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
static VALUE
|
|
786
|
+
region_madvise(VALUE self, VALUE advice)
|
|
787
|
+
{
|
|
788
|
+
region_data_t *data = get_region(self);
|
|
789
|
+
{
|
|
790
|
+
int native_advice;
|
|
791
|
+
if (advice == ID2SYM(rb_intern("dontneed"))) {
|
|
792
|
+
native_advice = MADV_DONTNEED;
|
|
793
|
+
#ifdef MADV_REMOVE
|
|
794
|
+
} else if (advice == ID2SYM(rb_intern("remove"))) {
|
|
795
|
+
native_advice = MADV_REMOVE;
|
|
796
|
+
#endif
|
|
797
|
+
} else {
|
|
798
|
+
rb_raise(rb_eArgError, "unknown advice");
|
|
799
|
+
}
|
|
800
|
+
if (madvise(data->address, data->size, native_advice) < 0)
|
|
801
|
+
rb_syserr_fail(errno, "madvise");
|
|
802
|
+
}
|
|
803
|
+
return Qnil;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
#ifdef __linux__
|
|
807
|
+
static uint64_t
|
|
808
|
+
register_mode(VALUE value)
|
|
809
|
+
{
|
|
810
|
+
uint64_t result = 0;
|
|
811
|
+
long i;
|
|
812
|
+
VALUE modes = RB_TYPE_P(value, T_ARRAY) ? value : rb_ary_new_from_args(1, value);
|
|
813
|
+
for (i = 0; i < RARRAY_LEN(modes); i++) {
|
|
814
|
+
VALUE mode = rb_ary_entry(modes, i);
|
|
815
|
+
if (mode == ID2SYM(rb_intern("missing"))) result |= UFFDIO_REGISTER_MODE_MISSING;
|
|
816
|
+
#ifdef UFFDIO_REGISTER_MODE_WP
|
|
817
|
+
else if (mode == ID2SYM(rb_intern("wp"))) result |= UFFDIO_REGISTER_MODE_WP;
|
|
818
|
+
#endif
|
|
819
|
+
#ifdef UFFDIO_REGISTER_MODE_MINOR
|
|
820
|
+
else if (mode == ID2SYM(rb_intern("minor"))) result |= UFFDIO_REGISTER_MODE_MINOR;
|
|
821
|
+
#endif
|
|
822
|
+
else rb_raise(rb_eArgError, "unknown registration mode");
|
|
823
|
+
}
|
|
824
|
+
if (result == 0) rb_raise(rb_eArgError, "at least one registration mode is required");
|
|
825
|
+
return result;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
static void
|
|
829
|
+
require_enabled_feature(uffd_data_t *data, uint64_t mask, const char *name)
|
|
830
|
+
{
|
|
831
|
+
if ((data->enabled_features & mask) == 0)
|
|
832
|
+
raise_unsupported(name);
|
|
833
|
+
}
|
|
834
|
+
#endif
|
|
835
|
+
|
|
836
|
+
static VALUE
|
|
837
|
+
uffd_register(int argc, VALUE *argv, VALUE self)
|
|
838
|
+
{
|
|
839
|
+
VALUE region_object, kwargs;
|
|
840
|
+
VALUE values[1] = {Qundef};
|
|
841
|
+
ID keys[] = {id_mode};
|
|
842
|
+
uffd_data_t *data = get_uffd(self);
|
|
843
|
+
region_data_t *region;
|
|
844
|
+
rb_scan_args(argc, argv, "1:", ®ion_object, &kwargs);
|
|
845
|
+
rb_get_kwargs(kwargs, keys, 1, 0, values);
|
|
846
|
+
region = get_region(region_object);
|
|
847
|
+
if (region->busy) rb_raise(eError, "region is in use");
|
|
848
|
+
#ifdef __linux__
|
|
849
|
+
{
|
|
850
|
+
struct uffdio_register request;
|
|
851
|
+
uint64_t mode = register_mode(values[0]);
|
|
852
|
+
#ifdef UFFDIO_REGISTER_MODE_WP
|
|
853
|
+
if (mode & UFFDIO_REGISTER_MODE_WP)
|
|
854
|
+
require_enabled_feature(data, UFFD_FEATURE_PAGEFAULT_FLAG_WP,
|
|
855
|
+
"write-protect faults were not enabled");
|
|
856
|
+
#endif
|
|
857
|
+
#ifdef UFFDIO_REGISTER_MODE_MINOR
|
|
858
|
+
if (mode & UFFDIO_REGISTER_MODE_MINOR)
|
|
859
|
+
require_enabled_feature(data,
|
|
860
|
+
UFFD_FEATURE_MINOR_HUGETLBFS |
|
|
861
|
+
UFFD_FEATURE_MINOR_SHMEM,
|
|
862
|
+
"minor faults were not enabled");
|
|
863
|
+
#endif
|
|
864
|
+
memset(&request, 0, sizeof(request));
|
|
865
|
+
request.range.start = (uint64_t)(uintptr_t)region->address;
|
|
866
|
+
request.range.len = region->size;
|
|
867
|
+
request.mode = mode;
|
|
868
|
+
if (ioctl(data->fd, UFFDIO_REGISTER, &request) < 0)
|
|
869
|
+
rb_syserr_fail(errno, "UFFDIO_REGISTER");
|
|
870
|
+
{
|
|
871
|
+
VALUE regions = rb_ivar_get(self, rb_intern("@registered_regions"));
|
|
872
|
+
if (NIL_P(regions)) {
|
|
873
|
+
regions = rb_ary_new();
|
|
874
|
+
rb_ivar_set(self, rb_intern("@registered_regions"), regions);
|
|
875
|
+
}
|
|
876
|
+
rb_ary_push(regions, region_object);
|
|
877
|
+
}
|
|
878
|
+
return ULL2NUM(request.ioctls);
|
|
879
|
+
}
|
|
880
|
+
#else
|
|
881
|
+
(void)data; (void)region; (void)values;
|
|
882
|
+
raise_unsupported("userfaultfd is only available on Linux");
|
|
883
|
+
#endif
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
static VALUE
|
|
887
|
+
uffd_unregister(VALUE self, VALUE region_object)
|
|
888
|
+
{
|
|
889
|
+
uffd_data_t *data = get_uffd(self);
|
|
890
|
+
region_data_t *region = get_region(region_object);
|
|
891
|
+
if (region->busy) rb_raise(eError, "region is in use");
|
|
892
|
+
#ifdef __linux__
|
|
893
|
+
{
|
|
894
|
+
struct uffdio_range range;
|
|
895
|
+
range.start = (uint64_t)(uintptr_t)region->address;
|
|
896
|
+
range.len = region->size;
|
|
897
|
+
if (ioctl(data->fd, UFFDIO_UNREGISTER, &range) < 0)
|
|
898
|
+
rb_syserr_fail(errno, "UFFDIO_UNREGISTER");
|
|
899
|
+
{
|
|
900
|
+
VALUE regions = rb_ivar_get(self, rb_intern("@registered_regions"));
|
|
901
|
+
if (!NIL_P(regions)) rb_ary_delete(regions, region_object);
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
#else
|
|
905
|
+
(void)data; (void)region;
|
|
906
|
+
raise_unsupported("userfaultfd is only available on Linux");
|
|
907
|
+
#endif
|
|
908
|
+
return Qnil;
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
static VALUE
|
|
912
|
+
uffd_writeprotect(int argc, VALUE *argv, VALUE self)
|
|
913
|
+
{
|
|
914
|
+
VALUE region_object, kwargs;
|
|
915
|
+
VALUE values[1] = {Qundef};
|
|
916
|
+
ID keys[] = {id_enabled};
|
|
917
|
+
uffd_data_t *data = get_uffd(self);
|
|
918
|
+
region_data_t *region;
|
|
919
|
+
rb_scan_args(argc, argv, "1:", ®ion_object, &kwargs);
|
|
920
|
+
rb_get_kwargs(kwargs, keys, 1, 0, values);
|
|
921
|
+
region = get_region(region_object);
|
|
922
|
+
#if defined(__linux__) && defined(UFFDIO_WRITEPROTECT)
|
|
923
|
+
{
|
|
924
|
+
struct uffdio_writeprotect request;
|
|
925
|
+
require_enabled_feature(data, UFFD_FEATURE_PAGEFAULT_FLAG_WP,
|
|
926
|
+
"write-protect faults were not enabled");
|
|
927
|
+
memset(&request, 0, sizeof(request));
|
|
928
|
+
request.range.start = (uint64_t)(uintptr_t)region->address;
|
|
929
|
+
request.range.len = region->size;
|
|
930
|
+
request.mode = values[0] == Qtrue ? UFFDIO_WRITEPROTECT_MODE_WP : 0;
|
|
931
|
+
if (ioctl(data->fd, UFFDIO_WRITEPROTECT, &request) < 0)
|
|
932
|
+
rb_syserr_fail(errno, "UFFDIO_WRITEPROTECT");
|
|
933
|
+
}
|
|
934
|
+
#else
|
|
935
|
+
(void)data; (void)region; (void)values;
|
|
936
|
+
raise_unsupported("write protection is unavailable on this platform");
|
|
937
|
+
#endif
|
|
938
|
+
return Qnil;
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
#ifdef __linux__
|
|
942
|
+
struct read_args {
|
|
943
|
+
int fd;
|
|
944
|
+
void *buffer;
|
|
945
|
+
size_t length;
|
|
946
|
+
ssize_t result;
|
|
947
|
+
int error;
|
|
948
|
+
};
|
|
949
|
+
|
|
950
|
+
static void *
|
|
951
|
+
without_gvl_read(void *ptr)
|
|
952
|
+
{
|
|
953
|
+
struct read_args *args = ptr;
|
|
954
|
+
args->result = read(args->fd, args->buffer, args->length);
|
|
955
|
+
args->error = errno;
|
|
956
|
+
return NULL;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
struct ioctl_args {
|
|
960
|
+
int fd;
|
|
961
|
+
unsigned long request;
|
|
962
|
+
void *argument;
|
|
963
|
+
int result;
|
|
964
|
+
int error;
|
|
965
|
+
};
|
|
966
|
+
|
|
967
|
+
static void *
|
|
968
|
+
without_gvl_ioctl(void *ptr)
|
|
969
|
+
{
|
|
970
|
+
struct ioctl_args *args = ptr;
|
|
971
|
+
args->result = ioctl(args->fd, args->request, args->argument);
|
|
972
|
+
args->error = errno;
|
|
973
|
+
return NULL;
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
static void
|
|
977
|
+
run_ioctl_without_gvl(int fd, unsigned long request, void *argument, const char *name)
|
|
978
|
+
{
|
|
979
|
+
struct ioctl_args args = {fd, request, argument, 0, 0};
|
|
980
|
+
rb_thread_call_without_gvl(without_gvl_ioctl, &args, RUBY_UBF_IO, NULL);
|
|
981
|
+
if (args.result < 0) rb_syserr_fail(args.error, name);
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
static VALUE
|
|
985
|
+
new_event(VALUE klass)
|
|
986
|
+
{
|
|
987
|
+
return rb_obj_alloc(klass);
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
static VALUE
|
|
991
|
+
new_fault(VALUE owner, const struct uffd_msg *message)
|
|
992
|
+
{
|
|
993
|
+
VALUE event = new_event(cFault);
|
|
994
|
+
VALUE flags = rb_ary_new();
|
|
995
|
+
size_t page_size = (size_t)sysconf(_SC_PAGESIZE);
|
|
996
|
+
uintptr_t address = (uintptr_t)message->arg.pagefault.address;
|
|
997
|
+
address &= ~((uintptr_t)page_size - 1);
|
|
998
|
+
if (message->arg.pagefault.flags & UFFD_PAGEFAULT_FLAG_WRITE)
|
|
999
|
+
rb_ary_push(flags, ID2SYM(rb_intern("write")));
|
|
1000
|
+
#ifdef UFFD_PAGEFAULT_FLAG_WP
|
|
1001
|
+
if (message->arg.pagefault.flags & UFFD_PAGEFAULT_FLAG_WP)
|
|
1002
|
+
rb_ary_push(flags, ID2SYM(rb_intern("wp")));
|
|
1003
|
+
#endif
|
|
1004
|
+
#ifdef UFFD_PAGEFAULT_FLAG_MINOR
|
|
1005
|
+
if (message->arg.pagefault.flags & UFFD_PAGEFAULT_FLAG_MINOR)
|
|
1006
|
+
rb_ary_push(flags, ID2SYM(rb_intern("minor")));
|
|
1007
|
+
#endif
|
|
1008
|
+
rb_ivar_set(event, rb_intern("@owner"), owner);
|
|
1009
|
+
rb_ivar_set(event, rb_intern("@address"), ULL2NUM(address));
|
|
1010
|
+
rb_ivar_set(event, rb_intern("@flags"), flags);
|
|
1011
|
+
rb_ivar_set(event, rb_intern("@page_size"), SIZET2NUM(page_size));
|
|
1012
|
+
#ifdef UFFD_FEATURE_THREAD_ID
|
|
1013
|
+
if (NIL_P(owner)) {
|
|
1014
|
+
rb_ivar_set(event, rb_intern("@thread_id"),
|
|
1015
|
+
UINT2NUM(message->arg.pagefault.feat.ptid));
|
|
1016
|
+
} else {
|
|
1017
|
+
uffd_data_t *data;
|
|
1018
|
+
TypedData_Get_Struct(owner, uffd_data_t, &uffd_type, data);
|
|
1019
|
+
rb_ivar_set(event, rb_intern("@thread_id"),
|
|
1020
|
+
data->enabled_features & UFFD_FEATURE_THREAD_ID ?
|
|
1021
|
+
UINT2NUM(message->arg.pagefault.feat.ptid) : Qnil);
|
|
1022
|
+
}
|
|
1023
|
+
#else
|
|
1024
|
+
rb_ivar_set(event, rb_intern("@thread_id"), Qnil);
|
|
1025
|
+
#endif
|
|
1026
|
+
return event;
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
static VALUE
|
|
1030
|
+
wrap_child_uffd(VALUE owner, int fd)
|
|
1031
|
+
{
|
|
1032
|
+
uffd_data_t *parent = get_uffd(owner);
|
|
1033
|
+
uffd_data_t *child;
|
|
1034
|
+
VALUE object = uffd_alloc(cUserfaultFD);
|
|
1035
|
+
TypedData_Get_Struct(object, uffd_data_t, &uffd_type, child);
|
|
1036
|
+
child->fd = fd;
|
|
1037
|
+
child->pid = getpid();
|
|
1038
|
+
child->features = parent->features;
|
|
1039
|
+
child->enabled_features = parent->enabled_features;
|
|
1040
|
+
child->ioctls = parent->ioctls;
|
|
1041
|
+
child->backend = parent->backend;
|
|
1042
|
+
fcntl(fd, F_SETFD, FD_CLOEXEC);
|
|
1043
|
+
return object;
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
static VALUE
|
|
1047
|
+
parse_event(VALUE owner, const struct uffd_msg *message)
|
|
1048
|
+
{
|
|
1049
|
+
VALUE event;
|
|
1050
|
+
switch (message->event) {
|
|
1051
|
+
case UFFD_EVENT_PAGEFAULT:
|
|
1052
|
+
return new_fault(owner, message);
|
|
1053
|
+
#ifdef UFFD_EVENT_FORK
|
|
1054
|
+
case UFFD_EVENT_FORK:
|
|
1055
|
+
event = new_event(cForkEvent);
|
|
1056
|
+
rb_ivar_set(event, rb_intern("@child_uffd"),
|
|
1057
|
+
wrap_child_uffd(owner, (int)message->arg.fork.ufd));
|
|
1058
|
+
return event;
|
|
1059
|
+
#endif
|
|
1060
|
+
#ifdef UFFD_EVENT_REMAP
|
|
1061
|
+
case UFFD_EVENT_REMAP:
|
|
1062
|
+
event = new_event(cRemapEvent);
|
|
1063
|
+
rb_ivar_set(event, rb_intern("@from"), ULL2NUM(message->arg.remap.from));
|
|
1064
|
+
rb_ivar_set(event, rb_intern("@to"), ULL2NUM(message->arg.remap.to));
|
|
1065
|
+
rb_ivar_set(event, rb_intern("@length"), ULL2NUM(message->arg.remap.len));
|
|
1066
|
+
return event;
|
|
1067
|
+
#endif
|
|
1068
|
+
#ifdef UFFD_EVENT_REMOVE
|
|
1069
|
+
case UFFD_EVENT_REMOVE:
|
|
1070
|
+
event = new_event(cRemoveEvent);
|
|
1071
|
+
rb_ivar_set(event, rb_intern("@start"), ULL2NUM(message->arg.remove.start));
|
|
1072
|
+
rb_ivar_set(event, rb_intern("@end"), ULL2NUM(message->arg.remove.end));
|
|
1073
|
+
return event;
|
|
1074
|
+
#endif
|
|
1075
|
+
#ifdef UFFD_EVENT_UNMAP
|
|
1076
|
+
case UFFD_EVENT_UNMAP:
|
|
1077
|
+
event = new_event(cUnmapEvent);
|
|
1078
|
+
rb_ivar_set(event, rb_intern("@start"), ULL2NUM(message->arg.remove.start));
|
|
1079
|
+
rb_ivar_set(event, rb_intern("@end"), ULL2NUM(message->arg.remove.end));
|
|
1080
|
+
return event;
|
|
1081
|
+
#endif
|
|
1082
|
+
default:
|
|
1083
|
+
return Qnil;
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
#endif
|
|
1087
|
+
|
|
1088
|
+
static VALUE
|
|
1089
|
+
uffd_read_events(int argc, VALUE *argv, VALUE self)
|
|
1090
|
+
{
|
|
1091
|
+
VALUE max_value = Qnil;
|
|
1092
|
+
uffd_data_t *data = get_uffd(self);
|
|
1093
|
+
size_t maximum = 16;
|
|
1094
|
+
rb_scan_args(argc, argv, "01", &max_value);
|
|
1095
|
+
if (!NIL_P(max_value)) maximum = NUM2SIZET(max_value);
|
|
1096
|
+
if (maximum == 0 || maximum > 1024)
|
|
1097
|
+
rb_raise(rb_eArgError, "max must be between 1 and 1024");
|
|
1098
|
+
#ifdef __linux__
|
|
1099
|
+
{
|
|
1100
|
+
struct uffd_msg *messages = ALLOC_N(struct uffd_msg, maximum);
|
|
1101
|
+
struct read_args args = {
|
|
1102
|
+
data->fd, messages, sizeof(struct uffd_msg) * maximum, 0, 0
|
|
1103
|
+
};
|
|
1104
|
+
VALUE result = rb_ary_new();
|
|
1105
|
+
size_t count, i;
|
|
1106
|
+
rb_thread_call_without_gvl(without_gvl_read, &args, RUBY_UBF_IO, NULL);
|
|
1107
|
+
if (args.result < 0) {
|
|
1108
|
+
xfree(messages);
|
|
1109
|
+
if (args.error == EAGAIN || args.error == EINTR) return result;
|
|
1110
|
+
rb_syserr_fail(args.error, "read(userfaultfd)");
|
|
1111
|
+
}
|
|
1112
|
+
if ((size_t)args.result % sizeof(struct uffd_msg) != 0) {
|
|
1113
|
+
xfree(messages);
|
|
1114
|
+
rb_raise(eError, "short userfaultfd message");
|
|
1115
|
+
}
|
|
1116
|
+
count = (size_t)args.result / sizeof(struct uffd_msg);
|
|
1117
|
+
for (i = 0; i < count; i++) {
|
|
1118
|
+
VALUE event = parse_event(self, &messages[i]);
|
|
1119
|
+
if (!NIL_P(event)) rb_ary_push(result, event);
|
|
1120
|
+
}
|
|
1121
|
+
xfree(messages);
|
|
1122
|
+
return result;
|
|
1123
|
+
}
|
|
1124
|
+
#else
|
|
1125
|
+
(void)data;
|
|
1126
|
+
raise_unsupported("userfaultfd is only available on Linux");
|
|
1127
|
+
#endif
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
static VALUE
|
|
1131
|
+
uffd_parse_message(VALUE klass, VALUE string)
|
|
1132
|
+
{
|
|
1133
|
+
(void)klass;
|
|
1134
|
+
StringValue(string);
|
|
1135
|
+
#ifdef __linux__
|
|
1136
|
+
if (RSTRING_LEN(string) != (long)sizeof(struct uffd_msg))
|
|
1137
|
+
rb_raise(rb_eArgError, "message has the wrong size");
|
|
1138
|
+
return parse_event(Qnil, (const struct uffd_msg *)RSTRING_PTR(string));
|
|
1139
|
+
#else
|
|
1140
|
+
raise_unsupported("userfaultfd messages are only available on Linux");
|
|
1141
|
+
#endif
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
static uffd_data_t *
|
|
1145
|
+
fault_owner(VALUE self)
|
|
1146
|
+
{
|
|
1147
|
+
return get_uffd(rb_ivar_get(self, rb_intern("@owner")));
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
#ifdef __linux__
|
|
1151
|
+
static uintptr_t
|
|
1152
|
+
fault_address(VALUE self)
|
|
1153
|
+
{
|
|
1154
|
+
return (uintptr_t)NUM2ULL(rb_ivar_get(self, rb_intern("@address")));
|
|
1155
|
+
}
|
|
1156
|
+
#endif
|
|
1157
|
+
|
|
1158
|
+
static size_t
|
|
1159
|
+
fault_page_size(VALUE self)
|
|
1160
|
+
{
|
|
1161
|
+
return NUM2SIZET(rb_ivar_get(self, rb_intern("@page_size")));
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
static int
|
|
1165
|
+
wake_requested(VALUE kwargs)
|
|
1166
|
+
{
|
|
1167
|
+
VALUE values[1] = {Qundef};
|
|
1168
|
+
ID keys[] = {id_wake};
|
|
1169
|
+
if (NIL_P(kwargs)) return 1;
|
|
1170
|
+
rb_get_kwargs(kwargs, keys, 0, 1, values);
|
|
1171
|
+
return values[0] == Qundef || values[0] != Qfalse;
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
static VALUE
|
|
1175
|
+
fault_copy(int argc, VALUE *argv, VALUE self)
|
|
1176
|
+
{
|
|
1177
|
+
VALUE string, kwargs;
|
|
1178
|
+
uffd_data_t *owner = fault_owner(self);
|
|
1179
|
+
size_t length, page_size = fault_page_size(self);
|
|
1180
|
+
char *buffer;
|
|
1181
|
+
int wake;
|
|
1182
|
+
rb_scan_args(argc, argv, "1:", &string, &kwargs);
|
|
1183
|
+
StringValue(string);
|
|
1184
|
+
length = (size_t)RSTRING_LEN(string);
|
|
1185
|
+
if (length == 0 || length % page_size != 0)
|
|
1186
|
+
rb_raise(rb_eArgError, "copy length must be a positive multiple of page_size");
|
|
1187
|
+
wake = wake_requested(kwargs);
|
|
1188
|
+
#ifdef __linux__
|
|
1189
|
+
{
|
|
1190
|
+
struct uffdio_copy request;
|
|
1191
|
+
buffer = xmalloc(length);
|
|
1192
|
+
memcpy(buffer, RSTRING_PTR(string), length);
|
|
1193
|
+
memset(&request, 0, sizeof(request));
|
|
1194
|
+
request.dst = (uint64_t)fault_address(self);
|
|
1195
|
+
request.src = (uint64_t)(uintptr_t)buffer;
|
|
1196
|
+
request.len = length;
|
|
1197
|
+
request.mode = wake ? 0 : UFFDIO_COPY_MODE_DONTWAKE;
|
|
1198
|
+
{
|
|
1199
|
+
struct ioctl_args args = {
|
|
1200
|
+
owner->fd, UFFDIO_COPY, &request, 0, 0
|
|
1201
|
+
};
|
|
1202
|
+
rb_thread_call_without_gvl(without_gvl_ioctl, &args, RUBY_UBF_IO, NULL);
|
|
1203
|
+
if (args.result < 0) {
|
|
1204
|
+
int saved_errno = args.error;
|
|
1205
|
+
xfree(buffer);
|
|
1206
|
+
rb_syserr_fail(saved_errno, "UFFDIO_COPY");
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
xfree(buffer);
|
|
1210
|
+
if (request.copy < 0) rb_syserr_fail((int)-request.copy, "UFFDIO_COPY");
|
|
1211
|
+
return LL2NUM(request.copy);
|
|
1212
|
+
}
|
|
1213
|
+
#else
|
|
1214
|
+
(void)owner; (void)buffer; (void)wake;
|
|
1215
|
+
raise_unsupported("userfaultfd is only available on Linux");
|
|
1216
|
+
#endif
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
static VALUE
|
|
1220
|
+
fault_zero(int argc, VALUE *argv, VALUE self)
|
|
1221
|
+
{
|
|
1222
|
+
VALUE kwargs;
|
|
1223
|
+
uffd_data_t *owner = fault_owner(self);
|
|
1224
|
+
int wake;
|
|
1225
|
+
rb_scan_args(argc, argv, "0:", &kwargs);
|
|
1226
|
+
wake = wake_requested(kwargs);
|
|
1227
|
+
#ifdef __linux__
|
|
1228
|
+
{
|
|
1229
|
+
struct uffdio_zeropage request;
|
|
1230
|
+
memset(&request, 0, sizeof(request));
|
|
1231
|
+
request.range.start = (uint64_t)fault_address(self);
|
|
1232
|
+
request.range.len = fault_page_size(self);
|
|
1233
|
+
request.mode = wake ? 0 : UFFDIO_ZEROPAGE_MODE_DONTWAKE;
|
|
1234
|
+
run_ioctl_without_gvl(owner->fd, UFFDIO_ZEROPAGE, &request, "UFFDIO_ZEROPAGE");
|
|
1235
|
+
if (request.zeropage < 0)
|
|
1236
|
+
rb_syserr_fail((int)-request.zeropage, "UFFDIO_ZEROPAGE");
|
|
1237
|
+
return LL2NUM(request.zeropage);
|
|
1238
|
+
}
|
|
1239
|
+
#else
|
|
1240
|
+
(void)owner; (void)wake;
|
|
1241
|
+
raise_unsupported("userfaultfd is only available on Linux");
|
|
1242
|
+
#endif
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
static VALUE
|
|
1246
|
+
fault_wake(VALUE self)
|
|
1247
|
+
{
|
|
1248
|
+
uffd_data_t *owner = fault_owner(self);
|
|
1249
|
+
#ifdef __linux__
|
|
1250
|
+
{
|
|
1251
|
+
struct uffdio_range range;
|
|
1252
|
+
range.start = (uint64_t)fault_address(self);
|
|
1253
|
+
range.len = fault_page_size(self);
|
|
1254
|
+
run_ioctl_without_gvl(owner->fd, UFFDIO_WAKE, &range, "UFFDIO_WAKE");
|
|
1255
|
+
}
|
|
1256
|
+
#else
|
|
1257
|
+
(void)owner;
|
|
1258
|
+
raise_unsupported("userfaultfd is only available on Linux");
|
|
1259
|
+
#endif
|
|
1260
|
+
return Qnil;
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
static VALUE
|
|
1264
|
+
fault_continue(int argc, VALUE *argv, VALUE self)
|
|
1265
|
+
{
|
|
1266
|
+
VALUE kwargs;
|
|
1267
|
+
uffd_data_t *owner = fault_owner(self);
|
|
1268
|
+
int wake;
|
|
1269
|
+
rb_scan_args(argc, argv, "0:", &kwargs);
|
|
1270
|
+
wake = wake_requested(kwargs);
|
|
1271
|
+
#if defined(__linux__) && defined(UFFDIO_CONTINUE)
|
|
1272
|
+
{
|
|
1273
|
+
struct uffdio_continue request;
|
|
1274
|
+
require_enabled_feature(owner,
|
|
1275
|
+
UFFD_FEATURE_MINOR_HUGETLBFS |
|
|
1276
|
+
UFFD_FEATURE_MINOR_SHMEM,
|
|
1277
|
+
"minor faults were not enabled");
|
|
1278
|
+
memset(&request, 0, sizeof(request));
|
|
1279
|
+
request.range.start = (uint64_t)fault_address(self);
|
|
1280
|
+
request.range.len = fault_page_size(self);
|
|
1281
|
+
request.mode = wake ? 0 : UFFDIO_CONTINUE_MODE_DONTWAKE;
|
|
1282
|
+
run_ioctl_without_gvl(owner->fd, UFFDIO_CONTINUE, &request, "UFFDIO_CONTINUE");
|
|
1283
|
+
if (request.mapped < 0)
|
|
1284
|
+
rb_syserr_fail((int)-request.mapped, "UFFDIO_CONTINUE");
|
|
1285
|
+
return LL2NUM(request.mapped);
|
|
1286
|
+
}
|
|
1287
|
+
#else
|
|
1288
|
+
(void)owner; (void)wake;
|
|
1289
|
+
raise_unsupported("UFFDIO_CONTINUE is unavailable on this platform");
|
|
1290
|
+
#endif
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
static VALUE
|
|
1294
|
+
fault_poison(int argc, VALUE *argv, VALUE self)
|
|
1295
|
+
{
|
|
1296
|
+
VALUE kwargs;
|
|
1297
|
+
uffd_data_t *owner = fault_owner(self);
|
|
1298
|
+
int wake;
|
|
1299
|
+
rb_scan_args(argc, argv, "0:", &kwargs);
|
|
1300
|
+
wake = wake_requested(kwargs);
|
|
1301
|
+
#if defined(__linux__) && defined(UFFDIO_POISON)
|
|
1302
|
+
{
|
|
1303
|
+
struct uffdio_poison request;
|
|
1304
|
+
require_enabled_feature(owner, UFFD_FEATURE_POISON,
|
|
1305
|
+
"page poisoning was not enabled");
|
|
1306
|
+
memset(&request, 0, sizeof(request));
|
|
1307
|
+
request.range.start = (uint64_t)fault_address(self);
|
|
1308
|
+
request.range.len = fault_page_size(self);
|
|
1309
|
+
request.mode = wake ? 0 : UFFDIO_POISON_MODE_DONTWAKE;
|
|
1310
|
+
run_ioctl_without_gvl(owner->fd, UFFDIO_POISON, &request, "UFFDIO_POISON");
|
|
1311
|
+
if (request.updated < 0)
|
|
1312
|
+
rb_syserr_fail((int)-request.updated, "UFFDIO_POISON");
|
|
1313
|
+
return LL2NUM(request.updated);
|
|
1314
|
+
}
|
|
1315
|
+
#else
|
|
1316
|
+
(void)owner; (void)wake;
|
|
1317
|
+
raise_unsupported("UFFDIO_POISON is unavailable on this platform");
|
|
1318
|
+
#endif
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
static VALUE
|
|
1322
|
+
fault_move(int argc, VALUE *argv, VALUE self)
|
|
1323
|
+
{
|
|
1324
|
+
VALUE source_object, kwargs;
|
|
1325
|
+
VALUE values[2] = {Qundef, Qundef};
|
|
1326
|
+
ID keys[] = {id_offset, id_wake};
|
|
1327
|
+
region_data_t *source;
|
|
1328
|
+
size_t offset = 0, page_size = fault_page_size(self);
|
|
1329
|
+
int wake = 1;
|
|
1330
|
+
uffd_data_t *owner;
|
|
1331
|
+
rb_scan_args(argc, argv, "1:", &source_object, &kwargs);
|
|
1332
|
+
source = get_region(source_object);
|
|
1333
|
+
if (!NIL_P(kwargs)) {
|
|
1334
|
+
rb_get_kwargs(kwargs, keys, 0, 2, values);
|
|
1335
|
+
if (values[0] != Qundef) offset = NUM2SIZET(values[0]);
|
|
1336
|
+
if (values[1] != Qundef) wake = values[1] != Qfalse;
|
|
1337
|
+
}
|
|
1338
|
+
if (offset % page_size != 0 || offset > source->size ||
|
|
1339
|
+
page_size > source->size - offset)
|
|
1340
|
+
rb_raise(rb_eArgError, "source offset must select a complete aligned page");
|
|
1341
|
+
owner = fault_owner(self);
|
|
1342
|
+
#if defined(__linux__) && defined(UFFDIO_MOVE)
|
|
1343
|
+
{
|
|
1344
|
+
struct uffdio_move request;
|
|
1345
|
+
require_enabled_feature(owner, UFFD_FEATURE_MOVE,
|
|
1346
|
+
"page move was not enabled");
|
|
1347
|
+
memset(&request, 0, sizeof(request));
|
|
1348
|
+
request.dst = (uint64_t)fault_address(self);
|
|
1349
|
+
request.src = (uint64_t)(uintptr_t)((char *)source->address + offset);
|
|
1350
|
+
request.len = page_size;
|
|
1351
|
+
request.mode = wake ? 0 : UFFDIO_MOVE_MODE_DONTWAKE;
|
|
1352
|
+
run_ioctl_without_gvl(owner->fd, UFFDIO_MOVE, &request, "UFFDIO_MOVE");
|
|
1353
|
+
if (request.move < 0) rb_syserr_fail((int)-request.move, "UFFDIO_MOVE");
|
|
1354
|
+
return LL2NUM(request.move);
|
|
1355
|
+
}
|
|
1356
|
+
#else
|
|
1357
|
+
(void)owner; (void)wake;
|
|
1358
|
+
raise_unsupported("UFFDIO_MOVE is unavailable on this platform");
|
|
1359
|
+
#endif
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
#ifdef __linux__
|
|
1363
|
+
static int
|
|
1364
|
+
fill_from_file(native_handler_data_t *data, void *buffer, off_t offset)
|
|
1365
|
+
{
|
|
1366
|
+
size_t done = 0;
|
|
1367
|
+
memset(buffer, 0, data->page_size);
|
|
1368
|
+
while (done < data->page_size) {
|
|
1369
|
+
ssize_t count = pread(data->source_fd, (char *)buffer + done,
|
|
1370
|
+
data->page_size - done, offset + (off_t)done);
|
|
1371
|
+
if (count == 0) break;
|
|
1372
|
+
if (count < 0) {
|
|
1373
|
+
if (errno == EINTR) continue;
|
|
1374
|
+
return -1;
|
|
1375
|
+
}
|
|
1376
|
+
done += (size_t)count;
|
|
1377
|
+
}
|
|
1378
|
+
return 0;
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
static int
|
|
1382
|
+
native_resolve_fault(native_handler_data_t *data, const struct uffd_msg *message,
|
|
1383
|
+
void *buffer)
|
|
1384
|
+
{
|
|
1385
|
+
uintptr_t address = (uintptr_t)message->arg.pagefault.address;
|
|
1386
|
+
uintptr_t offset;
|
|
1387
|
+
address &= ~((uintptr_t)data->page_size - 1);
|
|
1388
|
+
if (address < data->start || address >= data->start + data->length) {
|
|
1389
|
+
errno = EFAULT;
|
|
1390
|
+
return -1;
|
|
1391
|
+
}
|
|
1392
|
+
offset = address - data->start;
|
|
1393
|
+
if (data->mode == 1) {
|
|
1394
|
+
struct uffdio_zeropage request;
|
|
1395
|
+
int result;
|
|
1396
|
+
memset(&request, 0, sizeof(request));
|
|
1397
|
+
request.range.start = address;
|
|
1398
|
+
request.range.len = data->page_size;
|
|
1399
|
+
result = ioctl(data->uffd_fd, UFFDIO_ZEROPAGE, &request);
|
|
1400
|
+
if (result < 0) return -1;
|
|
1401
|
+
if (request.zeropage < 0) {
|
|
1402
|
+
errno = (int)-request.zeropage;
|
|
1403
|
+
return -1;
|
|
1404
|
+
}
|
|
1405
|
+
if ((size_t)request.zeropage != data->page_size) {
|
|
1406
|
+
errno = EIO;
|
|
1407
|
+
return -1;
|
|
1408
|
+
}
|
|
1409
|
+
return 0;
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
if (data->mode == 2 && fill_from_file(data, buffer, (off_t)offset) < 0)
|
|
1413
|
+
return -1;
|
|
1414
|
+
if (data->mode == 3)
|
|
1415
|
+
memcpy(buffer, (const char *)data->source_address + offset, data->page_size);
|
|
1416
|
+
|
|
1417
|
+
{
|
|
1418
|
+
struct uffdio_copy request;
|
|
1419
|
+
int result;
|
|
1420
|
+
memset(&request, 0, sizeof(request));
|
|
1421
|
+
request.dst = address;
|
|
1422
|
+
request.src = (uint64_t)(uintptr_t)buffer;
|
|
1423
|
+
request.len = data->page_size;
|
|
1424
|
+
result = ioctl(data->uffd_fd, UFFDIO_COPY, &request);
|
|
1425
|
+
if (result < 0) return -1;
|
|
1426
|
+
if (request.copy < 0) {
|
|
1427
|
+
errno = (int)-request.copy;
|
|
1428
|
+
return -1;
|
|
1429
|
+
}
|
|
1430
|
+
if ((size_t)request.copy != data->page_size) {
|
|
1431
|
+
errno = EIO;
|
|
1432
|
+
return -1;
|
|
1433
|
+
}
|
|
1434
|
+
return 0;
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
static void *
|
|
1439
|
+
native_handler_main(void *ptr)
|
|
1440
|
+
{
|
|
1441
|
+
native_handler_data_t *data = ptr;
|
|
1442
|
+
struct pollfd fds[2];
|
|
1443
|
+
struct uffd_msg messages[16];
|
|
1444
|
+
void *buffer = NULL;
|
|
1445
|
+
if (data->mode != 1) {
|
|
1446
|
+
buffer = malloc(data->page_size);
|
|
1447
|
+
if (!buffer) {
|
|
1448
|
+
data->error = ENOMEM;
|
|
1449
|
+
data->running = 0;
|
|
1450
|
+
return NULL;
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
fds[0].fd = data->uffd_fd;
|
|
1454
|
+
fds[0].events = POLLIN;
|
|
1455
|
+
fds[1].fd = data->stop_pipe[0];
|
|
1456
|
+
fds[1].events = POLLIN;
|
|
1457
|
+
while (1) {
|
|
1458
|
+
int ready = poll(fds, 2, -1);
|
|
1459
|
+
if (ready < 0) {
|
|
1460
|
+
if (errno == EINTR) continue;
|
|
1461
|
+
data->error = errno;
|
|
1462
|
+
break;
|
|
1463
|
+
}
|
|
1464
|
+
if (fds[1].revents) break;
|
|
1465
|
+
if (fds[0].revents & (POLLERR | POLLHUP | POLLNVAL)) {
|
|
1466
|
+
data->error = EIO;
|
|
1467
|
+
break;
|
|
1468
|
+
}
|
|
1469
|
+
if (fds[0].revents & POLLIN) {
|
|
1470
|
+
ssize_t count = read(data->uffd_fd, messages, sizeof(messages));
|
|
1471
|
+
size_t i, length;
|
|
1472
|
+
if (count < 0) {
|
|
1473
|
+
if (errno == EAGAIN || errno == EINTR) continue;
|
|
1474
|
+
data->error = errno;
|
|
1475
|
+
break;
|
|
1476
|
+
}
|
|
1477
|
+
if ((size_t)count % sizeof(struct uffd_msg) != 0) {
|
|
1478
|
+
data->error = EIO;
|
|
1479
|
+
break;
|
|
1480
|
+
}
|
|
1481
|
+
length = (size_t)count / sizeof(struct uffd_msg);
|
|
1482
|
+
for (i = 0; i < length; i++) {
|
|
1483
|
+
if (messages[i].event == UFFD_EVENT_PAGEFAULT) {
|
|
1484
|
+
if (native_resolve_fault(data, &messages[i], buffer) < 0) {
|
|
1485
|
+
data->error = errno;
|
|
1486
|
+
goto done;
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
#ifdef UFFD_EVENT_FORK
|
|
1490
|
+
else if (messages[i].event == UFFD_EVENT_FORK) {
|
|
1491
|
+
close((int)messages[i].arg.fork.ufd);
|
|
1492
|
+
}
|
|
1493
|
+
#endif
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
done:
|
|
1498
|
+
free(buffer);
|
|
1499
|
+
data->running = 0;
|
|
1500
|
+
return NULL;
|
|
1501
|
+
}
|
|
1502
|
+
#endif
|
|
1503
|
+
|
|
1504
|
+
struct join_args {
|
|
1505
|
+
pthread_t thread;
|
|
1506
|
+
int result;
|
|
1507
|
+
};
|
|
1508
|
+
|
|
1509
|
+
static void
|
|
1510
|
+
signal_stop_pipe(int fd)
|
|
1511
|
+
{
|
|
1512
|
+
char byte = 0;
|
|
1513
|
+
ssize_t result;
|
|
1514
|
+
if (fd < 0) return;
|
|
1515
|
+
do {
|
|
1516
|
+
result = write(fd, &byte, 1);
|
|
1517
|
+
} while (result < 0 && errno == EINTR);
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
static void *
|
|
1521
|
+
without_gvl_join(void *ptr)
|
|
1522
|
+
{
|
|
1523
|
+
struct join_args *args = ptr;
|
|
1524
|
+
args->result = pthread_join(args->thread, NULL);
|
|
1525
|
+
return NULL;
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
static void
|
|
1529
|
+
native_handler_release_regions(native_handler_data_t *data)
|
|
1530
|
+
{
|
|
1531
|
+
if (data->registered_region) {
|
|
1532
|
+
data->registered_region->busy--;
|
|
1533
|
+
data->registered_region = NULL;
|
|
1534
|
+
}
|
|
1535
|
+
if (data->source_region) {
|
|
1536
|
+
data->source_region->busy--;
|
|
1537
|
+
data->source_region = NULL;
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
static void
|
|
1542
|
+
native_handler_stop_and_join(native_handler_data_t *data)
|
|
1543
|
+
{
|
|
1544
|
+
struct join_args args;
|
|
1545
|
+
if (data->pid != getpid()) {
|
|
1546
|
+
data->joined = 1;
|
|
1547
|
+
data->running = 0;
|
|
1548
|
+
native_handler_release_regions(data);
|
|
1549
|
+
return;
|
|
1550
|
+
}
|
|
1551
|
+
if (data->joined || !data->started) return;
|
|
1552
|
+
signal_stop_pipe(data->stop_pipe[1]);
|
|
1553
|
+
args.thread = data->thread;
|
|
1554
|
+
args.result = 0;
|
|
1555
|
+
if (ruby_native_thread_p())
|
|
1556
|
+
rb_thread_call_without_gvl(without_gvl_join, &args, RUBY_UBF_IO, NULL);
|
|
1557
|
+
else
|
|
1558
|
+
args.result = pthread_join(args.thread, NULL);
|
|
1559
|
+
data->joined = 1;
|
|
1560
|
+
data->running = 0;
|
|
1561
|
+
native_handler_release_regions(data);
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
static VALUE
|
|
1565
|
+
uffd_start_native_handler(VALUE self, VALUE mode, VALUE source)
|
|
1566
|
+
{
|
|
1567
|
+
uffd_data_t *owner = get_uffd(self);
|
|
1568
|
+
VALUE regions = rb_ivar_get(self, rb_intern("@registered_regions"));
|
|
1569
|
+
VALUE registered_object;
|
|
1570
|
+
region_data_t *registered;
|
|
1571
|
+
native_handler_data_t *data;
|
|
1572
|
+
VALUE object;
|
|
1573
|
+
if (NIL_P(regions) || RARRAY_LEN(regions) != 1)
|
|
1574
|
+
rb_raise(eError, "native handlers require exactly one registered region");
|
|
1575
|
+
registered_object = rb_ary_entry(regions, 0);
|
|
1576
|
+
registered = get_region(registered_object);
|
|
1577
|
+
object = native_handler_alloc(cNativeHandler);
|
|
1578
|
+
TypedData_Get_Struct(object, native_handler_data_t, &native_handler_type, data);
|
|
1579
|
+
data->owner = self;
|
|
1580
|
+
data->source = source;
|
|
1581
|
+
data->uffd_fd = owner->fd;
|
|
1582
|
+
data->start = (uintptr_t)registered->address;
|
|
1583
|
+
data->length = registered->size;
|
|
1584
|
+
data->page_size = (size_t)sysconf(_SC_PAGESIZE);
|
|
1585
|
+
if (mode == ID2SYM(rb_intern("zero_fill"))) {
|
|
1586
|
+
data->mode = 1;
|
|
1587
|
+
} else if (mode == ID2SYM(rb_intern("backing_file"))) {
|
|
1588
|
+
VALUE fd;
|
|
1589
|
+
if (NIL_P(source)) rb_raise(rb_eArgError, "io is required for backing_file");
|
|
1590
|
+
fd = rb_funcall(source, rb_intern("fileno"), 0);
|
|
1591
|
+
data->source_fd = dup(NUM2INT(fd));
|
|
1592
|
+
if (data->source_fd < 0) rb_syserr_fail(errno, "dup");
|
|
1593
|
+
data->mode = 2;
|
|
1594
|
+
} else if (mode == ID2SYM(rb_intern("prefilled"))) {
|
|
1595
|
+
region_data_t *region;
|
|
1596
|
+
if (NIL_P(source)) rb_raise(rb_eArgError, "source is required for prefilled");
|
|
1597
|
+
if (source == registered_object)
|
|
1598
|
+
rb_raise(rb_eArgError, "source must differ from the registered region");
|
|
1599
|
+
region = get_region(source);
|
|
1600
|
+
if (region->size < data->length)
|
|
1601
|
+
rb_raise(rb_eArgError, "source region is smaller than registered region");
|
|
1602
|
+
data->source_address = region->address;
|
|
1603
|
+
data->mode = 3;
|
|
1604
|
+
} else {
|
|
1605
|
+
rb_raise(rb_eArgError, "unknown native handler mode");
|
|
1606
|
+
}
|
|
1607
|
+
#ifdef __linux__
|
|
1608
|
+
if (pipe(data->stop_pipe) < 0) rb_syserr_fail(errno, "pipe");
|
|
1609
|
+
fcntl(data->stop_pipe[0], F_SETFD, FD_CLOEXEC);
|
|
1610
|
+
fcntl(data->stop_pipe[1], F_SETFD, FD_CLOEXEC);
|
|
1611
|
+
data->registered_region = registered;
|
|
1612
|
+
registered->busy++;
|
|
1613
|
+
if (data->mode == 3) {
|
|
1614
|
+
data->source_region = get_region(source);
|
|
1615
|
+
data->source_region->busy++;
|
|
1616
|
+
}
|
|
1617
|
+
data->running = 1;
|
|
1618
|
+
if (pthread_create(&data->thread, NULL, native_handler_main, data) != 0) {
|
|
1619
|
+
data->running = 0;
|
|
1620
|
+
native_handler_release_regions(data);
|
|
1621
|
+
rb_raise(eError, "failed to start native handler thread");
|
|
1622
|
+
}
|
|
1623
|
+
data->started = 1;
|
|
1624
|
+
#else
|
|
1625
|
+
raise_unsupported("native handlers are only available on Linux");
|
|
1626
|
+
#endif
|
|
1627
|
+
return object;
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
static VALUE
|
|
1631
|
+
native_handler_stop(VALUE self)
|
|
1632
|
+
{
|
|
1633
|
+
native_handler_data_t *data;
|
|
1634
|
+
int wrong_process;
|
|
1635
|
+
TypedData_Get_Struct(self, native_handler_data_t, &native_handler_type, data);
|
|
1636
|
+
wrong_process = data->pid != getpid();
|
|
1637
|
+
native_handler_stop_and_join(data);
|
|
1638
|
+
if (wrong_process) rb_raise(eError, "handler cannot be used after fork");
|
|
1639
|
+
if (data->error) rb_syserr_fail(data->error, "userfaultfd handler");
|
|
1640
|
+
return self;
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
static VALUE
|
|
1644
|
+
native_handler_running_p(VALUE self)
|
|
1645
|
+
{
|
|
1646
|
+
native_handler_data_t *data;
|
|
1647
|
+
TypedData_Get_Struct(self, native_handler_data_t, &native_handler_type, data);
|
|
1648
|
+
return data->running ? Qtrue : Qfalse;
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1651
|
+
#ifdef __linux__
|
|
1652
|
+
struct event_record {
|
|
1653
|
+
int source_fd;
|
|
1654
|
+
struct uffd_msg message;
|
|
1655
|
+
};
|
|
1656
|
+
|
|
1657
|
+
static void
|
|
1658
|
+
event_reader_remove_fd(event_reader_data_t *data, size_t index)
|
|
1659
|
+
{
|
|
1660
|
+
size_t i;
|
|
1661
|
+
if (data->owned[index]) close(data->fds[index]);
|
|
1662
|
+
for (i = index + 1; i < data->fd_count; i++) {
|
|
1663
|
+
data->fds[i - 1] = data->fds[i];
|
|
1664
|
+
data->owned[i - 1] = data->owned[i];
|
|
1665
|
+
}
|
|
1666
|
+
data->fd_count--;
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
static int
|
|
1670
|
+
event_reader_emit(event_reader_data_t *data, int source_fd,
|
|
1671
|
+
const struct uffd_msg *message)
|
|
1672
|
+
{
|
|
1673
|
+
struct event_record record;
|
|
1674
|
+
ssize_t written;
|
|
1675
|
+
record.source_fd = source_fd;
|
|
1676
|
+
record.message = *message;
|
|
1677
|
+
do {
|
|
1678
|
+
written = write(data->output_pipe[1], &record, sizeof(record));
|
|
1679
|
+
} while (written < 0 && errno == EINTR);
|
|
1680
|
+
if (written == (ssize_t)sizeof(record)) return 0;
|
|
1681
|
+
data->error = written < 0 && errno != EAGAIN ? errno : ENOBUFS;
|
|
1682
|
+
return -1;
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
static void *
|
|
1686
|
+
event_reader_main(void *ptr)
|
|
1687
|
+
{
|
|
1688
|
+
event_reader_data_t *data = ptr;
|
|
1689
|
+
/* ponytail: 64 monitored processes; use epoll if larger fan-out is needed. */
|
|
1690
|
+
struct pollfd poll_fds[EVENT_READER_MAX_FDS + 1];
|
|
1691
|
+
while (1) {
|
|
1692
|
+
size_t i, polled_count = data->fd_count;
|
|
1693
|
+
int ready;
|
|
1694
|
+
for (i = 0; i < polled_count; i++) {
|
|
1695
|
+
poll_fds[i].fd = data->fds[i];
|
|
1696
|
+
poll_fds[i].events = POLLIN;
|
|
1697
|
+
poll_fds[i].revents = 0;
|
|
1698
|
+
}
|
|
1699
|
+
poll_fds[polled_count].fd = data->stop_pipe[0];
|
|
1700
|
+
poll_fds[polled_count].events = POLLIN;
|
|
1701
|
+
poll_fds[polled_count].revents = 0;
|
|
1702
|
+
ready = poll(poll_fds, polled_count + 1, -1);
|
|
1703
|
+
if (ready < 0) {
|
|
1704
|
+
if (errno == EINTR) continue;
|
|
1705
|
+
data->error = errno;
|
|
1706
|
+
break;
|
|
1707
|
+
}
|
|
1708
|
+
if (poll_fds[polled_count].revents) break;
|
|
1709
|
+
for (i = 0; i < polled_count; i++) {
|
|
1710
|
+
if (poll_fds[i].revents & (POLLERR | POLLHUP | POLLNVAL)) {
|
|
1711
|
+
if (i == 0) {
|
|
1712
|
+
data->error = EIO;
|
|
1713
|
+
goto done;
|
|
1714
|
+
}
|
|
1715
|
+
event_reader_remove_fd(data, i);
|
|
1716
|
+
goto repoll;
|
|
1717
|
+
}
|
|
1718
|
+
if (poll_fds[i].revents & POLLIN) {
|
|
1719
|
+
struct uffd_msg messages[16];
|
|
1720
|
+
ssize_t count = read(data->fds[i], messages, sizeof(messages));
|
|
1721
|
+
size_t j, length;
|
|
1722
|
+
if (count < 0) {
|
|
1723
|
+
if (errno == EAGAIN || errno == EINTR) {
|
|
1724
|
+
continue;
|
|
1725
|
+
}
|
|
1726
|
+
data->error = errno;
|
|
1727
|
+
goto done;
|
|
1728
|
+
}
|
|
1729
|
+
if ((size_t)count % sizeof(struct uffd_msg) != 0) {
|
|
1730
|
+
data->error = EIO;
|
|
1731
|
+
goto done;
|
|
1732
|
+
}
|
|
1733
|
+
length = (size_t)count / sizeof(struct uffd_msg);
|
|
1734
|
+
for (j = 0; j < length; j++) {
|
|
1735
|
+
#ifdef UFFD_EVENT_FORK
|
|
1736
|
+
if (messages[j].event == UFFD_EVENT_FORK) {
|
|
1737
|
+
int child_fd = (int)messages[j].arg.fork.ufd;
|
|
1738
|
+
if (data->fd_count == EVENT_READER_MAX_FDS) {
|
|
1739
|
+
close(child_fd);
|
|
1740
|
+
data->error = EMFILE;
|
|
1741
|
+
goto done;
|
|
1742
|
+
}
|
|
1743
|
+
fcntl(child_fd, F_SETFD, FD_CLOEXEC);
|
|
1744
|
+
fcntl(child_fd, F_SETFL,
|
|
1745
|
+
fcntl(child_fd, F_GETFL) | O_NONBLOCK);
|
|
1746
|
+
data->fds[data->fd_count] = child_fd;
|
|
1747
|
+
data->owned[data->fd_count] = 1;
|
|
1748
|
+
data->fd_count++;
|
|
1749
|
+
}
|
|
1750
|
+
#endif
|
|
1751
|
+
if (event_reader_emit(data, data->fds[i], &messages[j]) < 0)
|
|
1752
|
+
goto done;
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
repoll:
|
|
1757
|
+
;
|
|
1758
|
+
}
|
|
1759
|
+
done:
|
|
1760
|
+
data->running = 0;
|
|
1761
|
+
close(data->output_pipe[1]);
|
|
1762
|
+
data->output_pipe[1] = -1;
|
|
1763
|
+
return NULL;
|
|
1764
|
+
}
|
|
1765
|
+
#endif
|
|
1766
|
+
|
|
1767
|
+
static void
|
|
1768
|
+
event_reader_stop_and_join(event_reader_data_t *data)
|
|
1769
|
+
{
|
|
1770
|
+
struct join_args args;
|
|
1771
|
+
if (data->pid != getpid()) {
|
|
1772
|
+
data->joined = 1;
|
|
1773
|
+
data->running = 0;
|
|
1774
|
+
return;
|
|
1775
|
+
}
|
|
1776
|
+
if (data->joined || !data->started) return;
|
|
1777
|
+
signal_stop_pipe(data->stop_pipe[1]);
|
|
1778
|
+
args.thread = data->thread;
|
|
1779
|
+
args.result = 0;
|
|
1780
|
+
if (ruby_native_thread_p())
|
|
1781
|
+
rb_thread_call_without_gvl(without_gvl_join, &args, RUBY_UBF_IO, NULL);
|
|
1782
|
+
else
|
|
1783
|
+
args.result = pthread_join(args.thread, NULL);
|
|
1784
|
+
data->joined = 1;
|
|
1785
|
+
data->running = 0;
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
static VALUE
|
|
1789
|
+
uffd_start_event_reader(VALUE self)
|
|
1790
|
+
{
|
|
1791
|
+
uffd_data_t *owner = get_uffd(self);
|
|
1792
|
+
event_reader_data_t *data;
|
|
1793
|
+
VALUE object = event_reader_alloc(cEventReader);
|
|
1794
|
+
TypedData_Get_Struct(object, event_reader_data_t, &event_reader_type, data);
|
|
1795
|
+
data->owner = self;
|
|
1796
|
+
data->fds[0] = owner->fd;
|
|
1797
|
+
data->owned[0] = 0;
|
|
1798
|
+
data->fd_count = 1;
|
|
1799
|
+
rb_ivar_set(object, rb_intern("@owners"), rb_hash_new());
|
|
1800
|
+
rb_hash_aset(rb_ivar_get(object, rb_intern("@owners")),
|
|
1801
|
+
INT2NUM(owner->fd), self);
|
|
1802
|
+
#ifdef __linux__
|
|
1803
|
+
if (pipe(data->stop_pipe) < 0 || pipe(data->output_pipe) < 0)
|
|
1804
|
+
rb_syserr_fail(errno, "pipe");
|
|
1805
|
+
fcntl(data->stop_pipe[0], F_SETFD, FD_CLOEXEC);
|
|
1806
|
+
fcntl(data->stop_pipe[1], F_SETFD, FD_CLOEXEC);
|
|
1807
|
+
fcntl(data->output_pipe[0], F_SETFD, FD_CLOEXEC);
|
|
1808
|
+
fcntl(data->output_pipe[1], F_SETFD, FD_CLOEXEC);
|
|
1809
|
+
fcntl(data->output_pipe[0], F_SETFL,
|
|
1810
|
+
fcntl(data->output_pipe[0], F_GETFL) | O_NONBLOCK);
|
|
1811
|
+
fcntl(data->output_pipe[1], F_SETFL,
|
|
1812
|
+
fcntl(data->output_pipe[1], F_GETFL) | O_NONBLOCK);
|
|
1813
|
+
data->running = 1;
|
|
1814
|
+
if (pthread_create(&data->thread, NULL, event_reader_main, data) != 0) {
|
|
1815
|
+
data->running = 0;
|
|
1816
|
+
rb_raise(eError, "failed to start event reader thread");
|
|
1817
|
+
}
|
|
1818
|
+
data->started = 1;
|
|
1819
|
+
#else
|
|
1820
|
+
raise_unsupported("event readers are only available on Linux");
|
|
1821
|
+
#endif
|
|
1822
|
+
return object;
|
|
1823
|
+
}
|
|
1824
|
+
|
|
1825
|
+
static VALUE
|
|
1826
|
+
event_reader_fileno(VALUE self)
|
|
1827
|
+
{
|
|
1828
|
+
event_reader_data_t *data;
|
|
1829
|
+
TypedData_Get_Struct(self, event_reader_data_t, &event_reader_type, data);
|
|
1830
|
+
if (data->output_pipe[0] < 0) rb_raise(eError, "closed event reader");
|
|
1831
|
+
return INT2NUM(data->output_pipe[0]);
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
static VALUE
|
|
1835
|
+
event_reader_read_events(int argc, VALUE *argv, VALUE self)
|
|
1836
|
+
{
|
|
1837
|
+
VALUE max_value = Qnil;
|
|
1838
|
+
event_reader_data_t *data;
|
|
1839
|
+
size_t maximum = 16;
|
|
1840
|
+
rb_scan_args(argc, argv, "01", &max_value);
|
|
1841
|
+
if (!NIL_P(max_value)) maximum = NUM2SIZET(max_value);
|
|
1842
|
+
if (maximum == 0 || maximum > 1024)
|
|
1843
|
+
rb_raise(rb_eArgError, "max must be between 1 and 1024");
|
|
1844
|
+
TypedData_Get_Struct(self, event_reader_data_t, &event_reader_type, data);
|
|
1845
|
+
#ifdef __linux__
|
|
1846
|
+
{
|
|
1847
|
+
struct event_record *records = ALLOC_N(struct event_record, maximum);
|
|
1848
|
+
ssize_t count = read(data->output_pipe[0], records,
|
|
1849
|
+
sizeof(struct event_record) * maximum);
|
|
1850
|
+
VALUE result = rb_ary_new();
|
|
1851
|
+
VALUE owners = rb_ivar_get(self, rb_intern("@owners"));
|
|
1852
|
+
size_t length, i;
|
|
1853
|
+
if (count < 0) {
|
|
1854
|
+
xfree(records);
|
|
1855
|
+
if (errno == EAGAIN || errno == EINTR) return result;
|
|
1856
|
+
rb_syserr_fail(errno, "read(userfaultfd event pipe)");
|
|
1857
|
+
}
|
|
1858
|
+
if (count == 0) {
|
|
1859
|
+
xfree(records);
|
|
1860
|
+
if (data->error) rb_syserr_fail(data->error, "userfaultfd event reader");
|
|
1861
|
+
return result;
|
|
1862
|
+
}
|
|
1863
|
+
if ((size_t)count % sizeof(struct event_record) != 0) {
|
|
1864
|
+
xfree(records);
|
|
1865
|
+
rb_raise(eError, "short event-reader record");
|
|
1866
|
+
}
|
|
1867
|
+
length = (size_t)count / sizeof(struct event_record);
|
|
1868
|
+
for (i = 0; i < length; i++) {
|
|
1869
|
+
VALUE owner = rb_hash_aref(owners, INT2NUM(records[i].source_fd));
|
|
1870
|
+
VALUE event;
|
|
1871
|
+
if (NIL_P(owner)) {
|
|
1872
|
+
xfree(records);
|
|
1873
|
+
rb_raise(eError, "unknown userfaultfd event source");
|
|
1874
|
+
}
|
|
1875
|
+
#ifdef UFFD_EVENT_FORK
|
|
1876
|
+
if (records[i].message.event == UFFD_EVENT_FORK) {
|
|
1877
|
+
int raw_fd = (int)records[i].message.arg.fork.ufd;
|
|
1878
|
+
int duplicated = dup(raw_fd);
|
|
1879
|
+
if (duplicated < 0) {
|
|
1880
|
+
xfree(records);
|
|
1881
|
+
rb_syserr_fail(errno, "dup(child userfaultfd)");
|
|
1882
|
+
}
|
|
1883
|
+
records[i].message.arg.fork.ufd = (uint32_t)duplicated;
|
|
1884
|
+
event = parse_event(owner, &records[i].message);
|
|
1885
|
+
rb_hash_aset(owners, INT2NUM(raw_fd),
|
|
1886
|
+
rb_ivar_get(event, rb_intern("@child_uffd")));
|
|
1887
|
+
} else
|
|
1888
|
+
#endif
|
|
1889
|
+
{
|
|
1890
|
+
event = parse_event(owner, &records[i].message);
|
|
1891
|
+
}
|
|
1892
|
+
if (!NIL_P(event)) rb_ary_push(result, event);
|
|
1893
|
+
}
|
|
1894
|
+
xfree(records);
|
|
1895
|
+
return result;
|
|
1896
|
+
}
|
|
1897
|
+
#else
|
|
1898
|
+
raise_unsupported("event readers are only available on Linux");
|
|
1899
|
+
#endif
|
|
1900
|
+
}
|
|
1901
|
+
|
|
1902
|
+
static VALUE
|
|
1903
|
+
event_reader_stop(VALUE self)
|
|
1904
|
+
{
|
|
1905
|
+
event_reader_data_t *data;
|
|
1906
|
+
TypedData_Get_Struct(self, event_reader_data_t, &event_reader_type, data);
|
|
1907
|
+
if (data->pid != getpid()) rb_raise(eError, "event reader cannot be used after fork");
|
|
1908
|
+
event_reader_stop_and_join(data);
|
|
1909
|
+
event_reader_close_owned(data);
|
|
1910
|
+
if (data->error) rb_syserr_fail(data->error, "userfaultfd event reader");
|
|
1911
|
+
return self;
|
|
1912
|
+
}
|
|
1913
|
+
|
|
1914
|
+
static VALUE
|
|
1915
|
+
event_reader_running_p(VALUE self)
|
|
1916
|
+
{
|
|
1917
|
+
event_reader_data_t *data;
|
|
1918
|
+
TypedData_Get_Struct(self, event_reader_data_t, &event_reader_type, data);
|
|
1919
|
+
return data->running ? Qtrue : Qfalse;
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
void
|
|
1923
|
+
Init_userfaultfd(void)
|
|
1924
|
+
{
|
|
1925
|
+
cUserfaultFD = rb_define_class("UserfaultFD", rb_cObject);
|
|
1926
|
+
eError = rb_const_get(cUserfaultFD, rb_intern("Error"));
|
|
1927
|
+
eUnsupportedError = rb_const_get(cUserfaultFD, rb_intern("UnsupportedError"));
|
|
1928
|
+
cRegion = rb_define_class_under(cUserfaultFD, "Region", rb_cObject);
|
|
1929
|
+
|
|
1930
|
+
id_size = rb_intern("size");
|
|
1931
|
+
id_shared = rb_intern("shared");
|
|
1932
|
+
id_huge = rb_intern("huge");
|
|
1933
|
+
id_features = rb_intern("features");
|
|
1934
|
+
id_user_mode_only = rb_intern("user_mode_only");
|
|
1935
|
+
id_mode = rb_intern("mode");
|
|
1936
|
+
id_enabled = rb_intern("enabled");
|
|
1937
|
+
id_wake = rb_intern("wake");
|
|
1938
|
+
id_offset = rb_intern("offset");
|
|
1939
|
+
|
|
1940
|
+
rb_define_alloc_func(cUserfaultFD, uffd_alloc);
|
|
1941
|
+
rb_define_method(cUserfaultFD, "initialize", uffd_initialize, -1);
|
|
1942
|
+
rb_define_singleton_method(cUserfaultFD, "supported?", uffd_supported, -1);
|
|
1943
|
+
rb_define_singleton_method(cUserfaultFD, "features", uffd_features, -1);
|
|
1944
|
+
rb_define_private_method(rb_singleton_class(cUserfaultFD), "parse_message",
|
|
1945
|
+
uffd_parse_message, 1);
|
|
1946
|
+
rb_define_method(cUserfaultFD, "features", uffd_instance_features, 0);
|
|
1947
|
+
rb_define_method(cUserfaultFD, "enabled_features", uffd_enabled_features, 0);
|
|
1948
|
+
rb_define_method(cUserfaultFD, "backend", uffd_backend, 0);
|
|
1949
|
+
rb_define_method(cUserfaultFD, "fileno", uffd_fileno, 0);
|
|
1950
|
+
rb_define_method(cUserfaultFD, "close", uffd_close, 0);
|
|
1951
|
+
rb_define_method(cUserfaultFD, "closed?", uffd_closed_p, 0);
|
|
1952
|
+
rb_define_method(cUserfaultFD, "register", uffd_register, -1);
|
|
1953
|
+
rb_define_method(cUserfaultFD, "unregister", uffd_unregister, 1);
|
|
1954
|
+
rb_define_method(cUserfaultFD, "writeprotect", uffd_writeprotect, -1);
|
|
1955
|
+
rb_define_method(cUserfaultFD, "read_events", uffd_read_events, -1);
|
|
1956
|
+
rb_define_private_method(cUserfaultFD, "start_native_handler",
|
|
1957
|
+
uffd_start_native_handler, 2);
|
|
1958
|
+
rb_define_private_method(cUserfaultFD, "start_event_reader",
|
|
1959
|
+
uffd_start_event_reader, 0);
|
|
1960
|
+
|
|
1961
|
+
rb_define_alloc_func(cRegion, region_alloc);
|
|
1962
|
+
rb_define_method(cRegion, "initialize", region_initialize, -1);
|
|
1963
|
+
rb_define_method(cRegion, "address", region_address, 0);
|
|
1964
|
+
rb_define_method(cRegion, "to_ptr", region_address, 0);
|
|
1965
|
+
rb_define_method(cRegion, "size", region_size, 0);
|
|
1966
|
+
rb_define_method(cRegion, "page_size", region_page_size, 0);
|
|
1967
|
+
rb_define_method(cRegion, "read", region_read, 2);
|
|
1968
|
+
rb_define_method(cRegion, "write", region_write, 2);
|
|
1969
|
+
rb_define_method(cRegion, "madvise", region_madvise, 1);
|
|
1970
|
+
rb_define_method(cRegion, "unmap", region_unmap, 0);
|
|
1971
|
+
rb_define_method(cRegion, "unmapped?", region_unmapped_p, 0);
|
|
1972
|
+
|
|
1973
|
+
cFault = rb_define_class_under(cUserfaultFD, "Fault", rb_cObject);
|
|
1974
|
+
rb_define_attr(cFault, "address", 1, 0);
|
|
1975
|
+
rb_define_attr(cFault, "flags", 1, 0);
|
|
1976
|
+
rb_define_attr(cFault, "thread_id", 1, 0);
|
|
1977
|
+
rb_define_method(cFault, "copy", fault_copy, -1);
|
|
1978
|
+
rb_define_method(cFault, "zero", fault_zero, -1);
|
|
1979
|
+
rb_define_method(cFault, "continue", fault_continue, -1);
|
|
1980
|
+
rb_define_method(cFault, "poison", fault_poison, -1);
|
|
1981
|
+
rb_define_method(cFault, "move", fault_move, -1);
|
|
1982
|
+
rb_define_method(cFault, "wake", fault_wake, 0);
|
|
1983
|
+
|
|
1984
|
+
cForkEvent = rb_define_class_under(cUserfaultFD, "ForkEvent", rb_cObject);
|
|
1985
|
+
rb_define_attr(cForkEvent, "child_uffd", 1, 0);
|
|
1986
|
+
cRemapEvent = rb_define_class_under(cUserfaultFD, "RemapEvent", rb_cObject);
|
|
1987
|
+
rb_define_attr(cRemapEvent, "from", 1, 0);
|
|
1988
|
+
rb_define_attr(cRemapEvent, "to", 1, 0);
|
|
1989
|
+
rb_define_attr(cRemapEvent, "length", 1, 0);
|
|
1990
|
+
cRemoveEvent = rb_define_class_under(cUserfaultFD, "RemoveEvent", rb_cObject);
|
|
1991
|
+
rb_define_attr(cRemoveEvent, "start", 1, 0);
|
|
1992
|
+
rb_define_attr(cRemoveEvent, "end", 1, 0);
|
|
1993
|
+
cUnmapEvent = rb_define_class_under(cUserfaultFD, "UnmapEvent", rb_cObject);
|
|
1994
|
+
rb_define_attr(cUnmapEvent, "start", 1, 0);
|
|
1995
|
+
rb_define_attr(cUnmapEvent, "end", 1, 0);
|
|
1996
|
+
|
|
1997
|
+
cNativeHandler = rb_define_class_under(cUserfaultFD, "NativeHandler", rb_cObject);
|
|
1998
|
+
rb_define_alloc_func(cNativeHandler, native_handler_alloc);
|
|
1999
|
+
rb_define_method(cNativeHandler, "stop", native_handler_stop, 0);
|
|
2000
|
+
rb_define_method(cNativeHandler, "running?", native_handler_running_p, 0);
|
|
2001
|
+
|
|
2002
|
+
cEventReader = rb_define_class_under(cUserfaultFD, "EventReader", rb_cObject);
|
|
2003
|
+
rb_define_alloc_func(cEventReader, event_reader_alloc);
|
|
2004
|
+
rb_define_method(cEventReader, "fileno", event_reader_fileno, 0);
|
|
2005
|
+
rb_define_method(cEventReader, "read_events", event_reader_read_events, -1);
|
|
2006
|
+
rb_define_method(cEventReader, "stop", event_reader_stop, 0);
|
|
2007
|
+
rb_define_method(cEventReader, "running?", event_reader_running_p, 0);
|
|
2008
|
+
|
|
2009
|
+
userfaultfd_define_constants(cUserfaultFD);
|
|
2010
|
+
}
|