Table of Contents
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_
Modules
- AABB3D
- ACCEPT_POOL
- ALLEGRO4
- ALLEGRO5
- ANIM_LIB
- ASTAR
- ATLAS_LIB
- AVRO
- CLOCK_SYNC
- COMMONS
- CONFIGFILE
- CYPHER_BASE64
- CYPHER_VIGENERE
- DEAD_RECKONING
- ENUMS
- EXCEPTIONS
- GAME_ENV
- GIT
- GUI
- HASH_TABLE
- ISOMETRIC_ENGINE
- LIST
- LOG
- LOGNODUP
- LZ4
- NETWORKING
- NETWORK_MSG
- N_COOKIES
- N_DIFF
- N_DIGEST
- N_DISK_CACHE
- N_DNS
- N_ECC
- N_ENTROPY
- N_FILES
- N_FLUIDS
- N_GUI_COLOR
- N_GUI_COLORWHEEL
- N_GUI_CURVE
- N_GUI_GRID
- N_GUI_POPUP
- N_HEX
- N_HTTP3
- N_HTTP_COOKIE
- N_HTTP_FORM
- N_HTTP_MIME
- N_HTTP_REQ
- N_HTTP_RESP
- N_HTTP_ROUTER
- N_HTTP_SERVER
- N_ICC
- N_JSON
- N_KAFKA
- N_LINALG
- N_MODULE
- N_OPENSSL_COMPAT
- N_PRETTY
- N_PTH
- N_QUERY
- N_RANDOM
- N_SHA256
- N_STR
- N_TIFF
- N_TIME
- N_USER
- N_X509
- N_ZIP
- PARTICLES
- PCRE
- PHYSICS
- SIGNALS
- STACK
- THREADS
- TRAJECTORY
- TREE
- ZLIB