~bakpakin/janet

46950a8cb35ca98626ec4e96a16b09afba420006 — Calvin Rose 5 years ago 4867cab posix_spawn
Convert os/execute to use posix_spawn.
3 files changed, 143 insertions(+), 81 deletions(-)

M src/core/io.c
M src/core/os.c
M src/core/util.h
M src/core/io.c => src/core/io.c +3 -0
@@ 26,6 26,9 @@

#include <stdio.h>
#include <errno.h>
#ifndef JANET_WINDOWS
#include <sys/wait.h>
#endif

#ifndef JANET_AMALG
#include <janet.h>

M src/core/os.c => src/core/os.c +140 -78
@@ 35,6 35,7 @@
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <spawn.h>

#ifdef JANET_WINDOWS
#include <windows.h>


@@ 88,7 89,7 @@ static Janet os_exit(int32_t argc, Janet *argv) {
}

#ifdef JANET_REDUCED_OS
/* Provide a dud os/getenv so init.janet works, but nothing else */
/* Provide a dud os/getenv so boot.janet and init.janet work, but nothing else */

static Janet os_getenv(int32_t argc, Janet *argv) {
    (void) argv;


@@ 99,97 100,152 @@ static Janet os_getenv(int32_t argc, Janet *argv) {
#else
/* Provide full os functionality */

#ifdef JANET_WINDOWS
static Janet os_execute(int32_t argc, Janet *argv) {
    janet_arity(argc, 1, -1);
    JanetBuffer *buffer = janet_buffer(10);
    for (int32_t i = 0; i < argc; i++) {
        const uint8_t *argstring = janet_getstring(argv, i);
        janet_buffer_push_bytes(buffer, argstring, janet_string_length(argstring));
        if (i != argc - 1) {
            janet_buffer_push_u8(buffer, ' ');
#define JANET_OS_EFLAG_E 0x1
#define JANET_OS_EFLAG_P 0x2

/* Get flags */
/* Unfortunately, execvpe is linux (glibc) only. Instead, we can switch
 * between the more portable execve, execvp, or execv.
 * Use the :e or :p flag for execve and execvp respectively. Eventually
 * :ep or :pe could be execvpe. */
static int os_execute_flags(int32_t argc, const Janet *argv) {
    if (argc < 2) return 0;
    int flags = 0;
    if (argc > 1) {
        const uint8_t *f = janet_getkeyword(argv, 1);
        int32_t len = janet_string_length(f);
        for (int32_t i = 0; i < len; i++) {
            if (f[i] == 'e') flags |= JANET_OS_EFLAG_E;
            if (f[i] == 'p') flags |= JANET_OS_EFLAG_P;
        }
    }
    janet_buffer_push_u8(buffer, 0);
    return flags;
}

    /* Convert to wide chars */
    wchar_t *sys_str = malloc(buffer->count * sizeof(wchar_t));
    if (NULL == sys_str) {
        JANET_OUT_OF_MEMORY;
    }
    int nwritten = MultiByteToWideChar(
                       CP_UTF8,
                       MB_PRECOMPOSED,
                       buffer->data,
                       buffer->count,
                       sys_str,
                       buffer->count);
    if (nwritten == 0) {
        free(sys_str);
        janet_panic("could not create process");
/* Get env for os_execute (execv family of functions, as well as CreateProcess) */
static char **os_execute_env(int32_t argc, const Janet *argv) {
    char **envp = NULL;
    if (argc > 2) {
        JanetDictView dict = janet_getdictionary(argv, 2);
        envp = malloc(sizeof(char *) * (dict.len + 1));
        if (NULL == envp) {
            JANET_OUT_OF_MEMORY;
        }
        int32_t j = 0;
        for (int32_t i = 0; i < dict.cap; i++) {
            const JanetKV *kv = dict.kvs + i;
            if (!janet_checktype(kv->key, JANET_STRING)) continue;
            if (!janet_checktype(kv->value, JANET_STRING)) continue;
            const uint8_t *keys = janet_unwrap_string(kv->key);
            const uint8_t *vals = janet_unwrap_string(kv->value);
            int32_t klen = janet_string_length(keys);
            int32_t vlen = janet_string_length(vals);
            /* Check keys has no embedded 0s or =s. */
            int skip = 0;
            for (int32_t k = 0; k < klen; k++) {
                if (keys[k] == '\0' || keys[k] == '=') {
                    skip = 1;
                    break;
                }
            }
            if (skip) continue;
            char *envitem = malloc(klen + vlen + 2);
            if (NULL == envitem) {
                JANET_OUT_OF_MEMORY;
            }
            memcpy(envitem, keys, klen);
            envitem[klen] = '=';
            memcpy(envitem + klen + 1, vals, vlen);
            envitem[klen + vlen + 1] = 0;
            envp[j++] = envitem;
        }
        envp[j] = NULL;
    }
    return envp;
}

    STARTUPINFO si;
    PROCESS_INFORMATION pi;

    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);
    ZeroMemory(&pi, sizeof(pi));

    // Start the child process.
    if (!CreateProcess(NULL,
                       (LPSTR) sys_str,
                       NULL,
                       NULL,
                       FALSE,
                       0,
                       NULL,
                       NULL,
                       &si,
                       &pi)) {
        free(sys_str);
        janet_panic("could not create process");
/* Free memory from os_execute */
static void os_execute_cleanup(char **envp, const char **child_argv) {
    free(child_argv);
    if (NULL != envp) {
        char **envitem = envp;
        while (*envitem != NULL) {
            free(*envitem);
            envitem++;
        }
    }
    free(sys_str);

    // Wait until child process exits.
    WaitForSingleObject(pi.hProcess, INFINITE);

    // Close process and thread handles.
    WORD status;
    GetExitCodeProcess(pi.hProcess, (LPDWORD)&status);
    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);
    return janet_wrap_integer(status);
    free(envp);
}
#else

static Janet os_execute(int32_t argc, Janet *argv) {
    janet_arity(argc, 1, -1);
    const char **child_argv = malloc(sizeof(char *) * (argc + 1));
    janet_arity(argc, 1, 3);

    /* Get arguments */
    JanetView exargs = janet_getindexed(argv, 0);
    const char **child_argv = malloc(sizeof(char *) * (exargs.len + 1));
    int status = 0;
    if (NULL == child_argv) {
        JANET_OUT_OF_MEMORY;
    }
    for (int32_t i = 0; i < argc; i++) {
        child_argv[i] = janet_getcstring(argv, i);
    for (int32_t i = 0; i < exargs.len; i++) {
        child_argv[i] = janet_getcstring(exargs.items, i);
    }
    child_argv[argc] = NULL;

    /* Fork child process */
    pid_t pid = fork();
    if (pid < 0) {
        janet_panic("failed to execute");
    } else if (pid == 0) {
        if (-1 == execve(child_argv[0], (char **)child_argv, NULL)) {
            exit(1);
        }
    child_argv[exargs.len] = NULL;

    /* Get flags */
    int flags = os_execute_flags(argc, argv);

    /* Get environment */
    char **envp = os_execute_env(argc, argv);

    /* Coerce to form that works for spawn. I'm fairly confident no implementation
     * of posix_spawn would modify the argv array passed in. */
    char *const *cargv = (char *const *)child_argv;

#ifdef JANET_WINDOWS

    /* Use _spawn family of functions. */
    /* Windows docs say do this before any spawns. */
    _flushall();

    if (flags & (JANET_OS_EFLAG_P | JANET_OS_EFLAG_E)) {
        status = _spawnvpe(_P_WAIT, child_argv[0], cargv, envp);
    } else if (flags & JANET_OS_EFLAG_P) {
        status = _spawnvp(_P_WAIT, child_argv[0], cargv);
    } else if (flags & JANET_OS_EFLAG_E) {
        status = _spawnve(_P_WAIT, child_argv[0], cargv, envp);
    } else {
        waitpid(pid, &status, 0);
        status = _spawnv(_P_WAIT, child_argv[0], cargv);
    }
    free(child_argv);

    os_execute_cleanup(envp, child_argv);
    return janet_wrap_integer(status);
}
#else

    /* Use posix_spawn to spawn new process */
    pid_t pid;
    if (flags & JANET_OS_EFLAG_P) {
        status = posix_spawnp(&pid,
                              child_argv[0], NULL, NULL, cargv,
                              (flags & JANET_OS_EFLAG_E) ? envp : NULL);
    } else {
        status = posix_spawn(&pid,
                             child_argv[0], NULL, NULL, cargv,
                             (flags & JANET_OS_EFLAG_E) ? envp : NULL);
    }

    /* Wait for child */
    if (status) {
        os_execute_cleanup(envp, child_argv);
        janet_panic(strerror(status));
    } else {
        waitpid(pid, &status, 0);
    }

    os_execute_cleanup(envp, child_argv);
    return janet_wrap_integer(WEXITSTATUS(status));
#endif
}

static Janet os_shell(int32_t argc, Janet *argv) {
    janet_arity(argc, 0, 1);


@@ 701,9 757,15 @@ static const JanetReg os_cfuns[] = {
    },
    {
        "os/execute", os_execute,
        JDOC("(os/execute program & args)\n\n"
             "Execute a program on the system and pass it string arguments. Returns "
             "the exit status of the program.")
        JDOC("(os/execute args &opts flags env)\n\n"
             "Execute a program on the system and pass it string arguments. Flags "
             "is a keyword that modifies how the program will execute.\n\n"
             "\t:e - enables passing an environment to the program. Without :e, the "
             "current environment is inherited.\n"
             "\t:p - allows searching the current PATH for the binary to execute. "
             "Without this flag, binaries must use absolute paths.\n\n"
             "env is a table or struct mapping environment variables to values. "
             "Returns the exit status of the program.")
    },
    {
        "os/shell", os_shell,

M src/core/util.h => src/core/util.h +0 -3
@@ 25,9 25,6 @@

#include <stdio.h>
#include <errno.h>
#ifndef JANET_WINDOWS
#include <sys/wait.h>
#endif

#ifndef JANET_AMALG
#include <janet.h>