qr_reader-native 0.0.1

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.
Files changed (95) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE-mruby.txt +22 -0
  3. data/LICENSE-spinel.txt +19 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +61 -0
  6. data/ext/qr_reader_native/SPINEL_VERSION +1 -0
  7. data/ext/qr_reader_native/extconf.rb +8 -0
  8. data/ext/qr_reader_native/mruby/error.h +13 -0
  9. data/ext/qr_reader_native/mruby/internal.h +145 -0
  10. data/ext/qr_reader_native/mruby/string.h +22 -0
  11. data/ext/qr_reader_native/mruby.h +93 -0
  12. data/ext/qr_reader_native/mruby_shim.h +118 -0
  13. data/ext/qr_reader_native/qr_reader_native.c +13513 -0
  14. data/ext/qr_reader_native/qr_reader_native.h +29 -0
  15. data/ext/qr_reader_native/qr_reader_native_ext.c +598 -0
  16. data/ext/qr_reader_native/re_cased.h +71 -0
  17. data/ext/qr_reader_native/re_compile.c +5784 -0
  18. data/ext/qr_reader_native/re_ctype.h +3540 -0
  19. data/ext/qr_reader_native/re_exec.c +2190 -0
  20. data/ext/qr_reader_native/re_internal.h +671 -0
  21. data/ext/qr_reader_native/re_prop.h +4629 -0
  22. data/ext/qr_reader_native/re_spinel.c +363 -0
  23. data/ext/qr_reader_native/re_spinel.h +22 -0
  24. data/ext/qr_reader_native/re_spinel_utf8.inc +97 -0
  25. data/ext/qr_reader_native/re_utf8.c +156 -0
  26. data/ext/qr_reader_native/sp_alloc.c +1447 -0
  27. data/ext/qr_reader_native/sp_alloc.h +1196 -0
  28. data/ext/qr_reader_native/sp_argf.h +33 -0
  29. data/ext/qr_reader_native/sp_array.c +1052 -0
  30. data/ext/qr_reader_native/sp_array.h +456 -0
  31. data/ext/qr_reader_native/sp_bigint.c +5826 -0
  32. data/ext/qr_reader_native/sp_bigint.h +117 -0
  33. data/ext/qr_reader_native/sp_cold.c +5259 -0
  34. data/ext/qr_reader_native/sp_compat.h +402 -0
  35. data/ext/qr_reader_native/sp_core.c +818 -0
  36. data/ext/qr_reader_native/sp_core.h +56 -0
  37. data/ext/qr_reader_native/sp_crypto.c +877 -0
  38. data/ext/qr_reader_native/sp_crypto.h +169 -0
  39. data/ext/qr_reader_native/sp_dtoa.c +1533 -0
  40. data/ext/qr_reader_native/sp_dtoa.h +29 -0
  41. data/ext/qr_reader_native/sp_encoding_names.h +172 -0
  42. data/ext/qr_reader_native/sp_enum.h +86 -0
  43. data/ext/qr_reader_native/sp_exc.c +1185 -0
  44. data/ext/qr_reader_native/sp_exc.h +186 -0
  45. data/ext/qr_reader_native/sp_exc_ctx.h +56 -0
  46. data/ext/qr_reader_native/sp_fiber.c +889 -0
  47. data/ext/qr_reader_native/sp_fiber.h +162 -0
  48. data/ext/qr_reader_native/sp_fiber_ctx.h +84 -0
  49. data/ext/qr_reader_native/sp_format.c +695 -0
  50. data/ext/qr_reader_native/sp_format.h +83 -0
  51. data/ext/qr_reader_native/sp_gc.c +1793 -0
  52. data/ext/qr_reader_native/sp_gc.h +762 -0
  53. data/ext/qr_reader_native/sp_hash.c +131 -0
  54. data/ext/qr_reader_native/sp_hash.h +131 -0
  55. data/ext/qr_reader_native/sp_inspect.c +215 -0
  56. data/ext/qr_reader_native/sp_inspect.h +128 -0
  57. data/ext/qr_reader_native/sp_io.c +1676 -0
  58. data/ext/qr_reader_native/sp_io.h +272 -0
  59. data/ext/qr_reader_native/sp_iobuffer.c +1014 -0
  60. data/ext/qr_reader_native/sp_iobuffer.h +167 -0
  61. data/ext/qr_reader_native/sp_marshal.c +487 -0
  62. data/ext/qr_reader_native/sp_marshal.h +60 -0
  63. data/ext/qr_reader_native/sp_net.c +1029 -0
  64. data/ext/qr_reader_native/sp_net.h +205 -0
  65. data/ext/qr_reader_native/sp_pack.c +1254 -0
  66. data/ext/qr_reader_native/sp_poly_cold.c +1220 -0
  67. data/ext/qr_reader_native/sp_proc.c +410 -0
  68. data/ext/qr_reader_native/sp_proc.h +327 -0
  69. data/ext/qr_reader_native/sp_process.c +547 -0
  70. data/ext/qr_reader_native/sp_process_status.c +211 -0
  71. data/ext/qr_reader_native/sp_process_status.h +66 -0
  72. data/ext/qr_reader_native/sp_random.c +287 -0
  73. data/ext/qr_reader_native/sp_random.h +52 -0
  74. data/ext/qr_reader_native/sp_range.h +144 -0
  75. data/ext/qr_reader_native/sp_re.c +1379 -0
  76. data/ext/qr_reader_native/sp_re.h +186 -0
  77. data/ext/qr_reader_native/sp_re_names.h +59 -0
  78. data/ext/qr_reader_native/sp_sched.c +4044 -0
  79. data/ext/qr_reader_native/sp_sched.h +295 -0
  80. data/ext/qr_reader_native/sp_slab.c +1507 -0
  81. data/ext/qr_reader_native/sp_str.c +1462 -0
  82. data/ext/qr_reader_native/sp_str.h +306 -0
  83. data/ext/qr_reader_native/sp_str_crypt.c +23 -0
  84. data/ext/qr_reader_native/sp_string.c +92 -0
  85. data/ext/qr_reader_native/sp_string.h +253 -0
  86. data/ext/qr_reader_native/sp_system.c +78 -0
  87. data/ext/qr_reader_native/sp_system.h +18 -0
  88. data/ext/qr_reader_native/sp_time.c +860 -0
  89. data/ext/qr_reader_native/sp_time.h +100 -0
  90. data/ext/qr_reader_native/sp_types.h +292 -0
  91. data/ext/qr_reader_native/spinel_rt.h +19017 -0
  92. data/ext/qr_reader_native/unicase.c +399 -0
  93. data/ext/qr_reader_native/unicase.h +901 -0
  94. data/lib/qr_reader/native.rb +6 -0
  95. metadata +153 -0
@@ -0,0 +1,547 @@
1
+ /* lib/sp_process.c -- Process.spawn and Process.waitpid2 (CRuby-compatible)
2
+ *
3
+ * Lives in its own TU because the codegen calls sp_process_spawn
4
+ * with pre-resolved positional args (in_fd, out_fd, err_fd, pgroup,
5
+ * rlimit_cpu, rlimit_as, chdir, owned). All opts-hash unpacking happens
6
+ * at compile time in the codegen; the runtime just needs primitive int
7
+ * / int-fd / int-pgroup / int-rlimit values. The cmd is either a
8
+ * String or an Array (boxed as a PolyArray). args is a PolyArray of
9
+ * extra String args appended after cmd.
10
+ *
11
+ * The TU deliberately does NOT include spinel_rt.h (it pulls in only
12
+ * sp_alloc.h and the low-level headers). spinel_rt.h's static-inline
13
+ * family references sp_class_to_s / sp_sym_to_s which are emitted
14
+ * per-program by the codegen and not present in the runtime archive;
15
+ * including spinel_rt.h from here would force those symbols into the
16
+ * link and break the build.
17
+ *
18
+ * Process.spawn(cmd, *args, opts) -> child pid
19
+ * Process.waitpid2(pid) -> [pid, raw_status]
20
+ *
21
+ * opts is a PolyArray of 8 elements in this order:
22
+ * [0] in_fd - Integer fd (>= 0), -1 for false/nil/absent. An IO
23
+ * was resolved to its fd by the codegen; a String
24
+ * path was opened here, by sp_process_open_redirect,
25
+ * which the generated program calls before this
26
+ * [1] out_fd - same conventions
27
+ * [2] err_fd - same conventions
28
+ * [3] pgroup - Integer (0=inherit, 1=new, >1=specific pgid)
29
+ * [4] rlimit_cpu - Integer (seconds), nil = no limit
30
+ * [5] rlimit_as - Integer (bytes), nil = no limit
31
+ * [6] chdir - String path or nil
32
+ * [7] owned - Integer bit mask, bit 0/1/2 set when [0]/[1]/[2]
33
+ * is an fd sp_process_open_redirect opened for this
34
+ * spawn; those are closed here once the child has
35
+ * its copies, and on every raise this file makes
36
+ * before the fork.
37
+ * A caller's IO or Integer fd is never in the mask.
38
+ *
39
+ * An IO value in opts[0..2] is resolved to its fd by the codegen (which
40
+ * has access to sp_File_fileno); a String value reaches the runtime
41
+ * through sp_process_open_redirect. The [:child, :out|:err|Integer]
42
+ * form is resolved by the codegen (it sees the literal array at parse
43
+ * time).
44
+ */
45
+
46
+ #include <fcntl.h>
47
+ #include <signal.h>
48
+ #include <stdarg.h>
49
+ #include <stddef.h>
50
+ #include <stdio.h>
51
+ #include <stdlib.h>
52
+ #include <string.h>
53
+ #include <sys/resource.h>
54
+ #include <sys/types.h>
55
+ #include <sys/wait.h>
56
+ #include <unistd.h>
57
+ #include <errno.h>
58
+ #include <limits.h>
59
+
60
+ #if defined(_WIN32)
61
+ #include <spawn.h>
62
+ #include <sys/stat.h>
63
+ #endif
64
+
65
+ #include "sp_alloc.h" /* sp_PolyArray, sp_RbVal, sp_box_*, sp_raise_cls */
66
+ #include "sp_process_status.h" /* sp_ProcessStatus, sp_box_process_status */
67
+ #include "sp_system.h" /* sp_last_status: $? */
68
+ #include "sp_string.h" /* sp_String: a shared String's handle */
69
+
70
+ /* A String the program holds as the shared handle (--share-strings) arrives
71
+ boxed as that handle: the spawn reads its bytes as it reads a String's. */
72
+ static sp_RbVal sp_spawn_str(sp_RbVal v) {
73
+ if (v.tag == SP_TAG_OBJ && v.cls_id == SP_BUILTIN_STRBUF && v.v.p) {
74
+ sp_RbVal s = v;
75
+ s.tag = SP_TAG_STR;
76
+ s.v.s = ((sp_String *)v.v.p)->data;
77
+ return s;
78
+ }
79
+ return v;
80
+ }
81
+
82
+ /* Local error-message builder. Returns a static buffer; copy the
83
+ result before another call. Avoids sp_sprintf which would pull
84
+ sp_class_to_s / sp_sym_to_s into the link. */
85
+ static SP_TLS char sp_err_buf[512];
86
+ static const char *sp_errf_errno(const char *prefix, int err) {
87
+ snprintf(sp_err_buf, sizeof sp_err_buf, "%s - %s", prefix, strerror(err));
88
+ return sp_err_buf;
89
+ }
90
+ /* CRuby's SystemCallError message for a named path: the strerror text,
91
+ then the path that failed, as `No such file or directory - /bin/nope`. */
92
+ static const char *sp_errf_path(int err, const char *path) {
93
+ snprintf(sp_err_buf, sizeof sp_err_buf, "%s - %s", strerror(err), path);
94
+ return sp_err_buf;
95
+ }
96
+
97
+ /* CRuby's TypeError for a [program, argv0] element that is no String */
98
+ static const char *sp_errf_conv(sp_RbVal v) {
99
+ const char *k = v.tag == SP_TAG_INT ? "Integer" : v.tag == SP_TAG_FLT ? "Float"
100
+ : v.tag == SP_TAG_NIL ? "nil" : v.tag == SP_TAG_SYM ? "Symbol"
101
+ : v.tag == SP_TAG_BOOL ? (v.v.i ? "true" : "false") : "Object";
102
+ snprintf(sp_err_buf, sizeof sp_err_buf, "no implicit conversion of %s into String", k);
103
+ return sp_err_buf;
104
+ }
105
+
106
+ /* Apply the redirect in the child: dup2 src_fd onto target_fd, close src.
107
+ src_fd < 0 means the caller did not pass that slot in the opts hash
108
+ (the codegen initialises every slot to -1 and overwrites only the ones
109
+ the user set); in that case the fd is left inherited from the parent.
110
+ CRuby semantics: an unset :in/:out/:err slot is inherit, not /dev/null.
111
+ The previous /dev/null behaviour silently swallowed the child's stderr
112
+ and made `Process.spawn("cc -c foo.c")` in verbose mode look like it
113
+ produced no output. */
114
+ static void apply_redirect(int target_fd, int src_fd) {
115
+ if (src_fd < 0) return;
116
+ if (src_fd != target_fd) dup2(src_fd, target_fd);
117
+ }
118
+
119
+ /* Close the source descriptors, once every dup2 above has been made. Not
120
+ inside apply_redirect: `out: f, err: f` names one descriptor twice, and
121
+ closing it after the first redirect left the second dup2 working on a
122
+ closed fd -- silently, so the child's stderr went nowhere (#4176). */
123
+ static void close_redirect_srcs(int in_fd, int out_fd, int err_fd) {
124
+ int fds[3]; int n = 0;
125
+ if (in_fd > 2) fds[n++] = in_fd;
126
+ if (out_fd > 2) fds[n++] = out_fd;
127
+ if (err_fd > 2) fds[n++] = err_fd;
128
+ for (int i = 0; i < n; i++) {
129
+ int dup = 0;
130
+ for (int j = 0; j < i; j++) if (fds[j] == fds[i]) dup = 1;
131
+ if (!dup) close(fds[i]);
132
+ }
133
+ }
134
+
135
+ /* The fds this spawn opened itself, one slot each for in/out/err and -1
136
+ where the slot came from the caller (an IO, an Integer, nothing). Only
137
+ these are the spawn's to close: the child closes its copies after the
138
+ dup2s above, and the parent closes its own once the child has them. */
139
+ static void close_owned(const int *owned) {
140
+ for (int i = 0; i < 3; i++) if (owned[i] >= 0) close(owned[i]);
141
+ }
142
+
143
+ /* Raise on the spawn's behalf: the fds it opened are released first, so a
144
+ spawn that never forks does not leave them behind. errno survives the
145
+ closes for the Errno class the raise builds. */
146
+ SP_NORETURN void sp_process_spawn_fail(int *owned, const char *cls, const char *msg) {
147
+ int e = errno;
148
+ close_owned(owned);
149
+ errno = e;
150
+ sp_raise_cls(cls, msg);
151
+ }
152
+
153
+ #ifndef PATH_MAX
154
+ #define PATH_MAX 4096
155
+ #endif
156
+
157
+ /* A filename redirection, opened here rather than by the generated program:
158
+ read-only for slot 0 (:in), created and truncated for :out and :err. The
159
+ fd is recorded in owned[slot]. A failed open raises the Errno class CRuby
160
+ raises, with CRuby's message, after releasing the fds already opened for
161
+ this spawn. The ladder is sp_file_open_raise's four (sp_cold.c) plus the
162
+ six more errnos open(2) answers for a path; anything else is a bare
163
+ SystemCallError. The message is built in a buffer sized for a path, and
164
+ sp_raise_cls copies it before it can allocate, so a local is enough. */
165
+ int sp_process_open_redirect(const char *path, int slot, int *owned) {
166
+ int flags = slot == 0 ? O_RDONLY : O_WRONLY | O_CREAT | O_TRUNC;
167
+ int fd = open(path, flags, 0644);
168
+ if (fd < 0) {
169
+ int e = errno;
170
+ const char *cls = e == ENOENT ? "Errno::ENOENT" :
171
+ e == EACCES ? "Errno::EACCES" :
172
+ e == EEXIST ? "Errno::EEXIST" :
173
+ e == EISDIR ? "Errno::EISDIR" :
174
+ e == ENOTDIR ? "Errno::ENOTDIR" :
175
+ e == ENAMETOOLONG ? "Errno::ENAMETOOLONG" :
176
+ e == ELOOP ? "Errno::ELOOP" :
177
+ e == EROFS ? "Errno::EROFS" :
178
+ e == EMFILE ? "Errno::EMFILE" :
179
+ e == ENFILE ? "Errno::ENFILE" : "SystemCallError";
180
+ char msg[PATH_MAX + 64];
181
+ snprintf(msg, sizeof msg, "%s - %s", strerror(e), path);
182
+ errno = e;
183
+ sp_process_spawn_fail(owned, cls, msg);
184
+ }
185
+ owned[slot] = fd;
186
+ return fd;
187
+ }
188
+
189
+ /* Extract a resolved Integer fd from a pre-resolved opts slot. The
190
+ codegen turns IO/false into Integer before passing, and a String
191
+ path arrives as the fd sp_process_open_redirect returned; we just
192
+ unbox. -1 means "not set": inherit the parent's, per apply_redirect. */
193
+ static int slot_to_fd(sp_RbVal v) {
194
+ if (v.tag == SP_TAG_NIL) return -1;
195
+ if (v.tag == SP_TAG_BOOL && v.v.i == 0) return -1;
196
+ if (v.tag == SP_TAG_INT) return (int)v.v.i;
197
+ sp_raise_cls("TypeError", "redirect slot must be Integer (codegen bug)");
198
+ return -1;
199
+ }
200
+
201
+ sp_int sp_process_spawn(sp_RbVal cmd, sp_RbVal args_box,
202
+ sp_RbVal opts_box) {
203
+ SP_GC_ROOT_RBVAL(cmd);
204
+ SP_GC_ROOT_RBVAL(args_box);
205
+ SP_GC_ROOT_RBVAL(opts_box);
206
+
207
+ /* opts_box must be a PolyArray of 8 elements. */
208
+ if (opts_box.tag != SP_TAG_OBJ ||
209
+ opts_box.cls_id != SP_BUILTIN_POLY_ARRAY) {
210
+ sp_raise_cls("TypeError", "opts must be a PolyArray (codegen bug)");
211
+ }
212
+ sp_PolyArray *opts = (sp_PolyArray *)opts_box.v.p;
213
+ if (opts->len < 8) {
214
+ sp_raise_cls("ArgumentError", "opts array too short (codegen bug)");
215
+ }
216
+ int in_fd = slot_to_fd(opts->data[0]);
217
+ int out_fd = slot_to_fd(opts->data[1]);
218
+ int err_fd = slot_to_fd(opts->data[2]);
219
+ /* Slot 7: a bit per slot the generated program had this file open for
220
+ it, so those fds are the spawn's to close; a caller's IO stays open. */
221
+ int owned_mask = opts->data[7].tag == SP_TAG_INT ? (int)opts->data[7].v.i : 0;
222
+ int owned[3] = { owned_mask & 1 ? in_fd : -1,
223
+ owned_mask & 2 ? out_fd : -1,
224
+ owned_mask & 4 ? err_fd : -1 };
225
+
226
+ int pgroup = 0;
227
+ if (opts->data[3].tag == SP_TAG_NIL) pgroup = 0;
228
+ else if (opts->data[3].tag == SP_TAG_BOOL && opts->data[3].v.i == 1) pgroup = 1;
229
+ else if (opts->data[3].tag == SP_TAG_INT) pgroup = (int)opts->data[3].v.i;
230
+ else sp_process_spawn_fail(owned, "TypeError", "pgroup must be true, 0, or Integer");
231
+
232
+ int rlimit_cpu_set = 0;
233
+ rlim_t rlimit_cpu_val = 0;
234
+ if (opts->data[4].tag == SP_TAG_INT) { rlimit_cpu_set = 1; rlimit_cpu_val = (rlim_t)opts->data[4].v.i; }
235
+ else if (opts->data[4].tag != SP_TAG_NIL)
236
+ sp_process_spawn_fail(owned, "TypeError", "rlimit_cpu must be Integer");
237
+
238
+ int rlimit_as_set = 0;
239
+ rlim_t rlimit_as_val = 0;
240
+ if (opts->data[5].tag == SP_TAG_INT) { rlimit_as_set = 1; rlimit_as_val = (rlim_t)opts->data[5].v.i; }
241
+ else if (opts->data[5].tag != SP_TAG_NIL)
242
+ sp_process_spawn_fail(owned, "TypeError", "rlimit_as must be Integer");
243
+
244
+ const char *chdir_to = NULL;
245
+ sp_RbVal chdir_v = sp_spawn_str(opts->data[6]);
246
+ if (chdir_v.tag == SP_TAG_STR) chdir_to = chdir_v.v.s;
247
+ else if (opts->data[6].tag != SP_TAG_NIL)
248
+ sp_process_spawn_fail(owned, "TypeError", "chdir must be a String");
249
+
250
+ /* Resolve cmd + args into argv. */
251
+ const char *prog = NULL;
252
+ char **argv = NULL;
253
+ sp_PolyArray *cmd_arr = NULL;
254
+ sp_PolyArray *args_arr = NULL;
255
+ int extra_from_cmd = 0;
256
+ int extra_from_args = 0;
257
+
258
+ int via_shell = 0;
259
+ cmd = sp_spawn_str(cmd);
260
+ if (cmd.tag == SP_TAG_STR) {
261
+ prog = cmd.v.s;
262
+ if (args_box.tag == SP_TAG_OBJ &&
263
+ args_box.cls_id == SP_BUILTIN_POLY_ARRAY) {
264
+ args_arr = (sp_PolyArray *)args_box.v.p;
265
+ }
266
+ else if (args_box.tag != SP_TAG_NIL) {
267
+ sp_process_spawn_fail(owned, "TypeError", "args must be a PolyArray of extra args");
268
+ }
269
+ /* One string and no arguments is a command LINE, as for Kernel#system:
270
+ CRuby hands it to the shell when it carries a shell character and
271
+ splits it into words otherwise, which the shell also does; it was
272
+ exec'd as a program name, so `spawn("sleep 2")` was ENOENT. */
273
+ if (!args_arr || args_arr->len == 0)
274
+ via_shell = strpbrk(prog, " \t\n*?{}[]<>()~&|\\$;'`\"#=%") != NULL;
275
+ }
276
+ else if (cmd.tag == SP_TAG_OBJ &&
277
+ cmd.cls_id == SP_BUILTIN_POLY_ARRAY) {
278
+ /* [program, argv0]: run program with argv0 as its argv[0], exactly two
279
+ Strings, as CRuby takes them */
280
+ cmd_arr = (sp_PolyArray *)cmd.v.p;
281
+ if (cmd_arr->len != 2) sp_process_spawn_fail(owned, "ArgumentError", "wrong first argument");
282
+ for (int i = 0; i < 2; i++)
283
+ if (sp_spawn_str(cmd_arr->data[i]).tag != SP_TAG_STR)
284
+ sp_process_spawn_fail(owned, "TypeError", sp_errf_conv(cmd_arr->data[i]));
285
+ prog = sp_spawn_str(cmd_arr->data[0]).v.s;
286
+ if (args_box.tag == SP_TAG_OBJ &&
287
+ args_box.cls_id == SP_BUILTIN_POLY_ARRAY) {
288
+ args_arr = (sp_PolyArray *)args_box.v.p;
289
+ }
290
+ }
291
+ else {
292
+ sp_process_spawn_fail(owned, "TypeError",
293
+ "wrong first argument type (expected String or Array)");
294
+ }
295
+ if (args_arr) extra_from_args = (int)args_arr->len;
296
+
297
+ int total = 1 + extra_from_cmd + extra_from_args + (via_shell ? 2 : 0);
298
+ argv = (char **)malloc(sizeof(char *) * (size_t)(total + 1));
299
+ if (!argv) sp_process_spawn_fail(owned, "NoMemoryError", "out of memory");
300
+ int ai = 0;
301
+ if (via_shell) { argv[ai++] = (char *)"/bin/sh"; argv[ai++] = (char *)"-c"; }
302
+ argv[ai++] = cmd_arr ? (char *)sp_spawn_str(cmd_arr->data[1]).v.s : (char *)prog;
303
+ if (via_shell) prog = "/bin/sh";
304
+ if (args_arr) {
305
+ for (int i = 0; i < args_arr->len; i++) {
306
+ if (sp_spawn_str(args_arr->data[i]).tag != SP_TAG_STR)
307
+ sp_process_spawn_fail(owned, "ArgumentError", "spawn args must be Strings");
308
+ argv[ai++] = (char *)sp_spawn_str(args_arr->data[i]).v.s;
309
+ }
310
+ }
311
+ argv[ai] = NULL;
312
+
313
+ #if defined(_WIN32)
314
+ /* posix_spawn, where there is no fork (lib/win32 answers it over
315
+ CreateProcess). The child's setup is the file actions -- the same dup2s and closes the fork
316
+ path makes, in the same order -- and the attributes: chdir, and a new
317
+ process group. There is no posix_spawn counterpart for the rlimits;
318
+ they are not applied on this path. The directory is checked here, so a
319
+ missing one raises naming it, as the fork path's chdir failure does;
320
+ posix_spawn reports an exec failure as its return value. */
321
+ {
322
+ posix_spawn_file_actions_t fa;
323
+ posix_spawnattr_t at;
324
+ (void)rlimit_cpu_set; (void)rlimit_cpu_val; (void)rlimit_as_set; (void)rlimit_as_val;
325
+ if (chdir_to) {
326
+ struct stat cst;
327
+ int sr = stat(chdir_to, &cst);
328
+ if (sr != 0 || !S_ISDIR(cst.st_mode)) {
329
+ int e = sr != 0 ? errno : ENOTDIR;
330
+ free(argv);
331
+ sp_last_status = 127 << 8; /* as after the fork path's child fails its chdir */
332
+ errno = e;
333
+ sp_process_spawn_fail(owned, e == ENOENT ? "Errno::ENOENT" : e == ENOTDIR ? "Errno::ENOTDIR" :
334
+ e == EACCES ? "Errno::EACCES" : "SystemCallError", sp_errf_path(e, chdir_to));
335
+ }
336
+ }
337
+ posix_spawn_file_actions_init(&fa);
338
+ posix_spawnattr_init(&at);
339
+ if (chdir_to) posix_spawn_file_actions_addchdir_np(&fa, chdir_to);
340
+ if (in_fd >= 0 && in_fd != 0) posix_spawn_file_actions_adddup2(&fa, in_fd, 0);
341
+ if (out_fd >= 0 && out_fd != 1) posix_spawn_file_actions_adddup2(&fa, out_fd, 1);
342
+ if (err_fd >= 0 && err_fd != 2) posix_spawn_file_actions_adddup2(&fa, err_fd, 2);
343
+ { int srcs[3] = { in_fd, out_fd, err_fd };
344
+ for (int i = 0; i < 3; i++) {
345
+ if (srcs[i] <= 2) continue;
346
+ int dup = 0;
347
+ for (int j = 0; j < i; j++) if (srcs[j] == srcs[i]) dup = 1;
348
+ if (!dup) posix_spawn_file_actions_addclose(&fa, srcs[i]);
349
+ } }
350
+ if (pgroup) {
351
+ posix_spawnattr_setflags(&at, POSIX_SPAWN_SETPGROUP);
352
+ posix_spawnattr_setpgroup(&at, pgroup == 1 ? 0 : (pid_t)pgroup);
353
+ }
354
+ fflush(NULL);
355
+ pid_t pid = 0;
356
+ int rc = posix_spawnp(&pid, prog, &fa, &at, argv, environ);
357
+ posix_spawn_file_actions_destroy(&fa);
358
+ posix_spawnattr_destroy(&at);
359
+ close_owned(owned);
360
+ free(argv);
361
+ if (rc != 0) {
362
+ /* $? reads the 127 a failed exec exits with under the fork path */
363
+ sp_last_status = 127 << 8;
364
+ errno = rc;
365
+ sp_raise_cls(rc == ENOENT ? "Errno::ENOENT" : rc == EACCES ? "Errno::EACCES" : "SystemCallError",
366
+ sp_errf_path(rc, prog));
367
+ }
368
+ return (sp_int)pid;
369
+ }
370
+ #else
371
+ /* Pre-exec error pipe: the child writes the exec errno here if execve
372
+ fails, so the parent can raise the matching Errno (CRuby raises
373
+ Errno::ENOENT for "no such file or directory" instead of returning
374
+ a dead pid). FD_CLOEXEC on the write end so a successful execvp
375
+ closes it; the parent then sees a zero-byte read and no raise. */
376
+ int err_pipe[2];
377
+ if (pipe(err_pipe) < 0) {
378
+ free(argv);
379
+ sp_process_spawn_fail(owned, "SystemCallError", sp_errf_errno("pipe failed", errno));
380
+ }
381
+
382
+ /* what the parent has buffered for its streams is written before the
383
+ child can write to the same descriptors, as CRuby flushes them */
384
+ fflush(NULL);
385
+ pid_t pid = fork();
386
+ if (pid < 0) {
387
+ free(argv);
388
+ sp_process_spawn_fail(owned, "SystemCallError", sp_errf_errno("fork failed", errno));
389
+ }
390
+ if (pid == 0) {
391
+ /* CHILD. If chdir or execve fails, write the errno and which of
392
+ the two failed to the parent's pipe and exit 127; the parent
393
+ will raise the matching Errno (Errno::ENOENT for "no such
394
+ file") naming the directory or the program, as CRuby does.
395
+ Using a pipe (not relying on the child's exit code alone)
396
+ because the exit code is the same regardless of exec failure
397
+ reason. */
398
+ close(err_pipe[0]);
399
+ if (fcntl(err_pipe[1], F_SETFD, FD_CLOEXEC) < 0) { _exit(126); }
400
+ if (chdir_to) {
401
+ if (chdir(chdir_to) != 0) {
402
+ int fail[2] = { errno, 1 };
403
+ (void)!write(err_pipe[1], fail, sizeof fail);
404
+ _exit(127);
405
+ }
406
+ }
407
+ if (rlimit_cpu_set) {
408
+ struct rlimit rl = { rlimit_cpu_val, RLIM_INFINITY };
409
+ setrlimit(RLIMIT_CPU, &rl);
410
+ }
411
+ if (rlimit_as_set) {
412
+ struct rlimit rl = { rlimit_as_val, RLIM_INFINITY };
413
+ setrlimit(RLIMIT_AS, &rl);
414
+ }
415
+ if (pgroup == 1) {
416
+ setpgid(0, 0);
417
+ }
418
+ else if (pgroup > 1) {
419
+ setpgid(0, pgroup);
420
+ }
421
+ apply_redirect(0, in_fd);
422
+ apply_redirect(1, out_fd);
423
+ apply_redirect(2, err_fd);
424
+ close_redirect_srcs(in_fd, out_fd, err_fd);
425
+ execvp(prog, argv);
426
+ /* exec returned: failure. Send the errno to the parent. */
427
+ int fail[2] = { errno, 0 };
428
+ (void)!write(err_pipe[1], fail, sizeof fail);
429
+ _exit(127);
430
+ }
431
+ /* PARENT. The child has its own copies of the redirections now, so the
432
+ ones this spawn opened are closed here, on the exec-failure path too;
433
+ then close the child's write end and read the errno if any. */
434
+ close(err_pipe[1]);
435
+ close_owned(owned);
436
+ int fail[2] = { 0, 0 };
437
+ ssize_t got = read(err_pipe[0], fail, sizeof fail);
438
+ close(err_pipe[0]);
439
+ free(argv);
440
+ if (got > 0) {
441
+ /* The child failed to exec: it wrote its errno and is exiting 127.
442
+ It is reaped here before the raise, as CRuby does, so a failed
443
+ spawn leaves no zombie and no stray pid for a later waitpid2(-1)
444
+ to answer with instead of ECHILD. This is a plain blocking wait,
445
+ not sp_sched_wait_child (#4381, #4528): the child has written its
446
+ errno and is exiting, so it holds nothing this thread must drain,
447
+ the wait takes no lock, and it is cheap: two hundred failed spawns
448
+ take 0.36 to 0.52 s on master and 0.39 to 0.59 s with this wait.
449
+ The polling arm would put a scheduler yield inside a half-finished
450
+ spawn instead. */
451
+ { int st = 0; pid_t r;
452
+ do { r = waitpid(pid, &st, 0); } while (r < 0 && errno == EINTR);
453
+ /* the reaped child is the last one waited for, so $? reads its
454
+ exit 127, as it does under CRuby */
455
+ if (r == pid) { sp_last_status = st; sp_last_pid = (int)pid; } }
456
+ errno = fail[0];
457
+ sp_raise_cls(errno == ENOENT ? "Errno::ENOENT" :
458
+ errno == EACCES ? "Errno::EACCES" :
459
+ "SystemCallError",
460
+ sp_errf_path(errno, fail[1] == 1 && chdir_to ? chdir_to : prog));
461
+ }
462
+ return (sp_int)pid;
463
+ #endif
464
+ }
465
+
466
+ /* Kernel#exec / Process.exec: replace the process with the command, read
467
+ as spawn reads it -- one String a command line, through the shell when it
468
+ carries a shell character; [program, argv0]; the arguments Strings. What
469
+ the program has buffered for its streams is not written, as under CRuby.
470
+ Returns only by raising the Errno the exec failed with. */
471
+ void sp_process_exec(sp_RbVal cmd, sp_RbVal args_box) {
472
+ sp_PolyArray *args = (args_box.tag == SP_TAG_OBJ && args_box.cls_id == SP_BUILTIN_POLY_ARRAY)
473
+ ? (sp_PolyArray *)args_box.v.p : NULL;
474
+ int na = args ? (int)args->len : 0;
475
+ const char *prog = NULL, *argv0 = NULL;
476
+ cmd = sp_spawn_str(cmd);
477
+ if (cmd.tag == SP_TAG_STR) prog = argv0 = cmd.v.s;
478
+ else if (cmd.tag == SP_TAG_OBJ && cmd.cls_id == SP_BUILTIN_POLY_ARRAY) {
479
+ sp_PolyArray *pa = (sp_PolyArray *)cmd.v.p;
480
+ if (pa->len != 2) sp_raise_cls("ArgumentError", "wrong first argument");
481
+ for (int i = 0; i < 2; i++)
482
+ if (sp_spawn_str(pa->data[i]).tag != SP_TAG_STR) sp_raise_cls("TypeError", sp_errf_conv(pa->data[i]));
483
+ prog = sp_spawn_str(pa->data[0]).v.s; argv0 = sp_spawn_str(pa->data[1]).v.s;
484
+ }
485
+ else sp_raise_cls("TypeError", "wrong first argument type (expected String or Array)");
486
+ for (int i = 0; i < na; i++)
487
+ if (sp_spawn_str(args->data[i]).tag != SP_TAG_STR) sp_raise_cls("TypeError", sp_errf_conv(args->data[i]));
488
+ int via_shell = cmd.tag == SP_TAG_STR && na == 0 &&
489
+ strpbrk(prog, " \t\n*?{}[]<>()~&|\\$;'`\"#=%") != NULL;
490
+ char **argv = (char **)malloc(sizeof(char *) * (size_t)(na + 4));
491
+ if (!argv) sp_raise_cls("NoMemoryError", "out of memory");
492
+ int ai = 0;
493
+ if (via_shell) { argv[ai++] = (char *)"/bin/sh"; argv[ai++] = (char *)"-c"; }
494
+ argv[ai++] = (char *)argv0;
495
+ for (int i = 0; i < na; i++) argv[ai++] = (char *)sp_spawn_str(args->data[i]).v.s;
496
+ argv[ai] = NULL;
497
+ execvp(via_shell ? "/bin/sh" : prog, argv);
498
+ int e = errno;
499
+ free(argv);
500
+ errno = e;
501
+ sp_raise_cls(e == ENOENT ? "Errno::ENOENT" : e == EACCES ? "Errno::EACCES" : "SystemCallError",
502
+ sp_errf_path(e, prog));
503
+ }
504
+
505
+ /* The wait itself lives in the scheduler (sp_sched_wait_child): a blocking
506
+ waitpid answers for the OS worker, and a started green thread is pinned to
507
+ its worker, so blocking here stalls the thread that may have to drain this
508
+ child's output before it can exit (#4381). */
509
+ /* Process.wait / waitpid: the same wait as waitpid2, answering only the
510
+ pid; the status goes to $? */
511
+ sp_int sp_process_waitpid(sp_int pid) {
512
+ extern int sp_sched_wait_child(int pid, int *status);
513
+ int status = 0;
514
+ pid_t r = (pid_t)sp_sched_wait_child((int)pid, &status);
515
+ if (r < 0) {
516
+ if (errno == ECHILD) {
517
+ sp_raise_cls("Errno::ECHILD", "No child processes");
518
+ }
519
+ sp_raise_cls("SystemCallError", sp_errf_errno("waitpid failed", errno));
520
+ }
521
+ sp_last_status = status;
522
+ sp_last_pid = (int)r;
523
+ return (sp_int)r;
524
+ }
525
+
526
+ sp_PolyArray *sp_process_waitpid2(sp_int pid) {
527
+ extern int sp_sched_wait_child(int pid, int *status); /* see the note at the top on this TU's includes */
528
+ int status = 0;
529
+ pid_t r = (pid_t)sp_sched_wait_child((int)pid, &status);
530
+ if (r < 0) {
531
+ if (errno == ECHILD) {
532
+ sp_raise_cls("Errno::ECHILD", "No child processes");
533
+ }
534
+ sp_raise_cls("SystemCallError", sp_errf_errno("waitpid failed", errno));
535
+ }
536
+ /* $? follows the child this wait reaped, as it does after Kernel#system
537
+ and a backtick; before this a waitpid2 left $? at whatever the last
538
+ system call or backtick stored. */
539
+ sp_last_status = status;
540
+ sp_last_pid = (int)r;
541
+ sp_PolyArray *pa = sp_PolyArray_new(); SP_GC_ROOT(pa); /* the status object below is an allocation */
542
+ sp_PolyArray_push(pa, sp_box_int((sp_int)r));
543
+ /* Second element is a Process::Status instance wrapping (pid, status),
544
+ not a raw int -- .signaled? / .termsig dispatch on the boxed cls_id. */
545
+ sp_PolyArray_push(pa, sp_box_process_status(sp_process_status_new((sp_int)r, (sp_int)status)));
546
+ return pa;
547
+ }