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.
- checksums.yaml +7 -0
- data/LICENSE-mruby.txt +22 -0
- data/LICENSE-spinel.txt +19 -0
- data/LICENSE.txt +21 -0
- data/README.md +61 -0
- data/ext/qr_reader_native/SPINEL_VERSION +1 -0
- data/ext/qr_reader_native/extconf.rb +8 -0
- data/ext/qr_reader_native/mruby/error.h +13 -0
- data/ext/qr_reader_native/mruby/internal.h +145 -0
- data/ext/qr_reader_native/mruby/string.h +22 -0
- data/ext/qr_reader_native/mruby.h +93 -0
- data/ext/qr_reader_native/mruby_shim.h +118 -0
- data/ext/qr_reader_native/qr_reader_native.c +13513 -0
- data/ext/qr_reader_native/qr_reader_native.h +29 -0
- data/ext/qr_reader_native/qr_reader_native_ext.c +598 -0
- data/ext/qr_reader_native/re_cased.h +71 -0
- data/ext/qr_reader_native/re_compile.c +5784 -0
- data/ext/qr_reader_native/re_ctype.h +3540 -0
- data/ext/qr_reader_native/re_exec.c +2190 -0
- data/ext/qr_reader_native/re_internal.h +671 -0
- data/ext/qr_reader_native/re_prop.h +4629 -0
- data/ext/qr_reader_native/re_spinel.c +363 -0
- data/ext/qr_reader_native/re_spinel.h +22 -0
- data/ext/qr_reader_native/re_spinel_utf8.inc +97 -0
- data/ext/qr_reader_native/re_utf8.c +156 -0
- data/ext/qr_reader_native/sp_alloc.c +1447 -0
- data/ext/qr_reader_native/sp_alloc.h +1196 -0
- data/ext/qr_reader_native/sp_argf.h +33 -0
- data/ext/qr_reader_native/sp_array.c +1052 -0
- data/ext/qr_reader_native/sp_array.h +456 -0
- data/ext/qr_reader_native/sp_bigint.c +5826 -0
- data/ext/qr_reader_native/sp_bigint.h +117 -0
- data/ext/qr_reader_native/sp_cold.c +5259 -0
- data/ext/qr_reader_native/sp_compat.h +402 -0
- data/ext/qr_reader_native/sp_core.c +818 -0
- data/ext/qr_reader_native/sp_core.h +56 -0
- data/ext/qr_reader_native/sp_crypto.c +877 -0
- data/ext/qr_reader_native/sp_crypto.h +169 -0
- data/ext/qr_reader_native/sp_dtoa.c +1533 -0
- data/ext/qr_reader_native/sp_dtoa.h +29 -0
- data/ext/qr_reader_native/sp_encoding_names.h +172 -0
- data/ext/qr_reader_native/sp_enum.h +86 -0
- data/ext/qr_reader_native/sp_exc.c +1185 -0
- data/ext/qr_reader_native/sp_exc.h +186 -0
- data/ext/qr_reader_native/sp_exc_ctx.h +56 -0
- data/ext/qr_reader_native/sp_fiber.c +889 -0
- data/ext/qr_reader_native/sp_fiber.h +162 -0
- data/ext/qr_reader_native/sp_fiber_ctx.h +84 -0
- data/ext/qr_reader_native/sp_format.c +695 -0
- data/ext/qr_reader_native/sp_format.h +83 -0
- data/ext/qr_reader_native/sp_gc.c +1793 -0
- data/ext/qr_reader_native/sp_gc.h +762 -0
- data/ext/qr_reader_native/sp_hash.c +131 -0
- data/ext/qr_reader_native/sp_hash.h +131 -0
- data/ext/qr_reader_native/sp_inspect.c +215 -0
- data/ext/qr_reader_native/sp_inspect.h +128 -0
- data/ext/qr_reader_native/sp_io.c +1676 -0
- data/ext/qr_reader_native/sp_io.h +272 -0
- data/ext/qr_reader_native/sp_iobuffer.c +1014 -0
- data/ext/qr_reader_native/sp_iobuffer.h +167 -0
- data/ext/qr_reader_native/sp_marshal.c +487 -0
- data/ext/qr_reader_native/sp_marshal.h +60 -0
- data/ext/qr_reader_native/sp_net.c +1029 -0
- data/ext/qr_reader_native/sp_net.h +205 -0
- data/ext/qr_reader_native/sp_pack.c +1254 -0
- data/ext/qr_reader_native/sp_poly_cold.c +1220 -0
- data/ext/qr_reader_native/sp_proc.c +410 -0
- data/ext/qr_reader_native/sp_proc.h +327 -0
- data/ext/qr_reader_native/sp_process.c +547 -0
- data/ext/qr_reader_native/sp_process_status.c +211 -0
- data/ext/qr_reader_native/sp_process_status.h +66 -0
- data/ext/qr_reader_native/sp_random.c +287 -0
- data/ext/qr_reader_native/sp_random.h +52 -0
- data/ext/qr_reader_native/sp_range.h +144 -0
- data/ext/qr_reader_native/sp_re.c +1379 -0
- data/ext/qr_reader_native/sp_re.h +186 -0
- data/ext/qr_reader_native/sp_re_names.h +59 -0
- data/ext/qr_reader_native/sp_sched.c +4044 -0
- data/ext/qr_reader_native/sp_sched.h +295 -0
- data/ext/qr_reader_native/sp_slab.c +1507 -0
- data/ext/qr_reader_native/sp_str.c +1462 -0
- data/ext/qr_reader_native/sp_str.h +306 -0
- data/ext/qr_reader_native/sp_str_crypt.c +23 -0
- data/ext/qr_reader_native/sp_string.c +92 -0
- data/ext/qr_reader_native/sp_string.h +253 -0
- data/ext/qr_reader_native/sp_system.c +78 -0
- data/ext/qr_reader_native/sp_system.h +18 -0
- data/ext/qr_reader_native/sp_time.c +860 -0
- data/ext/qr_reader_native/sp_time.h +100 -0
- data/ext/qr_reader_native/sp_types.h +292 -0
- data/ext/qr_reader_native/spinel_rt.h +19017 -0
- data/ext/qr_reader_native/unicase.c +399 -0
- data/ext/qr_reader_native/unicase.h +901 -0
- data/lib/qr_reader/native.rb +6 -0
- 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
|
+
}
|