calog/libs/calogProc.c

717 lines
25 KiB
C

// calogProc.c -- calog subprocess library (see calogProc.h). Spawns a child, feeds its stdin while
// draining stdout/stderr so a large transfer cannot deadlock, and returns its exit code + captured
// output. POSIX spawns with posix_spawn (NOT fork -- calog runs many pthreads) and multiplexes the
// three pipes with one poll loop. Windows spawns with CreateProcess and, because anonymous pipes
// have no poll, writes stdin on a helper thread while the main thread drains stdout/stderr.
#define _GNU_SOURCE
#include "calogProc.h"
#include "calogInternal.h"
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#ifdef _WIN32
#include <windows.h>
#else
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <signal.h>
#include <spawn.h>
#include <sys/wait.h>
#include <unistd.h>
extern char **environ;
#endif
// Upper bound on captured stdout/stderr, so a runaway child cannot exhaust memory.
#define PROC_MAX (64 * 1024 * 1024)
// Transfer granularity for a single read/write while draining the child's pipes.
#define PROC_CHUNK (64 * 1024)
typedef struct ProcBufT {
char *data;
size_t len;
size_t cap;
} ProcBufT;
#ifdef _WIN32
// Payload handed to the stdin-writer thread (see procWinStdinThread).
typedef struct ProcWinStdinT {
HANDLE handle;
const char *bytes;
size_t length;
} ProcWinStdinT;
#endif
static int32_t procBufAppend(ProcBufT *buffer, const void *bytes, size_t length);
static CalogValueT *procOpt(CalogAggT *opts, const char *name);
static int32_t procResult(CalogValueT *result, int32_t exitCode, ProcBufT *out, ProcBufT *err);
static int32_t procRun(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
#ifdef _WIN32
static int32_t procWinAppendArg(ProcBufT *cmd, const char *arg);
static DWORD WINAPI procWinStdinThread(LPVOID param);
#endif
int32_t calogProcRegister(CalogT *calog) {
return calogRegisterInline(calog, "procRun", procRun, NULL);
}
static int32_t procBufAppend(ProcBufT *buffer, const void *bytes, size_t length) {
if (buffer->len + length > buffer->cap) {
size_t wanted;
char *grown;
wanted = buffer->cap ? buffer->cap * 2 : 4096;
while (wanted < buffer->len + length) {
wanted *= 2;
}
grown = (char *)realloc(buffer->data, wanted);
if (grown == NULL) {
return calogErrOomE;
}
buffer->data = grown;
buffer->cap = wanted;
}
memcpy(buffer->data + buffer->len, bytes, length);
buffer->len += length;
return calogOkE;
}
static CalogValueT *procOpt(CalogAggT *opts, const char *name) {
CalogValueT key;
CalogValueT *field;
if (opts == NULL) {
return NULL;
}
if (calogValueString(&key, name, (int64_t)strlen(name)) != calogOkE) {
return NULL;
}
field = calogAggGet(opts, &key);
calogValueFree(&key);
return field;
}
// Build a result map { exit, stdout, stderr } and hand ownership to result.
static int32_t procResult(CalogValueT *result, int32_t exitCode, ProcBufT *out, ProcBufT *err) {
CalogAggT *map;
int32_t status;
status = calogAggCreate(&map, calogMapE);
if (status != calogOkE) {
return calogFail(result, status, "procRun: out of memory");
}
status = calogMapSetInt(map, "exit", exitCode);
if (status == calogOkE) {
status = calogMapSetStr(map, "stdout", out->data ? out->data : "", (int64_t)out->len);
}
if (status == calogOkE) {
status = calogMapSetStr(map, "stderr", err->data ? err->data : "", (int64_t)err->len);
}
if (status != calogOkE) {
calogAggFree(map);
return calogFail(result, status, "procRun: failed to build the result");
}
calogValueAgg(result, map);
return calogOkE;
}
#ifdef _WIN32
// Spawn a child with CreateProcess. argvList is quoted into a single command line; opts may carry a
// stdin string, a cwd, and an env map. stdin is written on a helper thread (anonymous pipes have no
// poll, and a blocking write would otherwise deadlock the stdout/stderr drain done here). Output is
// capped at PROC_MAX. Returns { exit, stdout, stderr }; exit is the process exit code. Build-verified
// via the zig Windows cross build; not run-verified (no Windows host here).
static int32_t procRun(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
STARTUPINFOA si;
PROCESS_INFORMATION pi;
SECURITY_ATTRIBUTES sa;
ProcWinStdinT stdinJob;
ProcBufT cmd;
ProcBufT envBlock;
ProcBufT outBuf;
ProcBufT errBuf;
HANDLE inRead;
HANDLE inWrite;
HANDLE outRead;
HANDLE outWrite;
HANDLE errRead;
HANDLE errWrite;
HANDLE stdinThread;
const char *stdinBytes;
const char *cwd;
char *envArg;
CalogValueT *field;
int64_t stdinLen;
int64_t argListCount;
int64_t i;
int32_t status;
DWORD exitStatus;
bool outOpen;
bool errOpen;
(void)userData;
calogValueNil(result);
if (argCount < 1 || argCount > 2 || args[0].type != calogAggE || calogAggIsKeyed(args[0].as.agg)) {
return calogFail(result, calogErrArgE, "procRun expects (argvList [, opts])");
}
if (argCount == 2 && (args[1].type != calogAggE || !calogAggIsKeyed(args[1].as.agg))) {
return calogFail(result, calogErrArgE, "procRun: opts must be a map");
}
argListCount = args[0].as.agg->arrayCount;
if (argListCount < 1) {
return calogFail(result, calogErrArgE, "procRun: argvList must have at least the program");
}
// Quote each argv element into one command line (CreateProcess takes a single string).
memset(&cmd, 0, sizeof(cmd));
for (i = 0; i < argListCount; i++) {
if (args[0].as.agg->array[i].type != calogStringE) {
free(cmd.data);
return calogFail(result, calogErrArgE, "procRun: every argv element must be a string");
}
if ((i > 0 && procBufAppend(&cmd, " ", 1) != calogOkE) ||
procWinAppendArg(&cmd, args[0].as.agg->array[i].as.s.bytes) != calogOkE) {
free(cmd.data);
return calogFail(result, calogErrOomE, "procRun: out of memory");
}
}
if (procBufAppend(&cmd, "\0", 1) != calogOkE) {
free(cmd.data);
return calogFail(result, calogErrOomE, "procRun: out of memory");
}
stdinBytes = NULL;
stdinLen = 0;
cwd = NULL;
envArg = NULL;
memset(&envBlock, 0, sizeof(envBlock));
if (argCount == 2) {
field = procOpt(args[1].as.agg, "stdin");
if (field != NULL && field->type == calogStringE) {
stdinBytes = field->as.s.bytes;
stdinLen = field->as.s.length;
}
field = procOpt(args[1].as.agg, "cwd");
if (field != NULL && field->type == calogStringE) {
cwd = field->as.s.bytes;
}
field = procOpt(args[1].as.agg, "env");
if (field != NULL && field->type == calogAggE && calogAggIsKeyed(field->as.agg)) {
CalogAggT *envMap;
int64_t e;
envMap = field->as.agg;
for (e = 0; e < envMap->pairCount; e++) {
if (envMap->pairs[e].key.type != calogStringE || envMap->pairs[e].value.type != calogStringE) {
continue;
}
if (procBufAppend(&envBlock, envMap->pairs[e].key.as.s.bytes, (size_t)envMap->pairs[e].key.as.s.length) != calogOkE ||
procBufAppend(&envBlock, "=", 1) != calogOkE ||
procBufAppend(&envBlock, envMap->pairs[e].value.as.s.bytes, (size_t)envMap->pairs[e].value.as.s.length) != calogOkE ||
procBufAppend(&envBlock, "\0", 1) != calogOkE) {
free(cmd.data);
free(envBlock.data);
return calogFail(result, calogErrOomE, "procRun: out of memory");
}
}
// An environment block is terminated by a final extra NUL (a double NUL overall).
if (procBufAppend(&envBlock, "\0", 1) != calogOkE) {
free(cmd.data);
free(envBlock.data);
return calogFail(result, calogErrOomE, "procRun: out of memory");
}
envArg = envBlock.data;
}
}
// Three inheritable pipes; the parent's own ends are made non-inheritable so the child cannot
// keep them open (which would keep our reads from ever seeing end-of-file).
sa.nLength = sizeof(sa);
sa.bInheritHandle = TRUE;
sa.lpSecurityDescriptor = NULL;
// Create the three pipes one at a time so a partial failure can close the ends already opened
// (a single short-circuited guard would leak them).
if (!CreatePipe(&inRead, &inWrite, &sa, 0)) {
free(cmd.data);
free(envBlock.data);
return calogFail(result, calogErrArgE, "procRun: could not create pipes");
}
if (!CreatePipe(&outRead, &outWrite, &sa, 0)) {
CloseHandle(inRead);
CloseHandle(inWrite);
free(cmd.data);
free(envBlock.data);
return calogFail(result, calogErrArgE, "procRun: could not create pipes");
}
if (!CreatePipe(&errRead, &errWrite, &sa, 0)) {
CloseHandle(inRead);
CloseHandle(inWrite);
CloseHandle(outRead);
CloseHandle(outWrite);
free(cmd.data);
free(envBlock.data);
return calogFail(result, calogErrArgE, "procRun: could not create pipes");
}
SetHandleInformation(inWrite, HANDLE_FLAG_INHERIT, 0);
SetHandleInformation(outRead, HANDLE_FLAG_INHERIT, 0);
SetHandleInformation(errRead, HANDLE_FLAG_INHERIT, 0);
memset(&si, 0, sizeof(si));
si.cb = sizeof(si);
si.dwFlags = STARTF_USESTDHANDLES;
si.hStdInput = inRead;
si.hStdOutput = outWrite;
si.hStdError = errWrite;
memset(&pi, 0, sizeof(pi));
if (!CreateProcessA(NULL, cmd.data, NULL, NULL, TRUE, 0, envArg, cwd, &si, &pi)) {
CloseHandle(inRead);
CloseHandle(inWrite);
CloseHandle(outRead);
CloseHandle(outWrite);
CloseHandle(errRead);
CloseHandle(errWrite);
free(cmd.data);
free(envBlock.data);
return calogFail(result, calogErrArgE, "procRun: CreateProcess failed");
}
free(cmd.data);
free(envBlock.data);
// The child owns the far ends now; close ours so end-of-file becomes observable.
CloseHandle(inRead);
CloseHandle(outWrite);
CloseHandle(errWrite);
stdinThread = NULL;
if (stdinLen > 0) {
stdinJob.handle = inWrite;
stdinJob.bytes = stdinBytes;
stdinJob.length = (size_t)stdinLen;
stdinThread = CreateThread(NULL, 0, procWinStdinThread, &stdinJob, 0, NULL);
}
if (stdinThread == NULL) {
CloseHandle(inWrite); // nothing to send, or the thread could not start
}
memset(&outBuf, 0, sizeof(outBuf));
memset(&errBuf, 0, sizeof(errBuf));
status = calogOkE;
outOpen = true;
errOpen = true;
while (status == calogOkE && (outOpen || errOpen)) {
bool didRead;
HANDLE pipes[2];
bool *open[2];
ProcBufT *bufs[2];
int p;
didRead = false;
pipes[0] = outRead;
pipes[1] = errRead;
open[0] = &outOpen;
open[1] = &errOpen;
bufs[0] = &outBuf;
bufs[1] = &errBuf;
for (p = 0; p < 2; p++) {
DWORD avail;
DWORD got;
char chunk[PROC_CHUNK];
if (!*open[p]) {
continue;
}
if (!PeekNamedPipe(pipes[p], NULL, 0, NULL, &avail, NULL)) {
*open[p] = false; // broken pipe: the child closed this stream and it is drained
continue;
}
if (avail == 0) {
continue; // open but idle; the Sleep below yields before we retry
}
if (!ReadFile(pipes[p], chunk, (avail > PROC_CHUNK) ? PROC_CHUNK : avail, &got, NULL) || got == 0) {
*open[p] = false;
continue;
}
if (bufs[p]->len + got > PROC_MAX || procBufAppend(bufs[p], chunk, got) != calogOkE) {
status = calogFail(result, calogErrRangeE, (p == 0) ? "procRun: stdout exceeds the size cap" : "procRun: stderr exceeds the size cap");
break;
}
didRead = true;
}
if (status == calogOkE && !didRead && (outOpen || errOpen)) {
Sleep(1);
}
}
if (status != calogOkE) {
TerminateProcess(pi.hProcess, 1);
}
WaitForSingleObject(pi.hProcess, INFINITE);
GetExitCodeProcess(pi.hProcess, &exitStatus);
if (stdinThread != NULL) {
WaitForSingleObject(stdinThread, INFINITE);
CloseHandle(stdinThread);
}
CloseHandle(outRead);
CloseHandle(errRead);
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
if (status != calogOkE) {
free(outBuf.data);
free(errBuf.data);
return status;
}
status = procResult(result, (int32_t)exitStatus, &outBuf, &errBuf);
free(outBuf.data);
free(errBuf.data);
return status;
}
// Append arg to cmd as one command-line token, quoted per the CommandLineToArgvW rules: a run of
// backslashes is doubled only when it precedes a double quote or ends the (quoted) token, and the
// token is wrapped in quotes when it is empty or contains whitespace or a quote.
static int32_t procWinAppendArg(ProcBufT *cmd, const char *arg) {
size_t len;
size_t i;
bool quote;
len = strlen(arg);
quote = (len == 0);
for (i = 0; i < len && !quote; i++) {
if (arg[i] == ' ' || arg[i] == '\t' || arg[i] == '\n' || arg[i] == '\v' || arg[i] == '"') {
quote = true;
}
}
if (!quote) {
return procBufAppend(cmd, arg, len);
}
if (procBufAppend(cmd, "\"", 1) != calogOkE) {
return calogErrOomE;
}
i = 0;
while (i < len) {
size_t slashes;
size_t s;
slashes = 0;
while (i < len && arg[i] == '\\') {
slashes++;
i++;
}
if (i == len) {
slashes *= 2; // trailing backslashes precede the closing quote
} else if (arg[i] == '"') {
slashes = slashes * 2 + 1; // backslashes then the escaped quote
}
for (s = 0; s < slashes; s++) {
if (procBufAppend(cmd, "\\", 1) != calogOkE) {
return calogErrOomE;
}
}
if (i < len) {
if (procBufAppend(cmd, &arg[i], 1) != calogOkE) {
return calogErrOomE;
}
i++;
}
}
return procBufAppend(cmd, "\"", 1);
}
// Write the whole stdin payload, then close the pipe. Runs on its own thread so a blocking WriteFile
// on a full pipe cannot stall the main thread's stdout/stderr drain.
static DWORD WINAPI procWinStdinThread(LPVOID param) {
ProcWinStdinT *job;
size_t pos;
job = (ProcWinStdinT *)param;
pos = 0;
while (pos < job->length) {
DWORD wrote;
DWORD chunk;
chunk = (job->length - pos > PROC_CHUNK) ? PROC_CHUNK : (DWORD)(job->length - pos);
if (!WriteFile(job->handle, job->bytes + pos, chunk, &wrote, NULL) || wrote == 0) {
break; // the child closed its stdin (or exited); stop feeding it
}
pos += wrote;
}
CloseHandle(job->handle);
return 0;
}
#else
static int32_t procRun(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
posix_spawn_file_actions_t actions;
ProcBufT outBuf;
ProcBufT errBuf;
char **argv;
char **builtEnv;
char **envp;
const char *stdinBytes;
const char *cwd;
CalogValueT *field;
int64_t stdinLen;
int64_t stdinPos;
int64_t argCountList;
int64_t i;
int inPipe[2];
int outPipe[2];
int errPipe[2];
int spawnRc;
int waitStatus;
int32_t exitCode;
int32_t status;
pid_t pid;
bool actionsReady;
(void)userData;
calogValueNil(result);
if (argCount < 1 || argCount > 2 || args[0].type != calogAggE || calogAggIsKeyed(args[0].as.agg)) {
return calogFail(result, calogErrArgE, "procRun expects (argvList [, opts])");
}
if (argCount == 2 && (args[1].type != calogAggE || !calogAggIsKeyed(args[1].as.agg))) {
return calogFail(result, calogErrArgE, "procRun: opts must be a map");
}
argCountList = args[0].as.agg->arrayCount;
if (argCountList < 1) {
return calogFail(result, calogErrArgE, "procRun: argvList must have at least the program");
}
argv = (char **)calloc((size_t)argCountList + 1, sizeof(char *));
if (argv == NULL) {
return calogFail(result, calogErrOomE, "procRun: out of memory");
}
for (i = 0; i < argCountList; i++) {
if (args[0].as.agg->array[i].type != calogStringE) {
free(argv);
return calogFail(result, calogErrArgE, "procRun: every argv element must be a string");
}
argv[i] = args[0].as.agg->array[i].as.s.bytes;
}
stdinBytes = NULL;
stdinLen = 0;
cwd = NULL;
builtEnv = NULL;
envp = environ;
if (argCount == 2) {
field = procOpt(args[1].as.agg, "stdin");
if (field != NULL && field->type == calogStringE) {
stdinBytes = field->as.s.bytes;
stdinLen = field->as.s.length;
}
field = procOpt(args[1].as.agg, "cwd");
if (field != NULL && field->type == calogStringE) {
cwd = field->as.s.bytes;
}
field = procOpt(args[1].as.agg, "env");
if (field != NULL && field->type == calogAggE && calogAggIsKeyed(field->as.agg)) {
CalogAggT *envMap;
int64_t envCount;
int64_t e;
envMap = field->as.agg;
envCount = envMap->pairCount;
builtEnv = (char **)calloc((size_t)envCount + 1, sizeof(char *));
if (builtEnv == NULL) {
free(argv);
return calogFail(result, calogErrOomE, "procRun: out of memory");
}
for (e = 0; e < envCount; e++) {
const char *k;
const char *v;
size_t klen;
size_t vlen;
if (envMap->pairs[e].key.type != calogStringE || envMap->pairs[e].value.type != calogStringE) {
continue;
}
k = envMap->pairs[e].key.as.s.bytes;
v = envMap->pairs[e].value.as.s.bytes;
klen = (size_t)envMap->pairs[e].key.as.s.length;
vlen = (size_t)envMap->pairs[e].value.as.s.length;
builtEnv[e] = (char *)malloc(klen + vlen + 2);
if (builtEnv[e] == NULL) {
continue;
}
memcpy(builtEnv[e], k, klen);
builtEnv[e][klen] = '=';
memcpy(builtEnv[e] + klen + 1, v, vlen);
builtEnv[e][klen + 1 + vlen] = '\0';
}
envp = builtEnv;
}
}
if (pipe(inPipe) != 0 || pipe(outPipe) != 0 || pipe(errPipe) != 0) {
free(argv);
if (builtEnv != NULL) {
for (i = 0; builtEnv[i] != NULL; i++) {
free(builtEnv[i]);
}
free(builtEnv);
}
return calogFail(result, calogErrArgE, "procRun: could not create pipes");
}
posix_spawn_file_actions_init(&actions);
actionsReady = true;
if (cwd != NULL) {
posix_spawn_file_actions_addchdir_np(&actions, cwd);
}
posix_spawn_file_actions_adddup2(&actions, inPipe[0], 0);
posix_spawn_file_actions_adddup2(&actions, outPipe[1], 1);
posix_spawn_file_actions_adddup2(&actions, errPipe[1], 2);
posix_spawn_file_actions_addclose(&actions, inPipe[1]);
posix_spawn_file_actions_addclose(&actions, outPipe[0]);
posix_spawn_file_actions_addclose(&actions, errPipe[0]);
spawnRc = posix_spawnp(&pid, argv[0], &actions, NULL, argv, envp);
posix_spawn_file_actions_destroy(&actions);
(void)actionsReady;
// Parent keeps the outer ends: write inPipe[1], read outPipe[0]/errPipe[0].
close(inPipe[0]);
close(outPipe[1]);
close(errPipe[1]);
free(argv);
if (builtEnv != NULL) {
for (i = 0; builtEnv[i] != NULL; i++) {
free(builtEnv[i]);
}
free(builtEnv);
}
if (spawnRc != 0) {
close(inPipe[1]);
close(outPipe[0]);
close(errPipe[0]);
return calogFail(result, calogErrArgE, strerror(spawnRc));
}
fcntl(inPipe[1], F_SETFL, fcntl(inPipe[1], F_GETFL, 0) | O_NONBLOCK);
fcntl(outPipe[0], F_SETFL, fcntl(outPipe[0], F_GETFL, 0) | O_NONBLOCK);
fcntl(errPipe[0], F_SETFL, fcntl(errPipe[0], F_GETFL, 0) | O_NONBLOCK);
memset(&outBuf, 0, sizeof(outBuf));
memset(&errBuf, 0, sizeof(errBuf));
stdinPos = 0;
if (stdinLen == 0) {
close(inPipe[1]);
inPipe[1] = -1;
}
status = calogOkE;
while (inPipe[1] >= 0 || outPipe[0] >= 0 || errPipe[0] >= 0) {
struct pollfd fds[3];
int nfds;
int inIdx;
int outIdx;
int errIdx;
nfds = 0;
inIdx = -1;
outIdx = -1;
errIdx = -1;
if (inPipe[1] >= 0) {
fds[nfds].fd = inPipe[1];
fds[nfds].events = POLLOUT;
inIdx = nfds++;
}
if (outPipe[0] >= 0) {
fds[nfds].fd = outPipe[0];
fds[nfds].events = POLLIN;
outIdx = nfds++;
}
if (errPipe[0] >= 0) {
fds[nfds].fd = errPipe[0];
fds[nfds].events = POLLIN;
errIdx = nfds++;
}
if (poll(fds, (nfds_t)nfds, -1) < 0) {
if (errno == EINTR) {
continue;
}
status = calogFail(result, calogErrArgE, strerror(errno));
break;
}
if (inIdx >= 0 && (fds[inIdx].revents & (POLLOUT | POLLERR | POLLHUP))) {
ssize_t wrote;
wrote = write(inPipe[1], stdinBytes + stdinPos, (size_t)(stdinLen - stdinPos));
if (wrote > 0) {
stdinPos += wrote;
}
if (wrote < 0 && errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) {
close(inPipe[1]);
inPipe[1] = -1;
} else if (stdinPos >= stdinLen) {
close(inPipe[1]);
inPipe[1] = -1;
}
}
if (outIdx >= 0 && (fds[outIdx].revents & (POLLIN | POLLERR | POLLHUP))) {
char chunk[PROC_CHUNK];
ssize_t got;
got = read(outPipe[0], chunk, sizeof(chunk));
if (got > 0) {
if (outBuf.len + (size_t)got > PROC_MAX || procBufAppend(&outBuf, chunk, (size_t)got) != calogOkE) {
status = calogFail(result, calogErrRangeE, "procRun: stdout exceeds the size cap");
close(outPipe[0]);
outPipe[0] = -1;
}
} else if (got == 0 || (got < 0 && errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR)) {
close(outPipe[0]);
outPipe[0] = -1;
}
}
if (errIdx >= 0 && (fds[errIdx].revents & (POLLIN | POLLERR | POLLHUP))) {
char chunk[PROC_CHUNK];
ssize_t got;
got = read(errPipe[0], chunk, sizeof(chunk));
if (got > 0) {
if (errBuf.len + (size_t)got > PROC_MAX || procBufAppend(&errBuf, chunk, (size_t)got) != calogOkE) {
status = calogFail(result, calogErrRangeE, "procRun: stderr exceeds the size cap");
close(errPipe[0]);
errPipe[0] = -1;
}
} else if (got == 0 || (got < 0 && errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR)) {
close(errPipe[0]);
errPipe[0] = -1;
}
}
if (status != calogOkE) {
break;
}
}
if (inPipe[1] >= 0) {
close(inPipe[1]);
}
if (outPipe[0] >= 0) {
close(outPipe[0]);
}
if (errPipe[0] >= 0) {
close(errPipe[0]);
}
// If we aborted early (e.g. the output cap was exceeded), the child may still be running and
// blocked writing to the pipe ends we just closed; kill it so waitpid cannot hang forever.
if (status != calogOkE) {
kill(pid, SIGKILL);
}
while (waitpid(pid, &waitStatus, 0) < 0 && errno == EINTR) {
continue;
}
if (status != calogOkE) {
free(outBuf.data);
free(errBuf.data);
return status;
}
if (WIFEXITED(waitStatus)) {
exitCode = (int32_t)WEXITSTATUS(waitStatus);
} else if (WIFSIGNALED(waitStatus)) {
exitCode = -(int32_t)WTERMSIG(waitStatus);
} else {
exitCode = -1;
}
status = procResult(result, exitCode, &outBuf, &errBuf);
free(outBuf.data);
free(errBuf.data);
return status;
}
#endif