Coverage Report

Created: 2017-04-15 07:07

/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, &reg, 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, &reg, 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
}