9 ex_spawn_8c example
gullradriel edited this page 2026-10-29 20:54:40 +01:00

ex_spawn.c

start a program detached or waited for, with arguments no shell touches Starts a small shell script with n_spawn_detached(), passing it a file name full of the characters a shell would take apart - spaces, quotes, a semicolon, a dollar - and checks that the script received it whole: the script writes its first argument into a file of that very name, which exists afterwards only if the argument arrived as one piece. Also checks that a program that does not exist is not fatal, and that a NULL program is refused. Then the same through n_spawn_wait(): the exit code comes back and the file is there on return, a missing program is 127. Then n_spawn_wait_cancel(): a raised flag ends a long program within seconds. Last, n_spawn_pipe_open(): a megabyte written to a program's input arrives whole, what it says lands in the log file, a program that stops reading fails the write instead of ending this one with SIGPIPE, and a stop ends a program that would run for half a minute. Then n_spawn_pipe_open_read(): a megabyte read back whole then the end, the program's error in the log and not in its output, its input empty, a stop and a close ending a program that writes for ever, and each form refusing the other's call. ```c

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #include <unistd.h>

#include "nilorea/n_common.h" #include "nilorea/n_log.h"

int main(void) { char dir[256]; char target[512]; int failures = 0; int i = 0;

set_log_level(LOG_NOTICE);

#if defined(windows) n_log(LOG_NOTICE, "ex_spawn: the detached start is checked on POSIX; nothing to do here"); return 0; #else snprintf(dir, sizeof(dir), "%s/ex_spawn_%d", getenv("TMPDIR") ? getenv("TMPDIR") : "/tmp", (int)getpid()); snprintf(target, sizeof(target), "%s/a file; with "quotes" and $HOME", dir); { /* The script writes "ok" into the file named by its first argument. / const char args[] = {"-c", "echo ok > "$1"", "ex_spawn", target, NULL}; char mk[300];

    snprintf(mk, sizeof(mk), "mkdir -p '%s'", dir);
    if (system(mk) != 0) {
        n_log(LOG_ERR, "ex_spawn: cannot make %s", dir);
        return 1;
    }
    if (n_spawn_detached("sh", args) != TRUE) {
        n_log(LOG_ERR, "ex_spawn: sh could not be started");
        failures++;
    }
}
/* Detached: nothing to wait on, so the file is looked for. */
for (i = 0; i < 100 && !file_exist(target); i++)
    usleep(20000);
if (file_exist(target)) {
    n_log(LOG_NOTICE, "ex_spawn: the argument arrived whole");
} else {
    n_log(LOG_ERR, "ex_spawn: no file named %s appeared", target);
    failures++;
}
unlink(target);
rmdir(dir);

{
    const char* none[] = {NULL};

    /* Not found: the child reports it and exits 127; the call itself
     * started it, so it is not an error here, and nothing is left. */
    n_spawn_detached("ex-spawn-no-such-program", none);
}
if (n_spawn_detached(NULL, NULL) != FALSE) {
    n_log(LOG_ERR, "ex_spawn: a NULL program was accepted");
    failures++;
}

/* The waiting form: the exit code comes back, and the file the program
 * wrote is there the moment the call returns, with no polling. */
{
    const char* write_it[] = {"-c", "echo ok > \"$1\"; exit 3", "ex_spawn", target, NULL};
    const char* none[] = {NULL};
    char mk[300];
    int code = 0;

    snprintf(mk, sizeof(mk), "mkdir -p '%s'", dir);
    if (system(mk) != 0) {
        n_log(LOG_ERR, "ex_spawn: cannot make %s", dir);
        return 1;
    }
    if (n_spawn_wait("sh", write_it, &code) != FALSE || code != 3) {
        n_log(LOG_ERR, "ex_spawn: an exit code of 3 came back as %d", code);
        failures++;
    }
    if (!file_exist(target)) {
        n_log(LOG_ERR, "ex_spawn: the waited for program's file is not there on return");
        failures++;
    }
    unlink(target);
    rmdir(dir);
    if (n_spawn_wait("true", none, &code) != TRUE || code != 0) {
        n_log(LOG_ERR, "ex_spawn: true did not succeed (%d)", code);
        failures++;
    }
    if (n_spawn_wait("ex-spawn-no-such-program", none, &code) != FALSE || code != 127) {
        n_log(LOG_ERR, "ex_spawn: a missing program came back as %d, not 127", code);
        failures++;
    }
    if (n_spawn_wait(NULL, NULL, &code) != FALSE) {
        n_log(LOG_ERR, "ex_spawn: a NULL program was accepted by n_spawn_wait");
        failures++;
    }
}

/* The stoppable form. A flag left down waits as n_spawn_wait() does; a
 * flag raised ends a program that would otherwise run for half a
 * minute, and the call returns within the two seconds of grace. */
{
    const char* none[] = {NULL};
    const char* long_one[] = {"30", NULL};
    volatile int stop = 0;
    int code = 0;
    time_t started = 0;

    if (n_spawn_wait_cancel("true", none, &code, &stop) != TRUE || code != 0) {
        n_log(LOG_ERR, "ex_spawn: true with a lowered flag did not succeed (%d)", code);
        failures++;
    }
    stop = 1;
    started = time(NULL);
    if (n_spawn_wait_cancel("sleep", long_one, &code, &stop) != FALSE) {
        n_log(LOG_ERR, "ex_spawn: a stopped program reported success");
        failures++;
    }
    if (time(NULL) - started > 5) {
        n_log(LOG_ERR, "ex_spawn: the stop took %ld seconds", (long)(time(NULL) - started));
        failures++;
    }
}
/* The piped form. */
{
    const char* count_it[] = {"-c", "wc -c > \"$1\"; echo said >&2", "ex_spawn", target, NULL};
    const char* deaf[] = {"-c", "exit 0", NULL};
    const char* long_one[] = {"30", NULL};
    char log_path[600];
    char mk[300];
    static char block[1 << 20];
    N_SPAWN_PIPE* sp = NULL;
    FILE* in = NULL;
    long counted = 0;
    int code = 0;
    time_t started = 0;

    snprintf(mk, sizeof(mk), "mkdir -p '%s'", dir);
    if (system(mk) != 0) {
        n_log(LOG_ERR, "ex_spawn: cannot make %s", dir);
        return 1;
    }
    snprintf(log_path, sizeof(log_path), "%s/log", dir);
    memset(block, 'x', sizeof(block));
    sp = n_spawn_pipe_open("sh", count_it, log_path);
    if (!sp || n_spawn_pipe_write(sp, block, sizeof(block)) != TRUE ||
        n_spawn_pipe_close(&sp, 0, &code) != TRUE || code != 0 || sp) {
        n_log(LOG_ERR, "ex_spawn: the piped program failed (%d)", code);
        failures++;
    }
    in = fopen(target, "r");
    if (!in || fscanf(in, "%ld", &counted) != 1 || counted != (long)sizeof(block)) {
        n_log(LOG_ERR, "ex_spawn: %ld bytes arrived of %zu", counted, sizeof(block));
        failures++;
    }
    if (in)
        fclose(in);
    in = fopen(log_path, "r");
    mk[0] = '\0';
    if (!in || !fgets(mk, sizeof(mk), in) || strncmp(mk, "said", 4) != 0) {
        n_log(LOG_ERR, "ex_spawn: the program's words did not reach the log");
        failures++;
    }
    if (in)
        fclose(in);
    unlink(target);
    unlink(log_path);
    rmdir(dir);

    /* Gone before reading: the write fails, and this process lives on
     * to say so. A megabyte is more than a pipe holds, so the write
     * cannot succeed into the buffer alone. */
    sp = n_spawn_pipe_open("sh", deaf, NULL);
    if (!sp) {
        n_log(LOG_ERR, "ex_spawn: sh could not be piped to");
        failures++;
    } else {
        usleep(200000);
        if (n_spawn_pipe_write(sp, block, sizeof(block)) != FALSE) {
            n_log(LOG_ERR, "ex_spawn: a write to a program that has gone succeeded");
            failures++;
        }
        n_spawn_pipe_close(&sp, 0, &code);
    }

    sp = n_spawn_pipe_open("sleep", long_one, NULL);
    started = time(NULL);
    if (!sp || n_spawn_pipe_close(&sp, 1, &code) != FALSE || time(NULL) - started > 5) {
        n_log(LOG_ERR, "ex_spawn: a stopped piped program was not stopped");
        failures++;
    }
    if (n_spawn_pipe_open(NULL, NULL, NULL) || n_spawn_pipe_close(NULL, 0, NULL) != FALSE) {
        n_log(LOG_ERR, "ex_spawn: NULL was accepted by the piped form");
        failures++;
    }

    /* The reading form: a megabyte comes back whole and then the end,
     * what the program says on its error goes to the log and not into
     * the data, its input is empty, and a stop ends one that would
     * write for ever. */
    {
        const char* make_mb[] = {"-c", "echo said >&2; head -c 1048576 /dev/zero | tr '\\000' x", NULL};
        const char* reads_input[] = {"-c", "cat; printf done", NULL};
        const char* forever[] = {"y", NULL};
        static char got[(1 << 20) + 16];
        size_t n = 0, k = 0;

        snprintf(mk, sizeof(mk), "mkdir -p '%s'", dir);
        if (system(mk) != 0) {
            n_log(LOG_ERR, "ex_spawn: cannot make %s", dir);
            return 1;
        }
        sp = n_spawn_pipe_open_read("sh", make_mb, log_path);
        n = sp ? n_spawn_pipe_read(sp, got, sizeof(got)) : 0;
        for (k = 0; k < n && got[k] == 'x'; k++);
        if (!sp || n != (size_t)(1 << 20) || k != n || n_spawn_pipe_read(sp, got, 16) != 0 ||
            n_spawn_pipe_close(&sp, 0, &code) != TRUE || code != 0) {
            n_log(LOG_ERR, "ex_spawn: the read pipe gave %zu bytes, %zu of them right (code %d)", n, k, code);
            failures++;
        }
        in = fopen(log_path, "r");
        mk[0] = '\0';
        if (!in || !fgets(mk, sizeof(mk), in) || strncmp(mk, "said", 4) != 0) {
            n_log(LOG_ERR, "ex_spawn: the read program's error did not reach the log");
            failures++;
        }
        if (in)
            fclose(in);
        unlink(log_path);
        rmdir(dir);

        sp = n_spawn_pipe_open_read("sh", reads_input, NULL);
        n = sp ? n_spawn_pipe_read(sp, got, sizeof(got)) : 0;
        if (!sp || n != 4 || memcmp(got, "done", 4) != 0) {
            n_log(LOG_ERR, "ex_spawn: a read program waited on its input");
            failures++;
        }
        n_spawn_pipe_close(&sp, 0, &code);

        sp = n_spawn_pipe_open_read("yes", forever, NULL);
        started = time(NULL);
        if (!sp || n_spawn_pipe_read(sp, got, 1000) != 1000 || n_spawn_pipe_close(&sp, 1, &code) != FALSE ||
            time(NULL) - started > 5) {
            n_log(LOG_ERR, "ex_spawn: a stopped read program was not stopped");
            failures++;
        }

        sp = n_spawn_pipe_open_read("yes", forever, NULL);
        if (!sp || n_spawn_pipe_write(sp, "x", 1) != FALSE) {
            n_log(LOG_ERR, "ex_spawn: a read pipe took a write");
            failures++;
        }
        /* Closed without a stop while it writes: the broken pipe ends
         * it, and the close does not wait for ever. */
        started = time(NULL);
        n_spawn_pipe_close(&sp, 0, &code);
        if (time(NULL) - started > 5) {
            n_log(LOG_ERR, "ex_spawn: closing a read pipe waited on its writer");
            failures++;
        }
        sp = n_spawn_pipe_open("sleep", long_one, NULL);
        if (!sp || n_spawn_pipe_read(sp, got, 1) != SIZE_MAX) {
            n_log(LOG_ERR, "ex_spawn: a write pipe was read");
            failures++;
        }
        n_spawn_pipe_close(&sp, 1, &code);
    }
}
n_log(LOG_NOTICE, "ex_spawn: %s", failures ? "FAILED" : "ok");
return failures ? 1 : 0;

#endif }


_Filename: ex_spawn.c_