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.
@@ -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, &region_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, &region_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, &region_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, &region_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, &region_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, &region_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:", &region_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:", &region_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
+ }