/home/travis/build/MoarVM/MoarVM/src/io/procops.c
Line | Count | Source (jump to first uncovered line) |
1 | | #include "moar.h" |
2 | | #include "platform/time.h" |
3 | | #include "tinymt64.h" |
4 | | |
5 | | /* concatenating with "" ensures that only literal strings are accepted as argument. */ |
6 | 141 | #define STR_WITH_LEN(str) ("" str ""), (sizeof(str) - 1) |
7 | | |
8 | | /* MSVC compilers know about environ, |
9 | | * see http://msdn.microsoft.com/en-us//library/vstudio/stxk41x1.aspx */ |
10 | | #ifndef _WIN32 |
11 | | #include <unistd.h> |
12 | | # ifdef __APPLE_CC__ |
13 | | # include <crt_externs.h> |
14 | | # define environ (*_NSGetEnviron()) |
15 | | # else |
16 | | extern char **environ; |
17 | | # endif |
18 | | #else |
19 | | #include <stdlib.h> |
20 | | #endif |
21 | | |
22 | | #ifdef _WIN32 |
23 | | static wchar_t * ANSIToUnicode(MVMuint16 acp, const char *str) |
24 | | { |
25 | | const int len = MultiByteToWideChar(acp, 0, str, -1, NULL, 0); |
26 | | wchar_t * const result = (wchar_t *)MVM_malloc(len * sizeof(wchar_t)); |
27 | | |
28 | | MultiByteToWideChar(acp, 0, str, -1, (LPWSTR)result, len); |
29 | | |
30 | | return result; |
31 | | } |
32 | | |
33 | | static char * UnicodeToUTF8(const wchar_t *str) |
34 | | { |
35 | | const int len = WideCharToMultiByte(CP_UTF8, 0, str, -1, NULL, 0, NULL, NULL); |
36 | | char * const result = (char *)MVM_malloc(len + 1); |
37 | | |
38 | | WideCharToMultiByte(CP_UTF8, 0, str, -1, result, len, NULL, NULL); |
39 | | |
40 | | return result; |
41 | | } |
42 | | |
43 | | static char * ANSIToUTF8(MVMuint16 acp, const char * str) |
44 | | { |
45 | | wchar_t * const wstr = ANSIToUnicode(acp, str); |
46 | | char * const result = UnicodeToUTF8(wstr); |
47 | | |
48 | | MVM_free(wstr); |
49 | | return result; |
50 | | } |
51 | | |
52 | | MVM_PUBLIC char ** |
53 | | MVM_UnicodeToUTF8_argv(const int argc, wchar_t **wargv) |
54 | | { |
55 | | int i; |
56 | | char **argv = MVM_malloc((argc + 1) * sizeof(*argv)); |
57 | | for (i = 0; i < argc; ++i) |
58 | | { |
59 | | argv[i] = UnicodeToUTF8(wargv[i]); |
60 | | } |
61 | | argv[i] = NULL; |
62 | | return argv; |
63 | | } |
64 | | |
65 | | #endif |
66 | | |
67 | 141 | MVMObject * MVM_proc_getenvhash(MVMThreadContext *tc) { |
68 | 141 | MVMInstance * const instance = tc->instance; |
69 | 141 | MVMObject * env_hash; |
70 | 141 | |
71 | 141 | MVMuint32 pos = 0; |
72 | 141 | MVMString *needle = MVM_string_ascii_decode(tc, instance->VMString, STR_WITH_LEN("=")); |
73 | 141 | #ifndef _WIN32 |
74 | 141 | char *env; |
75 | 141 | #else |
76 | | wchar_t *env; |
77 | | (void) _wgetenv(L"windows"); /* populate _wenviron */ |
78 | | #endif |
79 | 141 | |
80 | 141 | MVM_gc_root_temp_push(tc, (MVMCollectable **)&needle); |
81 | 141 | |
82 | 141 | env_hash = MVM_repr_alloc_init(tc, MVM_hll_current(tc)->slurpy_hash_type); |
83 | 141 | MVM_gc_root_temp_push(tc, (MVMCollectable **)&env_hash); |
84 | 141 | |
85 | 141 | #ifndef _WIN32 |
86 | 15.3k | while ((env = environ[pos++]) != NULL) { |
87 | 15.2k | MVMString *str = MVM_string_utf8_c8_decode(tc, instance->VMString, env, strlen(env)); |
88 | 15.2k | #else |
89 | | while ((env = _wenviron[pos++]) != NULL) { |
90 | | char * const _env = UnicodeToUTF8(env); |
91 | | MVMString *str = MVM_string_utf8_c8_decode(tc, instance->VMString, _env, strlen(_env)); |
92 | | #endif |
93 | 15.2k | |
94 | 15.2k | MVMuint32 index = MVM_string_index(tc, str, needle, 0); |
95 | 15.2k | |
96 | 15.2k | MVMString *key, *val; |
97 | 15.2k | MVMObject *box; |
98 | 15.2k | |
99 | 15.2k | #ifdef _WIN32 |
100 | | MVM_free(_env); |
101 | | #endif |
102 | 15.2k | MVM_gc_root_temp_push(tc, (MVMCollectable **)&str); |
103 | 15.2k | |
104 | 15.2k | key = MVM_string_substring(tc, str, 0, index); |
105 | 15.2k | MVM_gc_root_temp_push(tc, (MVMCollectable **)&key); |
106 | 15.2k | |
107 | 15.2k | val = MVM_string_substring(tc, str, index + 1, -1); |
108 | 15.2k | box = MVM_repr_box_str(tc, MVM_hll_current(tc)->str_box_type, val); |
109 | 15.2k | MVM_repr_bind_key_o(tc, env_hash, key, box); |
110 | 15.2k | |
111 | 15.2k | MVM_gc_root_temp_pop_n(tc, 2); |
112 | 15.2k | } |
113 | 141 | |
114 | 141 | MVM_gc_root_temp_pop_n(tc, 2); |
115 | 141 | |
116 | 141 | return env_hash; |
117 | 141 | } |
118 | | |
119 | | #define INIT_ENV() do { \ |
120 | | MVMROOT(tc, iter, { \ |
121 | | MVMString * const equal = MVM_string_ascii_decode(tc, tc->instance->VMString, STR_WITH_LEN("=")); \ |
122 | | MVMROOT(tc, equal, { \ |
123 | | MVMString *env_str = NULL; \ |
124 | | MVMObject *iterval = NULL; \ |
125 | | i = 0; \ |
126 | | while(MVM_iter_istrue(tc, iter)) { \ |
127 | | MVM_repr_shift_o(tc, (MVMObject *)iter); \ |
128 | | env_str = MVM_string_concatenate(tc, MVM_iterkey_s(tc, iter), equal); \ |
129 | | iterval = MVM_iterval(tc, iter); \ |
130 | | env_str = MVM_string_concatenate(tc, env_str, MVM_repr_get_str(tc, iterval)); \ |
131 | | _env[i++] = MVM_string_utf8_c8_encode_C_string(tc, env_str); \ |
132 | | } \ |
133 | | _env[size] = NULL; \ |
134 | | }); \ |
135 | | }); \ |
136 | | } while (0) |
137 | | |
138 | 9 | #define FREE_ENV() do { \ |
139 | 9 | i = 0; \ |
140 | 984 | while(_env[i]) \ |
141 | 975 | MVM_free(_env[i++]); \ |
142 | 9 | MVM_free(_env); \ |
143 | 9 | } while (0) |
144 | | |
145 | 9 | static void spawn_on_exit(uv_process_t *req, MVMint64 exit_status, int term_signal) { |
146 | 9 | if (req->data) |
147 | 9 | *(MVMint64 *)req->data = (exit_status << 8) | term_signal; |
148 | 9 | uv_unref((uv_handle_t *)req); |
149 | 9 | uv_close((uv_handle_t *)req, NULL); |
150 | 9 | } |
151 | | |
152 | | static void setup_process_stdio(MVMThreadContext *tc, MVMObject *handle, uv_process_t *process, |
153 | 27 | uv_stdio_container_t *stdio, int fd, MVMint64 flags, const char *op) { |
154 | 27 | if (flags & MVM_PIPE_CAPTURE) { |
155 | 6 | MVMIOSyncPipeData *pipedata; |
156 | 6 | |
157 | 6 | if (REPR(handle)->ID != MVM_REPR_ID_MVMOSHandle) |
158 | 0 | MVM_exception_throw_adhoc(tc, "%s requires an object with REPR MVMOSHandle (got %s with REPR %s)", op, STABLE(handle)->debug_name, REPR(handle)->name); |
159 | 6 | |
160 | 6 | pipedata = (MVMIOSyncPipeData *)((MVMOSHandle *)handle)->body.data; |
161 | 6 | pipedata->process = process; |
162 | 6 | |
163 | 6 | stdio->flags = UV_CREATE_PIPE | (fd == 0 ? UV_READABLE_PIPE : UV_WRITABLE_PIPE); |
164 | 6 | stdio->data.stream = pipedata->ss.handle; |
165 | 6 | } |
166 | 21 | else if (flags & MVM_PIPE_INHERIT) { |
167 | 21 | if (handle == tc->instance->VMNull) { |
168 | 21 | stdio->flags = UV_INHERIT_FD; |
169 | 21 | stdio->data.fd = fd; |
170 | 21 | } |
171 | 0 | else { |
172 | 0 | MVMOSHandleBody body = ((MVMOSHandle *)handle)->body; |
173 | 0 |
|
174 | 0 | if (REPR(handle)->ID != MVM_REPR_ID_MVMOSHandle) |
175 | 0 | MVM_exception_throw_adhoc(tc, "%s requires an object with REPR MVMOSHandle (got %s with REPR %s)", op, STABLE(handle)->debug_name, REPR(handle)->name); |
176 | 0 |
|
177 | 0 | body.ops->pipeable->bind_stdio_handle(tc, ((MVMOSHandle *)handle), stdio, process); |
178 | 0 | } |
179 | 21 | } |
180 | 21 | else |
181 | 0 | stdio->flags = UV_IGNORE; |
182 | 27 | } |
183 | | |
184 | | MVMint64 MVM_proc_shell(MVMThreadContext *tc, MVMString *cmd, MVMString *cwd, MVMObject *env, |
185 | 9 | MVMObject *in, MVMObject *out, MVMObject *err, MVMint64 flags) { |
186 | 9 | MVMint64 result = 0, spawn_result; |
187 | 9 | uv_process_t *process = MVM_calloc(1, sizeof(uv_process_t)); |
188 | 9 | uv_process_options_t process_options = {0}; |
189 | 9 | uv_stdio_container_t process_stdio[3]; |
190 | 9 | int i, process_still_running; |
191 | 9 | |
192 | 9 | char * const cmdin = MVM_string_utf8_c8_encode_C_string(tc, cmd); |
193 | 9 | char * const _cwd = MVM_string_utf8_c8_encode_C_string(tc, cwd); |
194 | 9 | const MVMuint64 size = MVM_repr_elems(tc, env); |
195 | 9 | char **_env = MVM_malloc((size + 1) * sizeof(char *)); |
196 | 9 | MVMIter *iter; |
197 | 9 | |
198 | 9 | #ifdef _WIN32 |
199 | | const MVMuint16 acp = GetACP(); /* We should get ACP at runtime. */ |
200 | | char * const _cmd = ANSIToUTF8(acp, getenv("ComSpec")); |
201 | | char *args[3]; |
202 | | args[0] = "/c"; |
203 | | args[1] = cmdin; |
204 | | args[2] = NULL; |
205 | | #else |
206 | 9 | char * const _cmd = "/bin/sh"; |
207 | 9 | char *args[4]; |
208 | 9 | args[0] = "/bin/sh"; |
209 | 9 | args[1] = "-c"; |
210 | 9 | args[2] = cmdin; |
211 | 9 | args[3] = NULL; |
212 | 9 | #endif |
213 | 9 | |
214 | 9 | MVMROOT(tc, in, { |
215 | 9 | MVMROOT(tc, out, { |
216 | 9 | MVMROOT(tc, err, { |
217 | 9 | iter = (MVMIter *)MVM_iter(tc, env); |
218 | 9 | INIT_ENV(); |
219 | 9 | }); |
220 | 9 | }); |
221 | 9 | }); |
222 | 9 | |
223 | 9 | setup_process_stdio(tc, in, process, &process_stdio[0], 0, flags, "shell"); |
224 | 9 | setup_process_stdio(tc, out, process, &process_stdio[1], 1, flags >> 3, "shell"); |
225 | 9 | if (!(flags & MVM_PIPE_MERGED_OUT_ERR)) |
226 | 9 | setup_process_stdio(tc, err, process, &process_stdio[2], 2, flags >> 6, "shell"); |
227 | 9 | |
228 | 9 | process_options.stdio = process_stdio; |
229 | 9 | process_options.file = _cmd; |
230 | 9 | process_options.args = args; |
231 | 9 | process_options.cwd = _cwd; |
232 | 9 | process_options.flags = UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS | UV_PROCESS_WINDOWS_HIDE; |
233 | 9 | process_options.env = _env; |
234 | 9 | if (flags & MVM_PIPE_MERGED_OUT_ERR) { |
235 | 0 | process_options.stdio_count = 2; |
236 | 0 | } |
237 | 9 | else |
238 | 9 | process_options.stdio_count = 3; |
239 | 9 | process_options.exit_cb = spawn_on_exit; |
240 | 9 | if (flags & (MVM_PIPE_CAPTURE_IN | MVM_PIPE_CAPTURE_OUT | MVM_PIPE_CAPTURE_ERR)) { |
241 | 4 | process_still_running = 1; |
242 | 4 | process->data = MVM_calloc(1, sizeof(MVMint64)); |
243 | 4 | uv_ref((uv_handle_t *)process); |
244 | 4 | spawn_result = uv_spawn(tc->loop, process, &process_options); |
245 | 4 | if (spawn_result) |
246 | 0 | result = spawn_result; |
247 | 4 | } |
248 | 5 | else { |
249 | 5 | process_still_running = 0; |
250 | 5 | process->data = &result; |
251 | 5 | uv_ref((uv_handle_t *)process); |
252 | 5 | spawn_result = uv_spawn(tc->loop, process, &process_options); |
253 | 5 | if (spawn_result) |
254 | 0 | result = spawn_result; |
255 | 5 | else |
256 | 5 | uv_run(tc->loop, UV_RUN_DEFAULT); |
257 | 5 | } |
258 | 9 | |
259 | 9 | FREE_ENV(); |
260 | 9 | MVM_free(_cwd); |
261 | 9 | #ifdef _WIN32 |
262 | | MVM_free(_cmd); |
263 | | #endif |
264 | 9 | MVM_free(cmdin); |
265 | 9 | uv_unref((uv_handle_t *)process); |
266 | 9 | |
267 | 9 | if (!process_still_running) |
268 | 5 | MVM_free(process); |
269 | 9 | |
270 | 9 | return result; |
271 | 9 | } |
272 | | |
273 | | MVMint64 MVM_proc_spawn(MVMThreadContext *tc, MVMObject *argv, MVMString *cwd, MVMObject *env, |
274 | 0 | MVMObject *in, MVMObject *out, MVMObject *err, MVMint64 flags) { |
275 | 0 | MVMint64 result = 0, spawn_result; |
276 | 0 | uv_process_t *process = MVM_calloc(1, sizeof(uv_process_t)); |
277 | 0 | uv_process_options_t process_options = {0}; |
278 | 0 | uv_stdio_container_t process_stdio[3]; |
279 | 0 | int i; |
280 | 0 |
|
281 | 0 | char * const _cwd = MVM_string_utf8_c8_encode_C_string(tc, cwd); |
282 | 0 | const MVMuint64 size = MVM_repr_elems(tc, env); |
283 | 0 | char **_env = MVM_malloc((size + 1) * sizeof(char *)); |
284 | 0 | const MVMuint64 arg_size = MVM_repr_elems(tc, argv); |
285 | 0 | char **args = MVM_malloc((arg_size + 1) * sizeof(char *)); |
286 | 0 | MVMRegister reg; |
287 | 0 | MVMIter *iter; |
288 | 0 |
|
289 | 0 | i = 0; |
290 | 0 | while(i < arg_size) { |
291 | 0 | REPR(argv)->pos_funcs.at_pos(tc, STABLE(argv), argv, OBJECT_BODY(argv), i, ®, MVM_reg_obj); |
292 | 0 | args[i++] = MVM_string_utf8_c8_encode_C_string(tc, MVM_repr_get_str(tc, reg.o)); |
293 | 0 | } |
294 | 0 | args[arg_size] = NULL; |
295 | 0 |
|
296 | 0 | MVMROOT(tc, in, { |
297 | 0 | MVMROOT(tc, out, { |
298 | 0 | MVMROOT(tc, err, { |
299 | 0 | iter = (MVMIter *)MVM_iter(tc, env); |
300 | 0 | INIT_ENV(); |
301 | 0 | }); |
302 | 0 | }); |
303 | 0 | }); |
304 | 0 |
|
305 | 0 | setup_process_stdio(tc, in, process, &process_stdio[0], 0, flags, "spawn"); |
306 | 0 | setup_process_stdio(tc, out, process, &process_stdio[1], 1, flags >> 3, "spawn"); |
307 | 0 | if (!(flags & MVM_PIPE_MERGED_OUT_ERR)) |
308 | 0 | setup_process_stdio(tc, err, process, &process_stdio[2], 2, flags >> 6, "spawn"); |
309 | 0 |
|
310 | 0 | process_options.stdio = process_stdio; |
311 | 0 | process_options.file = arg_size ? args[0] : NULL; |
312 | 0 | process_options.args = args; |
313 | 0 | process_options.cwd = _cwd; |
314 | 0 | process_options.flags = UV_PROCESS_WINDOWS_HIDE; |
315 | 0 | process_options.env = _env; |
316 | 0 | if (flags & MVM_PIPE_MERGED_OUT_ERR) { |
317 | 0 | process_options.stdio_count = 2; |
318 | 0 | } |
319 | 0 | else |
320 | 0 | process_options.stdio_count = 3; |
321 | 0 | process_options.exit_cb = spawn_on_exit; |
322 | 0 | if (flags & (MVM_PIPE_CAPTURE_IN | MVM_PIPE_CAPTURE_OUT | MVM_PIPE_CAPTURE_ERR)) { |
323 | 0 | process->data = MVM_calloc(1, sizeof(MVMint64)); |
324 | 0 | uv_ref((uv_handle_t *)process); |
325 | 0 | spawn_result = uv_spawn(tc->loop, process, &process_options); |
326 | 0 | if (spawn_result) |
327 | 0 | result = spawn_result; |
328 | 0 | } |
329 | 0 | else { |
330 | 0 | process->data = &result; |
331 | 0 | uv_ref((uv_handle_t *)process); |
332 | 0 | spawn_result = uv_spawn(tc->loop, process, &process_options); |
333 | 0 | if (spawn_result) |
334 | 0 | result = spawn_result; |
335 | 0 | else |
336 | 0 | uv_run(tc->loop, UV_RUN_DEFAULT); |
337 | 0 | } |
338 | 0 |
|
339 | 0 | FREE_ENV(); |
340 | 0 | MVM_free(_cwd); |
341 | 0 | uv_unref((uv_handle_t *)process); |
342 | 0 |
|
343 | 0 | i = 0; |
344 | 0 | while(args[i]) |
345 | 0 | MVM_free(args[i++]); |
346 | 0 |
|
347 | 0 | MVM_free(args); |
348 | 0 |
|
349 | 0 | return result; |
350 | 0 | } |
351 | | |
352 | | /* Data that we keep for an asynchronous process handle. */ |
353 | | typedef struct { |
354 | | /* The libuv handle to the process. */ |
355 | | uv_process_t *handle; |
356 | | |
357 | | /* The async task handle, provided we're running. */ |
358 | | MVMObject *async_task; |
359 | | |
360 | | /* The exit signal to send, if any. */ |
361 | | MVMint64 signal; |
362 | | } MVMIOAsyncProcessData; |
363 | | |
364 | | typedef enum { |
365 | | STATE_UNSTARTED, |
366 | | STATE_STARTED, |
367 | | STATE_DONE |
368 | | } ProcessState; |
369 | | |
370 | | /* Info we convey about an async spawn task. */ |
371 | | typedef struct { |
372 | | MVMThreadContext *tc; |
373 | | int work_idx; |
374 | | MVMObject *handle; |
375 | | MVMObject *callbacks; |
376 | | char *prog; |
377 | | char *cwd; |
378 | | char **env; |
379 | | char **args; |
380 | | MVMuint32 seq_stdout; |
381 | | MVMuint32 seq_stderr; |
382 | | uv_stream_t *stdin_handle; |
383 | | ProcessState state; |
384 | | int using; |
385 | | } SpawnInfo; |
386 | | |
387 | | /* Info we convey about a write task. */ |
388 | | typedef struct { |
389 | | MVMOSHandle *handle; |
390 | | MVMString *str_data; |
391 | | MVMObject *buf_data; |
392 | | uv_write_t *req; |
393 | | uv_buf_t buf; |
394 | | MVMThreadContext *tc; |
395 | | int work_idx; |
396 | | } SpawnWriteInfo; |
397 | | |
398 | | /* Completion handler for an asynchronous write. */ |
399 | 0 | static void on_write(uv_write_t *req, int status) { |
400 | 0 | SpawnWriteInfo *wi = (SpawnWriteInfo *)req->data; |
401 | 0 | MVMThreadContext *tc = wi->tc; |
402 | 0 | MVMObject *arr = MVM_repr_alloc_init(tc, tc->instance->boot_types.BOOTArray); |
403 | 0 | MVMAsyncTask *t = MVM_io_eventloop_get_active_work(tc, wi->work_idx); |
404 | 0 | MVM_repr_push_o(tc, arr, t->body.schedulee); |
405 | 0 | if (status >= 0) { |
406 | 0 | MVMROOT(tc, arr, { |
407 | 0 | MVMROOT(tc, t, { |
408 | 0 | MVMObject *bytes_box = MVM_repr_box_int(tc, |
409 | 0 | tc->instance->boot_types.BOOTInt, |
410 | 0 | wi->buf.len); |
411 | 0 | MVM_repr_push_o(tc, arr, bytes_box); |
412 | 0 | }); |
413 | 0 | }); |
414 | 0 | MVM_repr_push_o(tc, arr, tc->instance->boot_types.BOOTStr); |
415 | 0 | } |
416 | 0 | else { |
417 | 0 | MVM_repr_push_o(tc, arr, tc->instance->boot_types.BOOTInt); |
418 | 0 | MVMROOT(tc, arr, { |
419 | 0 | MVMROOT(tc, t, { |
420 | 0 | MVMString *msg_str = MVM_string_ascii_decode_nt(tc, |
421 | 0 | tc->instance->VMString, uv_strerror(status)); |
422 | 0 | MVMObject *msg_box = MVM_repr_box_str(tc, |
423 | 0 | tc->instance->boot_types.BOOTStr, msg_str); |
424 | 0 | MVM_repr_push_o(tc, arr, msg_box); |
425 | 0 | }); |
426 | 0 | }); |
427 | 0 | } |
428 | 0 | MVM_repr_push_o(tc, t->body.queue, arr); |
429 | 0 | if (wi->str_data) |
430 | 0 | MVM_free(wi->buf.base); |
431 | 0 | MVM_io_eventloop_remove_active_work(tc, &(wi->work_idx)); |
432 | 0 | MVM_free(wi->req); |
433 | 0 | } |
434 | | |
435 | | /* Does setup work for an asynchronous write. */ |
436 | 0 | static void write_setup(MVMThreadContext *tc, uv_loop_t *loop, MVMObject *async_task, void *data) { |
437 | 0 | MVMIOAsyncProcessData *handle_data; |
438 | 0 | MVMAsyncTask *spawn_task; |
439 | 0 | SpawnInfo *si; |
440 | 0 | char *output; |
441 | 0 | int output_size, r; |
442 | 0 |
|
443 | 0 | /* Add to work in progress. */ |
444 | 0 | SpawnWriteInfo *wi = (SpawnWriteInfo *)data; |
445 | 0 | wi->tc = tc; |
446 | 0 | wi->work_idx = MVM_io_eventloop_add_active_work(tc, async_task); |
447 | 0 |
|
448 | 0 | /* Encode the string, or extract buf data. */ |
449 | 0 | if (wi->str_data) { |
450 | 0 | MVMuint64 output_size_64; |
451 | 0 | output = MVM_string_utf8_encode(tc, wi->str_data, &output_size_64, 1); |
452 | 0 | output_size = (int)output_size_64; |
453 | 0 | } |
454 | 0 | else { |
455 | 0 | MVMArray *buffer = (MVMArray *)wi->buf_data; |
456 | 0 | output = (char *)(buffer->body.slots.i8 + buffer->body.start); |
457 | 0 | output_size = (int)buffer->body.elems; |
458 | 0 | } |
459 | 0 |
|
460 | 0 | /* Create and initialize write request. */ |
461 | 0 | wi->req = MVM_malloc(sizeof(uv_write_t)); |
462 | 0 | wi->buf = uv_buf_init(output, output_size); |
463 | 0 | wi->req->data = data; |
464 | 0 | handle_data = (MVMIOAsyncProcessData *)wi->handle->body.data; |
465 | 0 | spawn_task = (MVMAsyncTask *)handle_data->async_task; |
466 | 0 | si = spawn_task ? (SpawnInfo *)spawn_task->body.data : NULL; |
467 | 0 | if (!si || !si->stdin_handle || (r = uv_write(wi->req, si->stdin_handle, &(wi->buf), 1, on_write)) < 0) { |
468 | 0 | /* Error; need to notify. */ |
469 | 0 | MVMROOT(tc, async_task, { |
470 | 0 | MVMObject *arr = MVM_repr_alloc_init(tc, tc->instance->boot_types.BOOTArray); |
471 | 0 | MVMAsyncTask *t = (MVMAsyncTask *)async_task; |
472 | 0 | MVM_repr_push_o(tc, arr, t->body.schedulee); |
473 | 0 | MVM_repr_push_o(tc, arr, tc->instance->boot_types.BOOTInt); |
474 | 0 | MVMROOT(tc, arr, { |
475 | 0 | MVMString *msg_str = MVM_string_ascii_decode_nt(tc, |
476 | 0 | tc->instance->VMString, (si && si->stdin_handle |
477 | 0 | ? uv_strerror(r) |
478 | 0 | : "This process is not opened for write")); |
479 | 0 | MVMObject *msg_box = MVM_repr_box_str(tc, |
480 | 0 | tc->instance->boot_types.BOOTStr, msg_str); |
481 | 0 | MVM_repr_push_o(tc, arr, msg_box); |
482 | 0 | }); |
483 | 0 | MVM_repr_push_o(tc, t->body.queue, arr); |
484 | 0 | }); |
485 | 0 |
|
486 | 0 | /* Cleanup handle. */ |
487 | 0 | MVM_free(wi->req); |
488 | 0 | wi->req = NULL; |
489 | 0 | } |
490 | 0 | } |
491 | | |
492 | | /* Marks objects for a write task. */ |
493 | 0 | static void write_gc_mark(MVMThreadContext *tc, void *data, MVMGCWorklist *worklist) { |
494 | 0 | SpawnWriteInfo *wi = (SpawnWriteInfo *)data; |
495 | 0 | MVM_gc_worklist_add(tc, worklist, &wi->handle); |
496 | 0 | MVM_gc_worklist_add(tc, worklist, &wi->str_data); |
497 | 0 | MVM_gc_worklist_add(tc, worklist, &wi->buf_data); |
498 | 0 | } |
499 | | |
500 | | /* Frees info for a write task. */ |
501 | 0 | static void write_gc_free(MVMThreadContext *tc, MVMObject *t, void *data) { |
502 | 0 | if (data) |
503 | 0 | MVM_free(data); |
504 | 0 | } |
505 | | |
506 | | /* Operations table for async write task. */ |
507 | | static const MVMAsyncTaskOps write_op_table = { |
508 | | write_setup, |
509 | | NULL, |
510 | | write_gc_mark, |
511 | | write_gc_free |
512 | | }; |
513 | | |
514 | | static MVMAsyncTask * write_str(MVMThreadContext *tc, MVMOSHandle *h, MVMObject *queue, |
515 | 0 | MVMObject *schedulee, MVMString *s, MVMObject *async_type) { |
516 | 0 | MVMAsyncTask *task; |
517 | 0 | SpawnWriteInfo *wi; |
518 | 0 |
|
519 | 0 | /* Validate REPRs. */ |
520 | 0 | if (REPR(queue)->ID != MVM_REPR_ID_ConcBlockingQueue) |
521 | 0 | MVM_exception_throw_adhoc(tc, |
522 | 0 | "asyncwritestr target queue must have ConcBlockingQueue REPR"); |
523 | 0 | if (REPR(async_type)->ID != MVM_REPR_ID_MVMAsyncTask) |
524 | 0 | MVM_exception_throw_adhoc(tc, |
525 | 0 | "asyncwritestr result type must have REPR AsyncTask"); |
526 | 0 |
|
527 | 0 | /* Create async task handle. */ |
528 | 0 | MVMROOT(tc, queue, { |
529 | 0 | MVMROOT(tc, schedulee, { |
530 | 0 | MVMROOT(tc, h, { |
531 | 0 | MVMROOT(tc, s, { |
532 | 0 | task = (MVMAsyncTask *)MVM_repr_alloc_init(tc, async_type); |
533 | 0 | }); |
534 | 0 | }); |
535 | 0 | }); |
536 | 0 | }); |
537 | 0 | MVM_ASSIGN_REF(tc, &(task->common.header), task->body.queue, queue); |
538 | 0 | MVM_ASSIGN_REF(tc, &(task->common.header), task->body.schedulee, schedulee); |
539 | 0 | task->body.ops = &write_op_table; |
540 | 0 | wi = MVM_calloc(1, sizeof(SpawnWriteInfo)); |
541 | 0 | MVM_ASSIGN_REF(tc, &(task->common.header), wi->handle, h); |
542 | 0 | MVM_ASSIGN_REF(tc, &(task->common.header), wi->str_data, s); |
543 | 0 | task->body.data = wi; |
544 | 0 |
|
545 | 0 | /* Hand the task off to the event loop. */ |
546 | 0 | MVMROOT(tc, task, { |
547 | 0 | MVM_io_eventloop_queue_work(tc, (MVMObject *)task); |
548 | 0 | }); |
549 | 0 |
|
550 | 0 | return task; |
551 | 0 | } |
552 | | |
553 | | static MVMAsyncTask * write_bytes(MVMThreadContext *tc, MVMOSHandle *h, MVMObject *queue, |
554 | 0 | MVMObject *schedulee, MVMObject *buffer, MVMObject *async_type) { |
555 | 0 | MVMAsyncTask *task; |
556 | 0 | SpawnWriteInfo *wi; |
557 | 0 |
|
558 | 0 | /* Validate REPRs. */ |
559 | 0 | if (REPR(queue)->ID != MVM_REPR_ID_ConcBlockingQueue) |
560 | 0 | MVM_exception_throw_adhoc(tc, |
561 | 0 | "asyncwritebytes target queue must have ConcBlockingQueue REPR"); |
562 | 0 | if (REPR(async_type)->ID != MVM_REPR_ID_MVMAsyncTask) |
563 | 0 | MVM_exception_throw_adhoc(tc, |
564 | 0 | "asyncwritebytes result type must have REPR AsyncTask"); |
565 | 0 | if (!IS_CONCRETE(buffer) || REPR(buffer)->ID != MVM_REPR_ID_VMArray) |
566 | 0 | MVM_exception_throw_adhoc(tc, "asyncwritebytes requires a native array to read from"); |
567 | 0 | if (((MVMArrayREPRData *)STABLE(buffer)->REPR_data)->slot_type != MVM_ARRAY_U8 |
568 | 0 | && ((MVMArrayREPRData *)STABLE(buffer)->REPR_data)->slot_type != MVM_ARRAY_I8) |
569 | 0 | MVM_exception_throw_adhoc(tc, "asyncwritebytes requires a native array of uint8 or int8"); |
570 | 0 |
|
571 | 0 | /* Create async task handle. */ |
572 | 0 | MVMROOT(tc, queue, { |
573 | 0 | MVMROOT(tc, schedulee, { |
574 | 0 | MVMROOT(tc, h, { |
575 | 0 | MVMROOT(tc, buffer, { |
576 | 0 | task = (MVMAsyncTask *)MVM_repr_alloc_init(tc, async_type); |
577 | 0 | }); |
578 | 0 | }); |
579 | 0 | }); |
580 | 0 | }); |
581 | 0 | MVM_ASSIGN_REF(tc, &(task->common.header), task->body.queue, queue); |
582 | 0 | MVM_ASSIGN_REF(tc, &(task->common.header), task->body.schedulee, schedulee); |
583 | 0 | task->body.ops = &write_op_table; |
584 | 0 | wi = MVM_calloc(1, sizeof(SpawnWriteInfo)); |
585 | 0 | MVM_ASSIGN_REF(tc, &(task->common.header), wi->handle, h); |
586 | 0 | MVM_ASSIGN_REF(tc, &(task->common.header), wi->buf_data, buffer); |
587 | 0 | task->body.data = wi; |
588 | 0 |
|
589 | 0 | /* Hand the task off to the event loop. */ |
590 | 0 | MVMROOT(tc, task, { |
591 | 0 | MVM_io_eventloop_queue_work(tc, (MVMObject *)task); |
592 | 0 | }); |
593 | 0 |
|
594 | 0 | return task; |
595 | 0 | } |
596 | | |
597 | | /* Marks an async handle. */ |
598 | 0 | static void proc_async_gc_mark(MVMThreadContext *tc, void *data, MVMGCWorklist *worklist) { |
599 | 0 | MVMIOAsyncProcessData *apd = (MVMIOAsyncProcessData *)data; |
600 | 0 | if (data) |
601 | 0 | MVM_gc_worklist_add(tc, worklist, &(apd->async_task)); |
602 | 0 | } |
603 | | |
604 | | /* Does an asynchronous close (since it must run on the event loop). */ |
605 | 0 | static void close_cb(uv_handle_t *handle) { |
606 | 0 | MVM_free(handle); |
607 | 0 | } |
608 | 0 | static void close_perform(MVMThreadContext *tc, uv_loop_t *loop, MVMObject *async_task, void *data) { |
609 | 0 | uv_close((uv_handle_t *)data, close_cb); |
610 | 0 | } |
611 | | |
612 | | /* Operations table for async close task. */ |
613 | | static const MVMAsyncTaskOps close_op_table = { |
614 | | close_perform, |
615 | | NULL, |
616 | | NULL, |
617 | | NULL |
618 | | }; |
619 | | |
620 | | static void deferred_close_perform(MVMThreadContext *tc, uv_loop_t *loop, MVMObject *async_task, void *data); |
621 | | |
622 | | static const MVMAsyncTaskOps deferred_close_op_table = { |
623 | | deferred_close_perform, |
624 | | NULL, |
625 | | NULL, |
626 | | NULL |
627 | | }; |
628 | | |
629 | 0 | static MVMint64 close_stdin(MVMThreadContext *tc, MVMOSHandle *h) { |
630 | 0 | MVMIOAsyncProcessData *handle_data = (MVMIOAsyncProcessData *)h->body.data; |
631 | 0 | MVMAsyncTask *spawn_task = (MVMAsyncTask *)handle_data->async_task; |
632 | 0 | SpawnInfo *si = spawn_task ? (SpawnInfo *)spawn_task->body.data : NULL; |
633 | 0 | if (si && si->state == STATE_UNSTARTED) { |
634 | 0 | MVMAsyncTask *task; |
635 | 0 | MVMROOT(tc, h, { |
636 | 0 | task = (MVMAsyncTask *)MVM_repr_alloc_init(tc, |
637 | 0 | tc->instance->boot_types.BOOTAsync); |
638 | 0 | }); |
639 | 0 | task->body.ops = &deferred_close_op_table; |
640 | 0 | task->body.data = si; |
641 | 0 | MVM_io_eventloop_queue_work(tc, (MVMObject *)task); |
642 | 0 | return 0; |
643 | 0 | } |
644 | 0 | if (si && si->stdin_handle) { |
645 | 0 | MVMAsyncTask *task; |
646 | 0 | MVMROOT(tc, h, { |
647 | 0 | task = (MVMAsyncTask *)MVM_repr_alloc_init(tc, |
648 | 0 | tc->instance->boot_types.BOOTAsync); |
649 | 0 | }); |
650 | 0 | task->body.ops = &close_op_table; |
651 | 0 | task->body.data = si->stdin_handle; |
652 | 0 | MVM_io_eventloop_queue_work(tc, (MVMObject *)task); |
653 | 0 | si->stdin_handle = NULL; |
654 | 0 | } |
655 | 0 | return 0; |
656 | 0 | } |
657 | | |
658 | 0 | static void deferred_close_perform(MVMThreadContext *tc, uv_loop_t *loop, MVMObject *async_task, void *data) { |
659 | 0 | SpawnInfo *si = (SpawnInfo *) data; |
660 | 0 | MVMOSHandle *h = (MVMOSHandle *) si->handle; |
661 | 0 |
|
662 | 0 | if (si->state == STATE_UNSTARTED) { |
663 | 0 | MVMAsyncTask *task; |
664 | 0 | MVMROOT(tc, h, { |
665 | 0 | task = (MVMAsyncTask *)MVM_repr_alloc_init(tc, |
666 | 0 | tc->instance->boot_types.BOOTAsync); |
667 | 0 | }); |
668 | 0 | task->body.ops = &deferred_close_op_table; |
669 | 0 | task->body.data = si; |
670 | 0 | MVM_io_eventloop_queue_work(tc, (MVMObject *)task); |
671 | 0 | return; |
672 | 0 | } |
673 | 0 | if (si->stdin_handle) { |
674 | 0 | close_stdin(tc, h); |
675 | 0 | } |
676 | 0 | } |
677 | | |
678 | | /* IO ops table, for async process, populated with functions. */ |
679 | | static const MVMIOAsyncWritable proc_async_writable = { write_str, write_bytes }; |
680 | | static const MVMIOClosable closable = { close_stdin }; |
681 | | static const MVMIOOps proc_op_table = { |
682 | | &closable, |
683 | | NULL, |
684 | | NULL, |
685 | | NULL, |
686 | | NULL, |
687 | | &proc_async_writable, |
688 | | NULL, |
689 | | NULL, |
690 | | NULL, |
691 | | NULL, |
692 | | NULL, |
693 | | NULL, |
694 | | proc_async_gc_mark, |
695 | | NULL |
696 | | }; |
697 | | |
698 | 0 | static void spawn_async_close(uv_handle_t *handle) { |
699 | 0 | MVM_free(handle); |
700 | 0 | } |
701 | | |
702 | 0 | static void async_spawn_on_exit(uv_process_t *req, MVMint64 exit_status, int term_signal) { |
703 | 0 | /* Check we've got a callback to fire. */ |
704 | 0 | SpawnInfo *si = (SpawnInfo *)req->data; |
705 | 0 | MVMThreadContext *tc = si->tc; |
706 | 0 | MVMObject *done_cb = MVM_repr_at_key_o(tc, si->callbacks, |
707 | 0 | tc->instance->str_consts.done); |
708 | 0 | MVMOSHandle *os_handle; |
709 | 0 | if (!MVM_is_null(tc, done_cb)) { |
710 | 0 | MVMROOT(tc, done_cb, { |
711 | 0 | /* Get status. */ |
712 | 0 | MVMint64 status = (exit_status << 8) | term_signal; |
713 | 0 |
|
714 | 0 | /* Get what we'll need to build and convey the result. */ |
715 | 0 | MVMObject *arr = MVM_repr_alloc_init(tc, tc->instance->boot_types.BOOTArray); |
716 | 0 | MVMAsyncTask *t = MVM_io_eventloop_get_active_work(tc, si->work_idx); |
717 | 0 |
|
718 | 0 | /* Box and send along status. */ |
719 | 0 | MVM_repr_push_o(tc, arr, done_cb); |
720 | 0 | MVMROOT(tc, arr, { |
721 | 0 | MVMROOT(tc, t, { |
722 | 0 | MVMObject *result_box = MVM_repr_box_int(tc, |
723 | 0 | tc->instance->boot_types.BOOTInt, status); |
724 | 0 | MVM_repr_push_o(tc, arr, result_box); |
725 | 0 | }); |
726 | 0 | }); |
727 | 0 | MVM_repr_push_o(tc, t->body.queue, arr); |
728 | 0 | }); |
729 | 0 | } |
730 | 0 |
|
731 | 0 | /* when invoked via MVMIOOps, close_stdin is already wrapped in a mutex */ |
732 | 0 | os_handle = (MVMOSHandle *) si->handle; |
733 | 0 | uv_mutex_lock(os_handle->body.mutex); |
734 | 0 | si->state = STATE_DONE; |
735 | 0 | close_stdin(tc, os_handle); |
736 | 0 | uv_mutex_unlock(os_handle->body.mutex); |
737 | 0 |
|
738 | 0 | /* Close handle. */ |
739 | 0 | uv_close((uv_handle_t *)req, spawn_async_close); |
740 | 0 | ((MVMIOAsyncProcessData *)((MVMOSHandle *)si->handle)->body.data)->handle = NULL; |
741 | 0 | if (--si->using == 0) |
742 | 0 | MVM_io_eventloop_remove_active_work(tc, &(si->work_idx)); |
743 | 0 | } |
744 | | |
745 | | /* Allocates a buffer of the suggested size. */ |
746 | 0 | static void on_alloc(uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf) { |
747 | 0 | size_t size = suggested_size > 0 ? suggested_size : 4; |
748 | 0 | buf->base = MVM_malloc(size); |
749 | 0 | buf->len = size; |
750 | 0 | } |
751 | | |
752 | | /* Read functions for stdout/stderr. */ |
753 | | static void async_read(uv_stream_t *handle, ssize_t nread, const uv_buf_t *buf, SpawnInfo *si, |
754 | 0 | MVMObject *callback, MVMuint32 seq_number) { |
755 | 0 | MVMThreadContext *tc = si->tc; |
756 | 0 | MVMObject *arr; |
757 | 0 | MVMAsyncTask *t; |
758 | 0 | MVMROOT(tc, callback, { |
759 | 0 | arr = MVM_repr_alloc_init(tc, tc->instance->boot_types.BOOTArray); |
760 | 0 | t = MVM_io_eventloop_get_active_work(tc, si->work_idx); |
761 | 0 | }); |
762 | 0 | MVM_repr_push_o(tc, arr, callback); |
763 | 0 | if (nread >= 0) { |
764 | 0 | MVMROOT(tc, t, { |
765 | 0 | MVMROOT(tc, arr, { |
766 | 0 | /* Push the sequence number. */ |
767 | 0 | MVMObject *seq_boxed = MVM_repr_box_int(tc, |
768 | 0 | tc->instance->boot_types.BOOTInt, seq_number); |
769 | 0 | MVM_repr_push_o(tc, arr, seq_boxed); |
770 | 0 |
|
771 | 0 | /* Push buffer of data. */ |
772 | 0 | { |
773 | 0 | MVMObject *buf_type = MVM_repr_at_key_o(tc, si->callbacks, |
774 | 0 | tc->instance->str_consts.buf_type); |
775 | 0 | MVMArray *res_buf = (MVMArray *)MVM_repr_alloc_init(tc, buf_type); |
776 | 0 | res_buf->body.slots.i8 = (MVMint8 *)buf->base; |
777 | 0 | res_buf->body.start = 0; |
778 | 0 | res_buf->body.ssize = buf->len; |
779 | 0 | res_buf->body.elems = nread; |
780 | 0 | MVM_repr_push_o(tc, arr, (MVMObject *)res_buf); |
781 | 0 | } |
782 | 0 |
|
783 | 0 | /* Finally, no error. */ |
784 | 0 | MVM_repr_push_o(tc, arr, tc->instance->boot_types.BOOTStr); |
785 | 0 | }); |
786 | 0 | }); |
787 | 0 | } |
788 | 0 | else if (nread == UV_EOF) { |
789 | 0 | MVMROOT(tc, t, { |
790 | 0 | MVMROOT(tc, arr, { |
791 | 0 | MVMObject *final = MVM_repr_box_int(tc, |
792 | 0 | tc->instance->boot_types.BOOTInt, seq_number); |
793 | 0 | MVM_repr_push_o(tc, arr, final); |
794 | 0 | MVM_repr_push_o(tc, arr, tc->instance->boot_types.BOOTStr); |
795 | 0 | MVM_repr_push_o(tc, arr, tc->instance->boot_types.BOOTStr); |
796 | 0 | }); |
797 | 0 | }); |
798 | 0 | if (buf->base) |
799 | 0 | MVM_free(buf->base); |
800 | 0 | uv_close((uv_handle_t *) handle, NULL); |
801 | 0 | if (--si->using == 0) |
802 | 0 | MVM_io_eventloop_remove_active_work(tc, &(si->work_idx)); |
803 | 0 | } |
804 | 0 | else { |
805 | 0 | MVM_repr_push_o(tc, arr, tc->instance->boot_types.BOOTInt); |
806 | 0 | MVM_repr_push_o(tc, arr, tc->instance->boot_types.BOOTStr); |
807 | 0 | MVMROOT(tc, t, { |
808 | 0 | MVMROOT(tc, arr, { |
809 | 0 | MVMString *msg_str = MVM_string_ascii_decode_nt(tc, |
810 | 0 | tc->instance->VMString, uv_strerror(nread)); |
811 | 0 | MVMObject *msg_box = MVM_repr_box_str(tc, |
812 | 0 | tc->instance->boot_types.BOOTStr, msg_str); |
813 | 0 | MVM_repr_push_o(tc, arr, msg_box); |
814 | 0 | }); |
815 | 0 | }); |
816 | 0 | if (buf->base) |
817 | 0 | MVM_free(buf->base); |
818 | 0 | uv_close((uv_handle_t *) handle, NULL); |
819 | 0 | if (--si->using == 0) |
820 | 0 | MVM_io_eventloop_remove_active_work(tc, &(si->work_idx)); |
821 | 0 | } |
822 | 0 | MVM_repr_push_o(tc, t->body.queue, arr); |
823 | 0 | } |
824 | 0 | static void async_spawn_stdout_bytes_read(uv_stream_t *handle, ssize_t nread, const uv_buf_t *buf) { |
825 | 0 | SpawnInfo *si = (SpawnInfo *)handle->data; |
826 | 0 | MVMObject *cb = MVM_repr_at_key_o(si->tc, si->callbacks, |
827 | 0 | si->tc->instance->str_consts.stdout_bytes); |
828 | 0 | async_read(handle, nread, buf, si, cb, si->seq_stdout++); |
829 | 0 | } |
830 | 0 | static void async_spawn_stderr_bytes_read(uv_stream_t *handle, ssize_t nread, const uv_buf_t *buf) { |
831 | 0 | SpawnInfo *si = (SpawnInfo *)handle->data; |
832 | 0 | MVMObject *cb = MVM_repr_at_key_o(si->tc, si->callbacks, |
833 | 0 | si->tc->instance->str_consts.stderr_bytes); |
834 | 0 | async_read(handle, nread, buf, si, cb, si->seq_stderr++); |
835 | 0 | } |
836 | | |
837 | | /* Actually spawns an async task. This runs in the event loop thread. */ |
838 | 0 | static void spawn_setup(MVMThreadContext *tc, uv_loop_t *loop, MVMObject *async_task, void *data) { |
839 | 0 | MVMint64 spawn_result; |
840 | 0 |
|
841 | 0 | /* Process info setup. */ |
842 | 0 | uv_process_t *process = MVM_calloc(1, sizeof(uv_process_t)); |
843 | 0 | uv_process_options_t process_options = {0}; |
844 | 0 | uv_stdio_container_t process_stdio[3]; |
845 | 0 | uv_pipe_t *stdout_pipe = NULL; |
846 | 0 | uv_pipe_t *stderr_pipe = NULL; |
847 | 0 | uv_read_cb stdout_cb, stderr_cb; |
848 | 0 |
|
849 | 0 | /* Add to work in progress. */ |
850 | 0 | SpawnInfo *si = (SpawnInfo *)data; |
851 | 0 | si->tc = tc; |
852 | 0 | si->work_idx = MVM_io_eventloop_add_active_work(tc, async_task); |
853 | 0 | si->using = 1; |
854 | 0 |
|
855 | 0 | /* Create input/output handles as needed. */ |
856 | 0 | if (MVM_repr_exists_key(tc, si->callbacks, tc->instance->str_consts.write)) { |
857 | 0 | uv_pipe_t *pipe = MVM_malloc(sizeof(uv_pipe_t)); |
858 | 0 | uv_pipe_init(tc->loop, pipe, 0); |
859 | 0 | pipe->data = si; |
860 | 0 | process_stdio[0].flags = UV_CREATE_PIPE | UV_READABLE_PIPE; |
861 | 0 | process_stdio[0].data.stream = (uv_stream_t *)pipe; |
862 | 0 | si->stdin_handle = (uv_stream_t *)pipe; |
863 | 0 | } |
864 | 0 | else { |
865 | 0 | process_stdio[0].flags = UV_INHERIT_FD; |
866 | 0 | process_stdio[0].data.fd = 0; |
867 | 0 | } |
868 | 0 | if (MVM_repr_exists_key(tc, si->callbacks, tc->instance->str_consts.stdout_bytes)) { |
869 | 0 | uv_pipe_t *pipe = MVM_malloc(sizeof(uv_pipe_t)); |
870 | 0 | uv_pipe_init(tc->loop, pipe, 0); |
871 | 0 | pipe->data = si; |
872 | 0 | process_stdio[1].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE; |
873 | 0 | process_stdio[1].data.stream = (uv_stream_t *)pipe; |
874 | 0 | stdout_pipe = pipe; |
875 | 0 | stdout_cb = async_spawn_stdout_bytes_read; |
876 | 0 | si->using++; |
877 | 0 | } |
878 | 0 | else { |
879 | 0 | process_stdio[1].flags = UV_INHERIT_FD; |
880 | 0 | process_stdio[1].data.fd = 1; |
881 | 0 | } |
882 | 0 | if (MVM_repr_exists_key(tc, si->callbacks, tc->instance->str_consts.stderr_bytes)) { |
883 | 0 | uv_pipe_t *pipe = MVM_malloc(sizeof(uv_pipe_t)); |
884 | 0 | uv_pipe_init(tc->loop, pipe, 0); |
885 | 0 | pipe->data = si; |
886 | 0 | process_stdio[2].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE; |
887 | 0 | process_stdio[2].data.stream = (uv_stream_t *)pipe; |
888 | 0 | stderr_pipe = pipe; |
889 | 0 | stderr_cb = async_spawn_stderr_bytes_read; |
890 | 0 | si->using++; |
891 | 0 | } |
892 | 0 | else { |
893 | 0 | process_stdio[2].flags = UV_INHERIT_FD; |
894 | 0 | process_stdio[2].data.fd = 2; |
895 | 0 | } |
896 | 0 |
|
897 | 0 | /* Set up process start info. */ |
898 | 0 | process_options.stdio = process_stdio; |
899 | 0 | process_options.file = si->prog; |
900 | 0 | process_options.args = si->args; |
901 | 0 | process_options.cwd = si->cwd; |
902 | 0 | process_options.flags = UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS | UV_PROCESS_WINDOWS_HIDE; |
903 | 0 | process_options.env = si->env; |
904 | 0 | process_options.stdio_count = 3; |
905 | 0 | process_options.exit_cb = async_spawn_on_exit; |
906 | 0 |
|
907 | 0 | /* Attach data, spawn, report any error. */ |
908 | 0 | process->data = si; |
909 | 0 | spawn_result = uv_spawn(tc->loop, process, &process_options); |
910 | 0 | if (spawn_result) { |
911 | 0 | MVMObject *msg_box = NULL; |
912 | 0 | si->state = STATE_DONE; |
913 | 0 | MVMROOT(tc, async_task, { |
914 | 0 | MVMROOT(tc, msg_box, { |
915 | 0 | MVMString *msg_str = MVM_string_ascii_decode_nt(tc, |
916 | 0 | tc->instance->VMString, uv_strerror(spawn_result)); |
917 | 0 | MVMObject *msg_box = MVM_repr_box_str(tc, |
918 | 0 | tc->instance->boot_types.BOOTStr, msg_str); |
919 | 0 |
|
920 | 0 | MVMObject *error_cb = MVM_repr_at_key_o(tc, si->callbacks, |
921 | 0 | tc->instance->str_consts.error); |
922 | 0 | if (!MVM_is_null(tc, error_cb)) { |
923 | 0 | MVMROOT(tc, error_cb, { |
924 | 0 | MVMObject *arr = MVM_repr_alloc_init(tc, tc->instance->boot_types.BOOTArray); |
925 | 0 | MVM_repr_push_o(tc, arr, error_cb); |
926 | 0 | MVM_repr_push_o(tc, arr, msg_box); |
927 | 0 | MVM_repr_push_o(tc, ((MVMAsyncTask *)async_task)->body.queue, arr); |
928 | 0 | }); |
929 | 0 | } |
930 | 0 |
|
931 | 0 | if (stdout_pipe) { |
932 | 0 | MVMObject *arr = MVM_repr_alloc_init(tc, tc->instance->boot_types.BOOTArray); |
933 | 0 | MVMObject *cb = MVM_repr_at_key_o(tc, si->callbacks, |
934 | 0 | tc->instance->str_consts.stdout_bytes); |
935 | 0 | MVM_repr_push_o(tc, arr, cb); |
936 | 0 | MVM_repr_push_o(tc, arr, tc->instance->boot_types.BOOTInt); |
937 | 0 | MVM_repr_push_o(tc, arr, tc->instance->boot_types.BOOTStr); |
938 | 0 | MVM_repr_push_o(tc, arr, msg_box); |
939 | 0 | MVM_repr_push_o(tc, ((MVMAsyncTask *)async_task)->body.queue, arr); |
940 | 0 | } |
941 | 0 |
|
942 | 0 | if (stderr_pipe) { |
943 | 0 | MVMObject *arr = MVM_repr_alloc_init(tc, tc->instance->boot_types.BOOTArray); |
944 | 0 | MVMObject *cb = MVM_repr_at_key_o(tc, si->callbacks, |
945 | 0 | tc->instance->str_consts.stderr_bytes); |
946 | 0 | MVM_repr_push_o(tc, arr, cb); |
947 | 0 | MVM_repr_push_o(tc, arr, tc->instance->boot_types.BOOTInt); |
948 | 0 | MVM_repr_push_o(tc, arr, tc->instance->boot_types.BOOTStr); |
949 | 0 | MVM_repr_push_o(tc, arr, msg_box); |
950 | 0 | MVM_repr_push_o(tc, ((MVMAsyncTask *)async_task)->body.queue, arr); |
951 | 0 | } |
952 | 0 | }); |
953 | 0 | }); |
954 | 0 |
|
955 | 0 | MVM_io_eventloop_remove_active_work(tc, &(si->work_idx)); |
956 | 0 | } |
957 | 0 | else { |
958 | 0 | MVMOSHandle *handle = (MVMOSHandle *)si->handle; |
959 | 0 | MVMIOAsyncProcessData *apd = (MVMIOAsyncProcessData *)handle->body.data; |
960 | 0 | MVMObject *ready_cb; |
961 | 0 | apd->handle = process; |
962 | 0 |
|
963 | 0 | ready_cb = MVM_repr_at_key_o(tc, si->callbacks, |
964 | 0 | tc->instance->str_consts.ready); |
965 | 0 | si->state = STATE_STARTED; |
966 | 0 |
|
967 | 0 | if (!MVM_is_null(tc, ready_cb)) { |
968 | 0 | MVMROOT(tc, ready_cb, { |
969 | 0 | MVMROOT(tc, async_task, { |
970 | 0 | MVMObject *arr = MVM_repr_alloc_init(tc, tc->instance->boot_types.BOOTArray); |
971 | 0 | MVM_repr_push_o(tc, arr, ready_cb); |
972 | 0 | MVM_repr_push_o(tc, ((MVMAsyncTask *)async_task)->body.queue, arr); |
973 | 0 | }); |
974 | 0 | }); |
975 | 0 | } |
976 | 0 |
|
977 | 0 | /* Start any output readers. */ |
978 | 0 | if (stdout_pipe) |
979 | 0 | uv_read_start((uv_stream_t *)stdout_pipe, on_alloc, stdout_cb); |
980 | 0 | if (stderr_pipe) |
981 | 0 | uv_read_start((uv_stream_t *)stderr_pipe, on_alloc, stderr_cb); |
982 | 0 | } |
983 | 0 | } |
984 | | |
985 | | /* On cancel, kill the process. */ |
986 | 0 | static void spawn_cancel(MVMThreadContext *tc, uv_loop_t *loop, MVMObject *async_task, void *data) { |
987 | 0 | /* Locate handle. */ |
988 | 0 | SpawnInfo *si = (SpawnInfo *)data; |
989 | 0 | MVMOSHandle *handle = (MVMOSHandle *)si->handle; |
990 | 0 | MVMIOAsyncProcessData *apd = (MVMIOAsyncProcessData *)handle->body.data; |
991 | 0 | uv_process_t *phandle = apd->handle; |
992 | 0 |
|
993 | 0 | /* If it didn't already end, try to kill it. exit_cb will clean up phandle |
994 | 0 | * should the signal lead to process exit. */ |
995 | 0 | if (phandle) { |
996 | 0 | #ifdef _WIN32 |
997 | | /* On Windows, make sure we use a signal that will actually work. */ |
998 | | if (apd->signal != SIGTERM && apd->signal != SIGKILL && apd->signal != SIGINT) |
999 | | apd->signal = SIGKILL; |
1000 | | #endif |
1001 | 0 | uv_process_kill(phandle, (int)apd->signal); |
1002 | 0 | } |
1003 | 0 | } |
1004 | | |
1005 | | /* Marks objects for a spawn task. */ |
1006 | 0 | static void spawn_gc_mark(MVMThreadContext *tc, void *data, MVMGCWorklist *worklist) { |
1007 | 0 | SpawnInfo *si = (SpawnInfo *)data; |
1008 | 0 | MVM_gc_worklist_add(tc, worklist, &si->handle); |
1009 | 0 | MVM_gc_worklist_add(tc, worklist, &si->callbacks); |
1010 | 0 | } |
1011 | | |
1012 | | /* Frees info for a spawn task. */ |
1013 | 0 | static void spawn_gc_free(MVMThreadContext *tc, MVMObject *t, void *data) { |
1014 | 0 | if (data) { |
1015 | 0 | SpawnInfo *si = (SpawnInfo *)data; |
1016 | 0 | if (si->cwd) { |
1017 | 0 | MVM_free(si->cwd); |
1018 | 0 | si->cwd = NULL; |
1019 | 0 | } |
1020 | 0 | if (si->env) { |
1021 | 0 | MVMuint32 i; |
1022 | 0 | char **_env = si->env; |
1023 | 0 | FREE_ENV(); |
1024 | 0 | si->env = NULL; |
1025 | 0 | } |
1026 | 0 | if (si->args) { |
1027 | 0 | MVMuint32 i = 0; |
1028 | 0 | while (si->args[i]) |
1029 | 0 | MVM_free(si->args[i++]); |
1030 | 0 | MVM_free(si->args); |
1031 | 0 | si->args = NULL; |
1032 | 0 | } |
1033 | 0 | MVM_free(si); |
1034 | 0 | } |
1035 | 0 | } |
1036 | | |
1037 | | /* Operations table for async connect task. */ |
1038 | | static const MVMAsyncTaskOps spawn_op_table = { |
1039 | | spawn_setup, |
1040 | | spawn_cancel, |
1041 | | spawn_gc_mark, |
1042 | | spawn_gc_free |
1043 | | }; |
1044 | | |
1045 | | /* Spawn a process asynchronously. */ |
1046 | | MVMObject * MVM_proc_spawn_async(MVMThreadContext *tc, MVMObject *queue, MVMObject *argv, |
1047 | 0 | MVMString *cwd, MVMObject *env, MVMObject *callbacks) { |
1048 | 0 | MVMAsyncTask *task; |
1049 | 0 | MVMOSHandle *handle; |
1050 | 0 | SpawnInfo *si; |
1051 | 0 | char *prog, *_cwd, **_env, **args; |
1052 | 0 | MVMuint64 size, arg_size, i; |
1053 | 0 | MVMIter *iter; |
1054 | 0 | MVMRegister reg; |
1055 | 0 |
|
1056 | 0 | /* Validate queue REPR. */ |
1057 | 0 | if (REPR(queue)->ID != MVM_REPR_ID_ConcBlockingQueue) |
1058 | 0 | MVM_exception_throw_adhoc(tc, |
1059 | 0 | "spawnprocasync target queue must have ConcBlockingQueue REPR"); |
1060 | 0 |
|
1061 | 0 | /* Encode arguments, taking first as program name. */ |
1062 | 0 | arg_size = MVM_repr_elems(tc, argv); |
1063 | 0 | if (arg_size < 1) |
1064 | 0 | MVM_exception_throw_adhoc(tc, "spawnprocasync must have first arg for program"); |
1065 | 0 | args = MVM_malloc((arg_size + 1) * sizeof(char *)); |
1066 | 0 | for (i = 0; i < arg_size; i++) { |
1067 | 0 | REPR(argv)->pos_funcs.at_pos(tc, STABLE(argv), argv, OBJECT_BODY(argv), i, ®, MVM_reg_obj); |
1068 | 0 | args[i] = MVM_string_utf8_c8_encode_C_string(tc, MVM_repr_get_str(tc, reg.o)); |
1069 | 0 | } |
1070 | 0 | args[arg_size] = NULL; |
1071 | 0 | prog = args[0]; |
1072 | 0 |
|
1073 | 0 | /* Encode CWD. */ |
1074 | 0 | _cwd = MVM_string_utf8_c8_encode_C_string(tc, cwd); |
1075 | 0 |
|
1076 | 0 | MVMROOT(tc, queue, { |
1077 | 0 | MVMROOT(tc, env, { |
1078 | 0 | MVMROOT(tc, callbacks, { |
1079 | 0 | MVMIOAsyncProcessData *data; |
1080 | 0 |
|
1081 | 0 | /* Encode environment. */ |
1082 | 0 | size = MVM_repr_elems(tc, env); |
1083 | 0 | iter = (MVMIter *)MVM_iter(tc, env); |
1084 | 0 | _env = MVM_malloc((size + 1) * sizeof(char *)); |
1085 | 0 | INIT_ENV(); |
1086 | 0 |
|
1087 | 0 | /* Create handle. */ |
1088 | 0 | data = MVM_calloc(1, sizeof(MVMIOAsyncProcessData)); |
1089 | 0 | handle = (MVMOSHandle *)MVM_repr_alloc_init(tc, tc->instance->boot_types.BOOTIO); |
1090 | 0 | handle->body.ops = &proc_op_table; |
1091 | 0 | handle->body.data = data; |
1092 | 0 |
|
1093 | 0 | /* Create async task handle. */ |
1094 | 0 | MVMROOT(tc, handle, { |
1095 | 0 | task = (MVMAsyncTask *)MVM_repr_alloc_init(tc, tc->instance->boot_types.BOOTAsync); |
1096 | 0 | }); |
1097 | 0 | MVM_ASSIGN_REF(tc, &(task->common.header), task->body.queue, queue); |
1098 | 0 | task->body.ops = &spawn_op_table; |
1099 | 0 | si = MVM_calloc(1, sizeof(SpawnInfo)); |
1100 | 0 | si->prog = prog; |
1101 | 0 | si->cwd = _cwd; |
1102 | 0 | si->env = _env; |
1103 | 0 | si->args = args; |
1104 | 0 | si->state = STATE_UNSTARTED; |
1105 | 0 | MVM_ASSIGN_REF(tc, &(task->common.header), si->handle, handle); |
1106 | 0 | MVM_ASSIGN_REF(tc, &(task->common.header), si->callbacks, callbacks); |
1107 | 0 | task->body.data = si; |
1108 | 0 | MVM_ASSIGN_REF(tc, &(handle->common.header), data->async_task, task); |
1109 | 0 | }); |
1110 | 0 | }); |
1111 | 0 | }); |
1112 | 0 |
|
1113 | 0 | /* Hand the task off to the event loop. */ |
1114 | 0 | MVMROOT(tc, handle, { |
1115 | 0 | MVM_io_eventloop_queue_work(tc, (MVMObject *)task); |
1116 | 0 | }); |
1117 | 0 |
|
1118 | 0 | return (MVMObject *)handle; |
1119 | 0 | } |
1120 | | |
1121 | | /* Kills an asynchronously spawned process. */ |
1122 | 0 | void MVM_proc_kill_async(MVMThreadContext *tc, MVMObject *handle_obj, MVMint64 signal) { |
1123 | 0 | /* Ensure it's a handle for a process. */ |
1124 | 0 | if (REPR(handle_obj)->ID == MVM_REPR_ID_MVMOSHandle) { |
1125 | 0 | MVMOSHandle *handle = (MVMOSHandle *)handle_obj; |
1126 | 0 | if (handle->body.ops == &proc_op_table) { |
1127 | 0 | /* It's fine; send the kill by cancelling the task. */ |
1128 | 0 | MVMIOAsyncProcessData *data = (MVMIOAsyncProcessData *)handle->body.data; |
1129 | 0 | data->signal = signal; |
1130 | 0 | MVM_io_eventloop_cancel_work(tc, data->async_task, NULL, NULL); |
1131 | 0 | return; |
1132 | 0 | } |
1133 | 0 | } |
1134 | 0 | MVM_exception_throw_adhoc(tc, "killprocasync requires a process handle"); |
1135 | 0 | } |
1136 | | |
1137 | | /* Get the current process ID. */ |
1138 | 130 | MVMint64 MVM_proc_getpid(MVMThreadContext *tc) { |
1139 | 130 | #ifdef _WIN32 |
1140 | | return _getpid(); |
1141 | | #else |
1142 | 130 | return getpid(); |
1143 | 130 | #endif |
1144 | 130 | } |
1145 | | |
1146 | | /* generates a random int64 */ |
1147 | 0 | MVMint64 MVM_proc_rand_i(MVMThreadContext *tc) { |
1148 | 0 | MVMuint64 result = tinymt64_generate_uint64(tc->rand_state); |
1149 | 0 | return *(MVMint64 *)&result; |
1150 | 0 | } |
1151 | | |
1152 | | /* generates a number between 0 and 1 */ |
1153 | 5 | MVMnum64 MVM_proc_rand_n(MVMThreadContext *tc) { |
1154 | 5 | return tinymt64_generate_double(tc->rand_state); |
1155 | 5 | } |
1156 | | |
1157 | 0 | MVMnum64 MVM_proc_randscale_n(MVMThreadContext *tc, MVMnum64 scale) { |
1158 | 0 | return tinymt64_generate_double(tc->rand_state) * scale; |
1159 | 0 | } |
1160 | | |
1161 | | /* seed random number generator */ |
1162 | 132 | void MVM_proc_seed(MVMThreadContext *tc, MVMint64 seed) { |
1163 | 132 | /* Seed our one, plus the normal C srand for libtommath. */ |
1164 | 132 | tinymt64_init(tc->rand_state, (MVMuint64)seed); |
1165 | 132 | /* do not call srand if we are not using rand */ |
1166 | 132 | #ifndef MP_USE_ALT_RAND |
1167 | 132 | srand((MVMuint32)seed); |
1168 | 132 | #endif |
1169 | 132 | } |
1170 | | |
1171 | | /* gets the system time since the epoch truncated to integral seconds */ |
1172 | 2 | MVMint64 MVM_proc_time_i(MVMThreadContext *tc) { |
1173 | 2 | return (MVMint64)(MVM_platform_now() / 1000000000); |
1174 | 2 | } |
1175 | | |
1176 | | /* gets the system time since the epoch as floating point seconds */ |
1177 | 14.4k | MVMnum64 MVM_proc_time_n(MVMThreadContext *tc) { |
1178 | 14.4k | return (MVMnum64)MVM_platform_now() / 1000000000.0; |
1179 | 14.4k | } |
1180 | | |
1181 | 0 | MVMString * MVM_executable_name(MVMThreadContext *tc) { |
1182 | 0 | MVMInstance * const instance = tc->instance; |
1183 | 0 | if (instance->exec_name) |
1184 | 0 | return MVM_string_utf8_c8_decode(tc, |
1185 | 0 | instance->VMString, |
1186 | 0 | instance->exec_name, strlen(instance->exec_name)); |
1187 | 0 | else |
1188 | 0 | return tc->instance->str_consts.empty; |
1189 | 0 | } |
1190 | | |
1191 | 130 | MVMObject * MVM_proc_clargs(MVMThreadContext *tc) { |
1192 | 130 | MVMInstance * const instance = tc->instance; |
1193 | 130 | MVMObject *clargs = instance->clargs; |
1194 | 130 | if (!clargs) { |
1195 | 130 | clargs = MVM_repr_alloc_init(tc, MVM_hll_current(tc)->slurpy_array_type); |
1196 | 130 | #ifndef _WIN32 |
1197 | 130 | MVMROOT(tc, clargs, { |
1198 | 130 | const MVMint64 num_clargs = instance->num_clargs; |
1199 | 130 | MVMint64 count; |
1200 | 130 | |
1201 | 130 | MVMString *prog_string = MVM_string_utf8_c8_decode(tc, |
1202 | 130 | instance->VMString, |
1203 | 130 | instance->prog_name, strlen(instance->prog_name)); |
1204 | 130 | MVMObject *boxed_str = MVM_repr_box_str(tc, |
1205 | 130 | instance->boot_types.BOOTStr, prog_string); |
1206 | 130 | MVM_repr_push_o(tc, clargs, boxed_str); |
1207 | 130 | |
1208 | 130 | for (count = 0; count < num_clargs; count++) { |
1209 | 130 | char *raw_clarg = instance->raw_clargs[count]; |
1210 | 130 | MVMString *string = MVM_string_utf8_c8_decode(tc, |
1211 | 130 | instance->VMString, raw_clarg, strlen(raw_clarg)); |
1212 | 130 | boxed_str = MVM_repr_box_str(tc, |
1213 | 130 | instance->boot_types.BOOTStr, string); |
1214 | 130 | MVM_repr_push_o(tc, clargs, boxed_str); |
1215 | 130 | } |
1216 | 130 | }); |
1217 | 130 | #else |
1218 | | MVMROOT(tc, clargs, { |
1219 | | const MVMint64 num_clargs = instance->num_clargs; |
1220 | | MVMint64 count; |
1221 | | |
1222 | | MVMString *prog_string = MVM_string_utf8_c8_decode(tc, |
1223 | | instance->VMString, |
1224 | | instance->prog_name, strlen(instance->prog_name)); |
1225 | | MVMObject *boxed_str = MVM_repr_box_str(tc, |
1226 | | instance->boot_types.BOOTStr, prog_string); |
1227 | | MVM_repr_push_o(tc, clargs, boxed_str); |
1228 | | |
1229 | | for (count = 0; count < num_clargs; count++) { |
1230 | | char *raw_clarg = instance->raw_clargs[count]; |
1231 | | MVMString *string = MVM_string_utf8_c8_decode(tc, |
1232 | | instance->VMString, raw_clarg, strlen(raw_clarg)); |
1233 | | boxed_str = MVM_repr_box_str(tc, |
1234 | | instance->boot_types.BOOTStr, string); |
1235 | | MVM_repr_push_o(tc, clargs, boxed_str); |
1236 | | } |
1237 | | }); |
1238 | | #endif |
1239 | 130 | |
1240 | 130 | instance->clargs = clargs; |
1241 | 130 | } |
1242 | 130 | return clargs; |
1243 | 130 | } |