mountfd 0.1.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,393 @@
1
+ #include "ruby.h"
2
+ #include "ruby/io.h"
3
+ #include "extconf.h"
4
+ #include "compat.h"
5
+ #include "mountfd.h"
6
+
7
+ #ifdef _WIN32
8
+ # include <io.h>
9
+ # define mountfd_close _close
10
+ #else
11
+ # include <unistd.h>
12
+ # define mountfd_close close
13
+ #endif
14
+ #ifdef __linux__
15
+ # include <errno.h>
16
+ # include <fcntl.h>
17
+ # include <string.h>
18
+ # include <sys/mount.h>
19
+ #endif
20
+
21
+ typedef struct { int fd; } mountfd_handle;
22
+
23
+ static VALUE mMountfd, mNative, cHandle, eError, eUnsupported;
24
+
25
+ static void handle_free(void *ptr)
26
+ {
27
+ mountfd_handle *handle = ptr;
28
+ if (!handle) return;
29
+ if (handle->fd >= 0) mountfd_close(handle->fd);
30
+ xfree(handle);
31
+ }
32
+
33
+ static size_t handle_size(const void *ptr)
34
+ {
35
+ return ptr ? sizeof(mountfd_handle) : 0;
36
+ }
37
+
38
+ static const rb_data_type_t handle_type = {
39
+ "Mountfd::Native::Handle",
40
+ {NULL, handle_free, handle_size, NULL, {NULL}},
41
+ NULL, NULL, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED
42
+ };
43
+
44
+ static VALUE handle_alloc(VALUE klass)
45
+ {
46
+ mountfd_handle *handle;
47
+ VALUE object = TypedData_Make_Struct(klass, mountfd_handle, &handle_type, handle);
48
+ handle->fd = -1;
49
+ return object;
50
+ }
51
+
52
+ static mountfd_handle *get_handle(VALUE object)
53
+ {
54
+ mountfd_handle *handle;
55
+ TypedData_Get_Struct(object, mountfd_handle, &handle_type, handle);
56
+ if (handle->fd < 0) rb_raise(eError, "closed file descriptor");
57
+ return handle;
58
+ }
59
+
60
+ #ifdef __linux__
61
+ static int fd_from(VALUE value)
62
+ {
63
+ if (rb_typeddata_is_kind_of(value, &handle_type)) return get_handle(value)->fd;
64
+ return NUM2INT(value);
65
+ }
66
+ #endif
67
+
68
+ VALUE mountfd_wrap_fd(int fd)
69
+ {
70
+ mountfd_handle *handle;
71
+ VALUE object = handle_alloc(cHandle);
72
+ TypedData_Get_Struct(object, mountfd_handle, &handle_type, handle);
73
+ handle->fd = fd;
74
+ return object;
75
+ }
76
+
77
+ static VALUE handle_fileno(VALUE self)
78
+ {
79
+ return INT2NUM(get_handle(self)->fd);
80
+ }
81
+
82
+ static VALUE handle_close(VALUE self)
83
+ {
84
+ mountfd_handle *handle;
85
+ int fd;
86
+ TypedData_Get_Struct(self, mountfd_handle, &handle_type, handle);
87
+ if (handle->fd >= 0) {
88
+ fd = handle->fd;
89
+ handle->fd = -1;
90
+ if (mountfd_close(fd) < 0) rb_sys_fail("close");
91
+ }
92
+ return Qnil;
93
+ }
94
+
95
+ static VALUE handle_closed(VALUE self)
96
+ {
97
+ mountfd_handle *handle;
98
+ TypedData_Get_Struct(self, mountfd_handle, &handle_type, handle);
99
+ return handle->fd < 0 ? Qtrue : Qfalse;
100
+ }
101
+
102
+ #ifndef __linux__
103
+ #if defined(__GNUC__) || defined(__clang__)
104
+ __attribute__((noreturn))
105
+ #endif
106
+ static void unavailable(void)
107
+ {
108
+ rb_raise(eUnsupported, "the Linux new mount API is unavailable on this platform");
109
+ }
110
+ #endif
111
+
112
+ #ifdef __linux__
113
+ void mountfd_syscall_failed(const char *name)
114
+ {
115
+ int error = errno;
116
+ if (error == ENOSYS || error == EOPNOTSUPP)
117
+ rb_raise(eUnsupported, "%s is not supported by this kernel or filesystem", name);
118
+ if (error == EPERM)
119
+ rb_exc_raise(rb_syserr_new_str(error, rb_sprintf(
120
+ "%s (insufficient privilege or operation disallowed in this namespace)", name
121
+ )));
122
+ rb_syserr_fail(error, name);
123
+ }
124
+
125
+ static void set_nonblocking_or_close(int fd)
126
+ {
127
+ int flags = fcntl(fd, F_GETFL);
128
+ if (flags >= 0 && fcntl(fd, F_SETFL, flags | O_NONBLOCK) >= 0) return;
129
+
130
+ int error = errno;
131
+ mountfd_close(fd);
132
+ errno = error;
133
+ rb_sys_fail("fcntl");
134
+ }
135
+ #endif
136
+
137
+ static VALUE native_linux_p(VALUE self)
138
+ {
139
+ #ifdef __linux__
140
+ return Qtrue;
141
+ #else
142
+ return Qfalse;
143
+ #endif
144
+ }
145
+
146
+ static VALUE native_syscall_available(VALUE self, VALUE name)
147
+ {
148
+ #ifdef __linux__
149
+ const char *value = StringValueCStr(name);
150
+ long result;
151
+ if (strcmp(value, "fsopen") == 0) {
152
+ result = syscall(SYS_fsopen, "__mountfd_probe__", FSOPEN_CLOEXEC);
153
+ if (result >= 0) mountfd_close((int)result);
154
+ } else if (strcmp(value, "mount_setattr") == 0) {
155
+ result = syscall(SYS_mount_setattr, -1, "", 0, NULL, 0);
156
+ } else if (strcmp(value, "statmount") == 0) {
157
+ result = syscall(SYS_statmount, NULL, NULL, 0, 0);
158
+ } else if (strcmp(value, "listmount") == 0) {
159
+ result = syscall(SYS_listmount, NULL, NULL, 0, 0);
160
+ } else {
161
+ rb_raise(rb_eArgError, "unknown syscall: %s", value);
162
+ }
163
+ return result >= 0 || errno != ENOSYS ? Qtrue : Qfalse;
164
+ #else
165
+ return Qfalse;
166
+ #endif
167
+ }
168
+
169
+ static VALUE native_fsopen(VALUE self, VALUE fsname, VALUE flags)
170
+ {
171
+ #ifdef __linux__
172
+ unsigned int raw_flags = NUM2UINT(flags);
173
+ const char *name = StringValueCStr(fsname);
174
+ int fd = (int)syscall(SYS_fsopen, name, raw_flags);
175
+ RB_GC_GUARD(fsname);
176
+ if (fd < 0) mountfd_syscall_failed("fsopen");
177
+ set_nonblocking_or_close(fd);
178
+ return mountfd_wrap_fd(fd);
179
+ #else
180
+ unavailable(); return Qnil;
181
+ #endif
182
+ }
183
+
184
+ static VALUE native_fsconfig(VALUE self, VALUE handle, VALUE command, VALUE key,
185
+ VALUE value, VALUE aux)
186
+ {
187
+ #ifdef __linux__
188
+ int fd = fd_from(handle);
189
+ unsigned int cmd = NUM2UINT(command);
190
+ int raw_aux = NUM2INT(aux);
191
+ const char *key_ptr;
192
+ const void *value_ptr;
193
+ if (!NIL_P(key)) StringValueCStr(key);
194
+ if (!NIL_P(value)) {
195
+ if (cmd == FSCONFIG_SET_BINARY) StringValue(value);
196
+ else StringValueCStr(value);
197
+ }
198
+ if (cmd == FSCONFIG_SET_BINARY &&
199
+ (raw_aux < 0 || (NIL_P(value) ? raw_aux != 0 : raw_aux > RSTRING_LEN(value))))
200
+ rb_raise(rb_eArgError, "binary fsconfig length must be between zero and the value size");
201
+ key_ptr = NIL_P(key) ? NULL : RSTRING_PTR(key);
202
+ if (NIL_P(value)) value_ptr = NULL;
203
+ else value_ptr = RSTRING_PTR(value);
204
+ if (syscall(SYS_fsconfig, fd, cmd, key_ptr, value_ptr, raw_aux) < 0) {
205
+ int error = errno;
206
+ RB_GC_GUARD(key);
207
+ RB_GC_GUARD(value);
208
+ errno = error;
209
+ mountfd_syscall_failed("fsconfig");
210
+ }
211
+ RB_GC_GUARD(key);
212
+ RB_GC_GUARD(value);
213
+ return Qnil;
214
+ #else
215
+ unavailable(); return Qnil;
216
+ #endif
217
+ }
218
+
219
+ static VALUE native_fsmount(VALUE self, VALUE handle, VALUE flags, VALUE attrs)
220
+ {
221
+ #ifdef __linux__
222
+ int context_fd = fd_from(handle);
223
+ unsigned int raw_flags = NUM2UINT(flags);
224
+ unsigned int raw_attrs = NUM2UINT(attrs);
225
+ int fd = (int)syscall(SYS_fsmount, context_fd, raw_flags, raw_attrs);
226
+ if (fd < 0) mountfd_syscall_failed("fsmount");
227
+ return mountfd_wrap_fd(fd);
228
+ #else
229
+ unavailable(); return Qnil;
230
+ #endif
231
+ }
232
+
233
+ static VALUE native_fspick(VALUE self, VALUE dfd, VALUE path, VALUE flags)
234
+ {
235
+ #ifdef __linux__
236
+ int directory_fd = NUM2INT(dfd);
237
+ unsigned int raw_flags = NUM2UINT(flags);
238
+ const char *raw_path = StringValueCStr(path);
239
+ int fd = (int)syscall(SYS_fspick, directory_fd, raw_path, raw_flags);
240
+ RB_GC_GUARD(path);
241
+ if (fd < 0) mountfd_syscall_failed("fspick");
242
+ set_nonblocking_or_close(fd);
243
+ return mountfd_wrap_fd(fd);
244
+ #else
245
+ unavailable(); return Qnil;
246
+ #endif
247
+ }
248
+
249
+ static VALUE native_open_tree(VALUE self, VALUE dfd, VALUE path, VALUE flags)
250
+ {
251
+ #ifdef __linux__
252
+ int directory_fd = NUM2INT(dfd);
253
+ unsigned int raw_flags = NUM2UINT(flags);
254
+ const char *raw_path = StringValueCStr(path);
255
+ int fd = (int)syscall(SYS_open_tree, directory_fd, raw_path, raw_flags);
256
+ RB_GC_GUARD(path);
257
+ if (fd < 0) mountfd_syscall_failed("open_tree");
258
+ return mountfd_wrap_fd(fd);
259
+ #else
260
+ unavailable(); return Qnil;
261
+ #endif
262
+ }
263
+
264
+ static VALUE native_move_mount(VALUE self, VALUE from_dfd, VALUE from_path,
265
+ VALUE to_dfd, VALUE to_path, VALUE flags)
266
+ {
267
+ #ifdef __linux__
268
+ int source_fd = fd_from(from_dfd);
269
+ int target_fd = NUM2INT(to_dfd);
270
+ unsigned int raw_flags = NUM2UINT(flags);
271
+ const char *source_path, *target_path;
272
+ StringValueCStr(from_path);
273
+ StringValueCStr(to_path);
274
+ source_path = RSTRING_PTR(from_path);
275
+ target_path = RSTRING_PTR(to_path);
276
+ if (syscall(SYS_move_mount, source_fd, source_path, target_fd, target_path, raw_flags) < 0) {
277
+ int error = errno;
278
+ RB_GC_GUARD(from_path);
279
+ RB_GC_GUARD(to_path);
280
+ errno = error;
281
+ mountfd_syscall_failed("move_mount");
282
+ }
283
+ RB_GC_GUARD(from_path);
284
+ RB_GC_GUARD(to_path);
285
+ return Qnil;
286
+ #else
287
+ unavailable(); return Qnil;
288
+ #endif
289
+ }
290
+
291
+ static VALUE native_mount_setattr(VALUE self, VALUE dfd, VALUE path, VALUE flags,
292
+ VALUE attr_set, VALUE attr_clr, VALUE propagation,
293
+ VALUE userns_fd)
294
+ {
295
+ #ifdef __linux__
296
+ int directory_fd = fd_from(dfd);
297
+ unsigned int raw_flags = NUM2UINT(flags);
298
+ uint64_t raw_set = NUM2ULL(attr_set);
299
+ uint64_t raw_clr = NUM2ULL(attr_clr);
300
+ uint64_t raw_propagation = NUM2ULL(propagation);
301
+ int has_idmap = (raw_set & MOUNT_ATTR_IDMAP) != 0;
302
+ if ((raw_clr & MOUNT_ATTR_IDMAP) != 0)
303
+ rb_raise(rb_eArgError, "an idmapped mount cannot be cleared");
304
+ if ((has_idmap && NIL_P(userns_fd)) || (!has_idmap && !NIL_P(userns_fd)))
305
+ rb_raise(rb_eArgError, "MOUNT_ATTR_IDMAP and a user namespace fd must be provided together");
306
+ struct mount_attr attr = {
307
+ raw_set, raw_clr, raw_propagation,
308
+ NIL_P(userns_fd) ? 0 : (uint64_t)fd_from(userns_fd)
309
+ };
310
+ const char *raw_path = StringValueCStr(path);
311
+ if (syscall(SYS_mount_setattr, directory_fd, raw_path, raw_flags,
312
+ &attr, MOUNT_ATTR_SIZE_VER0) < 0) {
313
+ int error = errno;
314
+ RB_GC_GUARD(path);
315
+ errno = error;
316
+ mountfd_syscall_failed("mount_setattr");
317
+ }
318
+ RB_GC_GUARD(path);
319
+ return Qnil;
320
+ #else
321
+ unavailable(); return Qnil;
322
+ #endif
323
+ }
324
+
325
+ static VALUE native_umount2(VALUE self, VALUE path, VALUE flags)
326
+ {
327
+ #ifdef __linux__
328
+ int raw_flags = NUM2INT(flags);
329
+ const char *raw_path = StringValueCStr(path);
330
+ if (umount2(raw_path, raw_flags) < 0) {
331
+ int error = errno;
332
+ RB_GC_GUARD(path);
333
+ errno = error;
334
+ mountfd_syscall_failed("umount2");
335
+ }
336
+ RB_GC_GUARD(path);
337
+ return Qnil;
338
+ #else
339
+ unavailable(); return Qnil;
340
+ #endif
341
+ }
342
+
343
+ static VALUE native_read_diagnostics(VALUE self, VALUE handle)
344
+ {
345
+ #ifdef __linux__
346
+ char buffer[4096];
347
+ int fd = fd_from(handle);
348
+ VALUE output = rb_str_new(NULL, 0);
349
+ ssize_t length;
350
+ for (;;) {
351
+ length = read(fd, buffer, sizeof(buffer));
352
+ if (length > 0) rb_str_cat(output, buffer, length);
353
+ else if (length < 0 && errno == EINTR) continue;
354
+ else break;
355
+ }
356
+ if (length < 0 && errno != EAGAIN && errno != EWOULDBLOCK && errno != ENODATA)
357
+ rb_sys_fail("read(fs_context)");
358
+ return output;
359
+ #else
360
+ unavailable(); return Qnil;
361
+ #endif
362
+ }
363
+
364
+ void mountfd_define_constants(VALUE native);
365
+
366
+ void Init_mountfd(void)
367
+ {
368
+ mMountfd = rb_define_module("Mountfd");
369
+ eError = rb_const_get(mMountfd, rb_intern("Error"));
370
+ eUnsupported = rb_const_get(mMountfd, rb_intern("UnsupportedError"));
371
+ mNative = rb_define_module_under(mMountfd, "Native");
372
+ cHandle = rb_define_class_under(mNative, "Handle", rb_cObject);
373
+ rb_define_alloc_func(cHandle, handle_alloc);
374
+ rb_undef_method(rb_singleton_class(cHandle), "new");
375
+ rb_define_method(cHandle, "fileno", handle_fileno, 0);
376
+ rb_define_method(cHandle, "close", handle_close, 0);
377
+ rb_define_method(cHandle, "closed?", handle_closed, 0);
378
+ rb_define_singleton_method(mNative, "linux?", native_linux_p, 0);
379
+ rb_define_singleton_method(mNative, "syscall_available?", native_syscall_available, 1);
380
+ rb_define_singleton_method(mNative, "fsopen", native_fsopen, 2);
381
+ rb_define_singleton_method(mNative, "fsconfig", native_fsconfig, 5);
382
+ rb_define_singleton_method(mNative, "fsmount", native_fsmount, 3);
383
+ rb_define_singleton_method(mNative, "fspick", native_fspick, 3);
384
+ rb_define_singleton_method(mNative, "open_tree", native_open_tree, 3);
385
+ rb_define_singleton_method(mNative, "move_mount", native_move_mount, 5);
386
+ rb_define_singleton_method(mNative, "mount_setattr", native_mount_setattr, 7);
387
+ rb_define_singleton_method(mNative, "umount2", native_umount2, 2);
388
+ rb_define_singleton_method(mNative, "read_diagnostics", native_read_diagnostics, 1);
389
+ mountfd_define_constants(mNative);
390
+ mountfd_user_namespace_init(mNative);
391
+ mountfd_mount_info_init(mNative);
392
+ mountfd_namespace_init(mNative);
393
+ }
@@ -0,0 +1,17 @@
1
+ #ifndef MOUNTFD_H
2
+ #define MOUNTFD_H
3
+
4
+ #include "ruby.h"
5
+
6
+ VALUE mountfd_wrap_fd(int fd);
7
+ #ifdef __linux__
8
+ #if defined(__GNUC__) || defined(__clang__)
9
+ __attribute__((noreturn))
10
+ #endif
11
+ void mountfd_syscall_failed(const char *name);
12
+ #endif
13
+ void mountfd_user_namespace_init(VALUE native);
14
+ void mountfd_mount_info_init(VALUE native);
15
+ void mountfd_namespace_init(VALUE native);
16
+
17
+ #endif
@@ -0,0 +1,74 @@
1
+ #include "ruby.h"
2
+ #include "mountfd.h"
3
+ #include "compat.h"
4
+
5
+ #ifdef __linux__
6
+ # include <errno.h>
7
+ # include <sched.h>
8
+ # include <sys/mount.h>
9
+ # include <unistd.h>
10
+ #endif
11
+
12
+ static VALUE native_unshare(VALUE self, VALUE flags)
13
+ {
14
+ #ifdef __linux__
15
+ if (unshare(NUM2INT(flags)) < 0) mountfd_syscall_failed("unshare");
16
+ return Qnil;
17
+ #else
18
+ VALUE mountfd = rb_const_get(rb_cObject, rb_intern("Mountfd"));
19
+ VALUE error = rb_const_get(mountfd, rb_intern("UnsupportedError"));
20
+ rb_raise(error, "namespaces are unavailable on this platform");
21
+ #endif
22
+ }
23
+
24
+ static VALUE native_change_propagation(VALUE self, VALUE path, VALUE flags)
25
+ {
26
+ #ifdef __linux__
27
+ unsigned long raw_flags = NUM2ULONG(flags);
28
+ const char *raw_path = StringValueCStr(path);
29
+ if (mount(NULL, raw_path, NULL, raw_flags, NULL) < 0) {
30
+ int error = errno;
31
+ RB_GC_GUARD(path);
32
+ errno = error;
33
+ mountfd_syscall_failed("mount(propagation)");
34
+ }
35
+ RB_GC_GUARD(path);
36
+ return Qnil;
37
+ #else
38
+ VALUE mountfd = rb_const_get(rb_cObject, rb_intern("Mountfd"));
39
+ VALUE error = rb_const_get(mountfd, rb_intern("UnsupportedError"));
40
+ rb_raise(error, "mount propagation is unavailable on this platform");
41
+ #endif
42
+ }
43
+
44
+ static VALUE native_pivot_root(VALUE self, VALUE new_root, VALUE put_old)
45
+ {
46
+ #ifdef __linux__
47
+ const char *root_path, *old_path;
48
+ StringValueCStr(new_root);
49
+ StringValueCStr(put_old);
50
+ root_path = RSTRING_PTR(new_root);
51
+ old_path = RSTRING_PTR(put_old);
52
+ if (syscall(SYS_pivot_root, root_path, old_path) < 0) {
53
+ int error = errno;
54
+ RB_GC_GUARD(new_root);
55
+ RB_GC_GUARD(put_old);
56
+ errno = error;
57
+ mountfd_syscall_failed("pivot_root");
58
+ }
59
+ RB_GC_GUARD(new_root);
60
+ RB_GC_GUARD(put_old);
61
+ return Qnil;
62
+ #else
63
+ VALUE mountfd = rb_const_get(rb_cObject, rb_intern("Mountfd"));
64
+ VALUE error = rb_const_get(mountfd, rb_intern("UnsupportedError"));
65
+ rb_raise(error, "pivot_root is unavailable on this platform");
66
+ #endif
67
+ }
68
+
69
+ void mountfd_namespace_init(VALUE native)
70
+ {
71
+ rb_define_singleton_method(native, "unshare", native_unshare, 1);
72
+ rb_define_singleton_method(native, "change_propagation", native_change_propagation, 2);
73
+ rb_define_singleton_method(native, "pivot_root", native_pivot_root, 2);
74
+ }
@@ -0,0 +1,220 @@
1
+ #include "ruby.h"
2
+ #include "extconf.h"
3
+ #include "mountfd.h"
4
+
5
+ #ifdef __linux__
6
+ # include <errno.h>
7
+ # include <fcntl.h>
8
+ # include <sched.h>
9
+ # include <signal.h>
10
+ # include <spawn.h>
11
+ # include <stdio.h>
12
+ # include <stdlib.h>
13
+ # include <string.h>
14
+ # include <sys/types.h>
15
+ # include <sys/wait.h>
16
+ # include <unistd.h>
17
+ extern char **environ;
18
+ #endif
19
+
20
+ #ifdef __linux__
21
+ static int write_all(int fd, const void *data, size_t length)
22
+ {
23
+ const char *cursor = data;
24
+ while (length > 0) {
25
+ ssize_t written = write(fd, cursor, length);
26
+ if (written < 0) {
27
+ if (errno == EINTR) continue;
28
+ return -1;
29
+ }
30
+ cursor += written;
31
+ length -= (size_t)written;
32
+ }
33
+ return 0;
34
+ }
35
+
36
+ static int write_proc_file(pid_t pid, const char *name, const char *value)
37
+ {
38
+ char path[64];
39
+ int fd;
40
+ snprintf(path, sizeof(path), "/proc/%ld/%s", (long)pid, name);
41
+ fd = open(path, O_WRONLY | O_CLOEXEC);
42
+ if (fd < 0) return -1;
43
+ if (write_all(fd, value, strlen(value)) < 0) {
44
+ int error = errno;
45
+ close(fd);
46
+ errno = error;
47
+ return -1;
48
+ }
49
+ return close(fd);
50
+ }
51
+
52
+ static int run_map_helper(const char *program, pid_t pid, const char *mapping)
53
+ {
54
+ char *copy = strdup(mapping), *saveptr, *token, pid_text[32];
55
+ size_t count = 0, capacity = 16;
56
+ char **arguments = malloc(capacity * sizeof(char *));
57
+ pid_t helper;
58
+ int status;
59
+ if (!copy || !arguments) {
60
+ free(copy); free(arguments); errno = ENOMEM; return -1;
61
+ }
62
+
63
+ snprintf(pid_text, sizeof(pid_text), "%ld", (long)pid);
64
+ arguments[count++] = (char *)program;
65
+ arguments[count++] = pid_text;
66
+ token = strtok_r(copy, " \t\r\n", &saveptr);
67
+ while (token) {
68
+ if (count + 1 == capacity) {
69
+ capacity *= 2;
70
+ char **grown = realloc(arguments, capacity * sizeof(char *));
71
+ if (!grown) { free(arguments); free(copy); errno = ENOMEM; return -1; }
72
+ arguments = grown;
73
+ }
74
+ arguments[count++] = token;
75
+ token = strtok_r(NULL, " \t\r\n", &saveptr);
76
+ }
77
+ arguments[count] = NULL;
78
+
79
+ int error = posix_spawnp(&helper, program, NULL, NULL, arguments, environ);
80
+ if (error != 0) {
81
+ free(arguments); free(copy); errno = error; return -1;
82
+ }
83
+ while (waitpid(helper, &status, 0) < 0) {
84
+ if (errno == EINTR) continue;
85
+ int error = errno;
86
+ free(arguments); free(copy); errno = error; return -1;
87
+ }
88
+ free(arguments); free(copy);
89
+ if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { errno = EPERM; return -1; }
90
+ return 0;
91
+ }
92
+
93
+ static int configure_maps(pid_t pid, const char *uid_map, const char *gid_map, int helper)
94
+ {
95
+ if (helper) {
96
+ if (run_map_helper("newuidmap", pid, uid_map) < 0) return -1;
97
+ if (write_proc_file(pid, "setgroups", "deny") < 0 && errno != ENOENT && errno != EPERM)
98
+ return -1;
99
+ return run_map_helper("newgidmap", pid, gid_map);
100
+ }
101
+
102
+ if (write_proc_file(pid, "uid_map", uid_map) < 0) return -1;
103
+ if (write_proc_file(pid, "setgroups", "deny") < 0 && errno != ENOENT && errno != EPERM)
104
+ return -1;
105
+ return write_proc_file(pid, "gid_map", gid_map);
106
+ }
107
+
108
+ static void stop_keeper(pid_t pid, int release_fd)
109
+ {
110
+ if (release_fd >= 0) close(release_fd);
111
+ kill(pid, SIGKILL);
112
+ while (waitpid(pid, NULL, 0) < 0 && errno == EINTR) {}
113
+ }
114
+ #endif
115
+
116
+ static VALUE native_open_handle(VALUE self, VALUE path)
117
+ {
118
+ #ifdef __linux__
119
+ const char *raw_path = StringValueCStr(path);
120
+ int fd = open(raw_path, O_RDONLY | O_CLOEXEC);
121
+ RB_GC_GUARD(path);
122
+ if (fd < 0) rb_sys_fail_str(path);
123
+ return mountfd_wrap_fd(fd);
124
+ #else
125
+ VALUE mountfd = rb_const_get(rb_cObject, rb_intern("Mountfd"));
126
+ VALUE error = rb_const_get(mountfd, rb_intern("UnsupportedError"));
127
+ rb_raise(error, "user namespaces are unavailable on this platform");
128
+ #endif
129
+ }
130
+
131
+ static VALUE native_user_namespace(VALUE self, VALUE uid_value, VALUE gid_value, VALUE helper_value)
132
+ {
133
+ #ifdef __linux__
134
+ const char *uid_map, *gid_map;
135
+ int ready[2], release[2], child_error = 0, namespace_fd = -1;
136
+ pid_t pid;
137
+ char path[64], byte;
138
+ ssize_t length;
139
+
140
+ StringValueCStr(uid_value);
141
+ StringValueCStr(gid_value);
142
+ uid_map = RSTRING_PTR(uid_value);
143
+ gid_map = RSTRING_PTR(gid_value);
144
+
145
+ if (pipe2(ready, O_CLOEXEC) < 0) rb_sys_fail("pipe2");
146
+ if (pipe2(release, O_CLOEXEC) < 0) {
147
+ int error = errno;
148
+ close(ready[0]); close(ready[1]); errno = error; rb_sys_fail("pipe2");
149
+ }
150
+ pid = fork();
151
+ if (pid == 0) {
152
+ close(ready[0]); close(release[1]);
153
+ if (unshare(CLONE_NEWUSER) < 0) child_error = errno;
154
+ write_all(ready[1], &child_error, sizeof(child_error));
155
+ close(ready[1]);
156
+ if (child_error == 0) while (read(release[0], &byte, 1) < 0 && errno == EINTR) {}
157
+ _exit(child_error == 0 ? 0 : 1);
158
+ }
159
+ close(ready[1]); close(release[0]);
160
+ if (pid < 0) {
161
+ close(ready[0]); close(release[1]); rb_sys_fail("fork");
162
+ }
163
+
164
+ do { length = read(ready[0], &child_error, sizeof(child_error)); } while (length < 0 && errno == EINTR);
165
+ close(ready[0]);
166
+ if (length != sizeof(child_error) || child_error != 0) {
167
+ int error = child_error ? child_error : EIO;
168
+ stop_keeper(pid, -1); close(release[1]); errno = error;
169
+ mountfd_syscall_failed("unshare(CLONE_NEWUSER)");
170
+ }
171
+ if (configure_maps(pid, uid_map, gid_map, RTEST(helper_value)) < 0) {
172
+ int error = errno;
173
+ stop_keeper(pid, -1); close(release[1]); errno = error; rb_sys_fail("configure user namespace maps");
174
+ }
175
+ snprintf(path, sizeof(path), "/proc/%ld/ns/user", (long)pid);
176
+ namespace_fd = open(path, O_RDONLY | O_CLOEXEC);
177
+ if (namespace_fd < 0) {
178
+ int error = errno;
179
+ stop_keeper(pid, -1); close(release[1]); errno = error; rb_sys_fail("open user namespace");
180
+ }
181
+ stop_keeper(pid, release[1]);
182
+ RB_GC_GUARD(uid_value);
183
+ RB_GC_GUARD(gid_value);
184
+ return mountfd_wrap_fd(namespace_fd);
185
+ #else
186
+ VALUE mountfd = rb_const_get(rb_cObject, rb_intern("Mountfd"));
187
+ VALUE error = rb_const_get(mountfd, rb_intern("UnsupportedError"));
188
+ rb_raise(error, "user namespaces are unavailable on this platform");
189
+ #endif
190
+ }
191
+
192
+ static VALUE native_unshare_user(VALUE self, VALUE map_root_value)
193
+ {
194
+ #ifdef __linux__
195
+ uid_t uid = getuid();
196
+ gid_t gid = getgid();
197
+ char uid_map[64], gid_map[64];
198
+ if (unshare(CLONE_NEWUSER) < 0) mountfd_syscall_failed("unshare(CLONE_NEWUSER)");
199
+ if (!RTEST(map_root_value)) return Qnil;
200
+
201
+ snprintf(uid_map, sizeof(uid_map), "0 %lu 1\n", (unsigned long)uid);
202
+ snprintf(gid_map, sizeof(gid_map), "0 %lu 1\n", (unsigned long)gid);
203
+ if (write_proc_file(getpid(), "uid_map", uid_map) < 0) rb_sys_fail("write uid_map");
204
+ if (write_proc_file(getpid(), "setgroups", "deny") < 0 && errno != ENOENT && errno != EPERM)
205
+ rb_sys_fail("write setgroups");
206
+ if (write_proc_file(getpid(), "gid_map", gid_map) < 0) rb_sys_fail("write gid_map");
207
+ return Qnil;
208
+ #else
209
+ VALUE mountfd = rb_const_get(rb_cObject, rb_intern("Mountfd"));
210
+ VALUE error = rb_const_get(mountfd, rb_intern("UnsupportedError"));
211
+ rb_raise(error, "user namespaces are unavailable on this platform");
212
+ #endif
213
+ }
214
+
215
+ void mountfd_user_namespace_init(VALUE native)
216
+ {
217
+ rb_define_singleton_method(native, "open_handle", native_open_handle, 1);
218
+ rb_define_singleton_method(native, "user_namespace", native_user_namespace, 3);
219
+ rb_define_singleton_method(native, "unshare_user", native_unshare_user, 1);
220
+ }