Overhaul process spawning

This commit is contained in:
Justine Tunney 2023-09-10 08:12:43 -07:00
parent 99dc1281f5
commit 26e254fb4d
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
96 changed files with 1848 additions and 1541 deletions

View file

@ -46,6 +46,7 @@ TOOL_BUILD_DIRECTDEPS = \
LIBC_TIME \
LIBC_TINYMATH \
LIBC_X \
NET_HTTP \
NET_HTTPS \
THIRD_PARTY_COMPILER_RT \
THIRD_PARTY_GDTOA \

View file

@ -34,7 +34,6 @@
#include "libc/fmt/libgen.h"
#include "libc/fmt/magnumstrs.internal.h"
#include "libc/intrin/bits.h"
#include "libc/intrin/kprintf.h"
#include "libc/intrin/safemacros.internal.h"
#include "libc/limits.h"
#include "libc/log/appendresourcereport.internal.h"
@ -50,7 +49,7 @@
#include "libc/nexgen32e/x86info.h"
#include "libc/runtime/runtime.h"
#include "libc/stdio/append.h"
#include "libc/stdio/stdio.h"
#include "libc/stdio/posix_spawn.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/auxv.h"
#include "libc/sysv/consts/clock.h"
@ -115,7 +114,7 @@ FLAGS\n\
-C SECS set cpu limit [default 16]\n\
-L SECS set lat limit [default 90]\n\
-P PROCS set pro limit [default 2048]\n\
-S BYTES set stk limit [default 2m]\n\
-S BYTES set stk limit [default 8m]\n\
-M BYTES set mem limit [default 512m]\n\
-F BYTES set fsz limit [default 256m]\n\
-O BYTES set out limit [default 1m]\n\
@ -138,7 +137,8 @@ ENVIRONMENT\n\
\n"
struct Strings {
size_t n;
int n;
int c;
char **p;
};
@ -206,6 +206,8 @@ sigset_t mask;
char buf[4096];
sigset_t savemask;
char tmpout[PATH_MAX];
posix_spawnattr_t spawnattr;
posix_spawn_file_actions_t spawnfila;
char *g_tmpout;
const char *g_tmpout_original;
@ -414,16 +416,20 @@ bool IsGccOnlyFlag(const char *s) {
return true;
}
}
if (startswith(s, "-ffixed-")) return true;
if (startswith(s, "-fcall-saved")) return true;
if (startswith(s, "-fcall-used")) return true;
if (startswith(s, "-fgcse-")) return true;
if (startswith(s, "-fvect-cost-model=")) return true;
if (startswith(s, "-fsimd-cost-model=")) return true;
if (startswith(s, "-fopt-info")) return true;
if (startswith(s, "-mstringop-strategy=")) return true;
if (startswith(s, "-mpreferred-stack-boundary=")) return true;
if (startswith(s, "-Wframe-larger-than=")) return true;
if (s[0] == '-') {
if (s[1] == 'f') {
if (startswith(s, "-ffixed-")) return true;
if (startswith(s, "-fcall-saved")) return true;
if (startswith(s, "-fcall-used")) return true;
if (startswith(s, "-fgcse-")) return true;
if (startswith(s, "-fvect-cost-model=")) return true;
if (startswith(s, "-fsimd-cost-model=")) return true;
if (startswith(s, "-fopt-info")) return true;
}
if (startswith(s, "-mstringop-strategy=")) return true;
if (startswith(s, "-mpreferred-stack-boundary=")) return true;
if (startswith(s, "-Wframe-larger-than=")) return true;
}
return false;
}
@ -446,9 +452,16 @@ static size_t TallyArgs(char **p) {
}
void AddStr(struct Strings *l, char *s) {
l->p = realloc(l->p, (++l->n + 1) * sizeof(*l->p));
l->p[l->n - 1] = s;
l->p[l->n - 0] = 0;
if (l->n == l->c) {
if (l->c) {
l->c += l->c >> 1;
} else {
l->c = 16;
}
l->p = realloc(l->p, (l->c + 1) * sizeof(*l->p));
}
l->p[l->n++] = s;
l->p[l->n] = 0;
}
void AddEnv(char *s) {
@ -510,58 +523,47 @@ static int GetBaseCpuFreqMhz(void) {
return KCPUIDS(16H, EAX) & 0x7fff;
}
void PlanResource(int resource, struct rlimit rlim) {
struct rlimit prior;
if (getrlimit(resource, &prior)) return;
rlim.rlim_cur = MIN(rlim.rlim_cur, prior.rlim_max);
rlim.rlim_max = MIN(rlim.rlim_max, prior.rlim_max);
posix_spawnattr_setrlimit(&spawnattr, resource, &rlim);
}
void SetCpuLimit(int secs) {
if (secs <= 0) return;
if (IsWindows()) return;
#ifdef __x86_64__
int mhz, lim;
struct rlimit rlim;
if (!(mhz = GetBaseCpuFreqMhz())) return;
lim = ceil(3100. / mhz * secs);
rlim.rlim_cur = lim;
rlim.rlim_max = lim + 1;
if (setrlimit(RLIMIT_CPU, &rlim) == -1) {
if (getrlimit(RLIMIT_CPU, &rlim) == -1) return;
if (lim < rlim.rlim_cur) {
rlim.rlim_cur = lim;
setrlimit(RLIMIT_CPU, &rlim);
}
}
PlanResource(RLIMIT_CPU, (struct rlimit){lim, lim + 1});
#endif
}
void SetFszLimit(long n) {
struct rlimit rlim;
if (n <= 0) return;
if (IsWindows()) return;
rlim.rlim_cur = n;
rlim.rlim_max = n + (n >> 1);
if (setrlimit(RLIMIT_FSIZE, &rlim) == -1) {
if (getrlimit(RLIMIT_FSIZE, &rlim) == -1) return;
rlim.rlim_cur = n;
setrlimit(RLIMIT_FSIZE, &rlim);
}
PlanResource(RLIMIT_FSIZE, (struct rlimit){n, n + (n >> 1)});
}
void SetMemLimit(long n) {
struct rlimit rlim = {n, n};
if (n <= 0) return;
if (IsWindows() || IsXnu()) return;
setrlimit(RLIMIT_AS, &rlim);
PlanResource(RLIMIT_AS, (struct rlimit){n, n});
}
void SetStkLimit(long n) {
if (IsWindows()) return;
if (n <= 0) return;
n = MAX(n, PTHREAD_STACK_MIN * 2);
struct rlimit rlim = {n, n};
setrlimit(RLIMIT_STACK, &rlim);
PlanResource(RLIMIT_STACK, (struct rlimit){n, n});
}
void SetProLimit(long n) {
struct rlimit rlim = {n, n};
if (n <= 0) return;
setrlimit(RLIMIT_NPROC, &rlim);
PlanResource(RLIMIT_NPROC, (struct rlimit){n, n});
}
bool ArgNeedsShellQuotes(const char *s) {
@ -616,7 +618,7 @@ char *AddShellQuotes(const char *s) {
void MakeDirs(const char *path, int mode) {
if (makedirs(path, mode)) {
kprintf("error: makedirs(%#s) failed\n", path);
perror(path);
exit(1);
}
}
@ -624,15 +626,29 @@ void MakeDirs(const char *path, int mode) {
int Launch(void) {
size_t got;
ssize_t rc;
errno_t err;
int ws, pid;
uint64_t us;
gotchld = 0;
if (pipe2(pipefds, O_CLOEXEC) == -1) {
kprintf("pipe2 failed: %s\n", _strerrno(errno));
perror("pipe2");
exit(1);
}
posix_spawnattr_init(&spawnattr);
posix_spawnattr_setsigmask(&spawnattr, &savemask);
SetCpuLimit(cpuquota);
SetFszLimit(fszquota);
SetMemLimit(memquota);
SetStkLimit(stkquota);
SetProLimit(proquota);
posix_spawn_file_actions_init(&spawnfila);
if (stdoutmustclose)
posix_spawn_file_actions_adddup2(&spawnfila, pipefds[1], 1);
posix_spawn_file_actions_adddup2(&spawnfila, pipefds[1], 2);
clock_gettime(CLOCK_MONOTONIC, &start);
if (timeout > 0) {
timer.it_value.tv_sec = timeout;
@ -640,36 +656,17 @@ int Launch(void) {
setitimer(ITIMER_REAL, &timer, 0);
}
pid = vfork();
if (pid == -1) {
kprintf("vfork failed: %s\n", _strerrno(errno));
err = posix_spawn(&pid, cmd, &spawnfila, &spawnattr, args.p, env.p);
if (err) {
tinyprint(2, program_invocation_short_name, ": failed to spawn ", cmd, ": ",
strerror(err), " (see --strace for further details)\n", NULL);
exit(1);
}
#if 0
int fd;
size_t n;
char b[1024], *p;
size_t t = strlen(cmd) + 1 + TallyArgs(args.p) + 9 + TallyArgs(env.p) + 9;
n = ksnprintf(b, sizeof(b), "%ld %s %s\n", t, cmd, outpath);
fd = open("o/argmax.txt", O_APPEND | O_CREAT | O_WRONLY, 0644);
write(fd, b, n);
close(fd);
#endif
if (!pid) {
SetCpuLimit(cpuquota);
SetFszLimit(fszquota);
SetMemLimit(memquota);
SetStkLimit(stkquota);
SetProLimit(proquota);
if (stdoutmustclose) dup2(pipefds[1], 1);
dup2(pipefds[1], 2);
sigprocmask(SIG_SETMASK, &savemask, 0);
execve(cmd, args.p, env.p);
kprintf("execve(%#s) failed: %s\n", cmd, _strerrno(errno));
_Exit(127);
}
signal(SIGINT, SIG_IGN);
signal(SIGQUIT, SIG_IGN);
posix_spawn_file_actions_destroy(&spawnfila);
posix_spawnattr_destroy(&spawnattr);
close(pipefds[1]);
for (;;) {
@ -790,12 +787,10 @@ bool MovePreservingDestinationInode(const char *from, const char *to) {
remain -= rc;
} else if (errno == EXDEV || errno == ENOSYS) {
if (lseek(fdin, 0, SEEK_SET) == -1) {
kprintf("%s: failed to lseek\n", from);
res = false;
break;
}
if (lseek(fdout, 0, SEEK_SET) == -1) {
kprintf("%s: failed to lseek\n", to);
res = false;
break;
}
@ -811,26 +806,6 @@ bool MovePreservingDestinationInode(const char *from, const char *to) {
return res;
}
bool IsNativeExecutable(const char *path) {
bool res;
char buf[4];
int got, fd;
res = false;
if ((fd = open(path, O_RDONLY)) != -1) {
if ((got = read(fd, buf, 4)) == 4) {
if (IsWindows()) {
res = READ16LE(buf) == READ16LE("MZ");
} else if (IsXnu()) {
res = READ32LE(buf) == 0xFEEDFACEu + 1;
} else {
res = READ32LE(buf) == READ32LE("\177ELF");
}
}
close(fd);
}
return res;
}
char *MakeTmpOut(const char *path) {
int c;
char *p = tmpout;
@ -840,7 +815,10 @@ char *MakeTmpOut(const char *path) {
while ((c = *path++)) {
if (c == '/') c = '_';
if (p == e) {
kprintf("MakeTmpOut path too long: %s\n", tmpout);
tinyprint(2, program_invocation_short_name,
": fatal error: MakeTmpOut() generated temporary filename "
"that's too long: ",
tmpout, "\n", NULL);
exit(1);
}
*p++ = c;
@ -864,14 +842,12 @@ int main(int argc, char *argv[]) {
mode = firstnonnull(getenv("MODE"), MODE);
/*
* parse prefix arguments
*/
// parse prefix arguments
verbose = 4;
timeout = 90; /* secs */
cpuquota = 32; /* secs */
proquota = 2048; /* procs */
stkquota = 2 * 1024 * 1024; /* bytes */
stkquota = 8 * 1024 * 1024; /* bytes */
fszquota = 256 * 1000 * 1000; /* bytes */
memquota = 512 * 1024 * 1024; /* bytes */
if ((s = getenv("V"))) verbose = atoi(s);
@ -922,24 +898,23 @@ int main(int argc, char *argv[]) {
outquota = sizetol(optarg, 1024);
break;
case 'h':
fputs(MANUAL, stdout);
tinyprint(1, MANUAL, NULL);
exit(0);
default:
fputs(MANUAL, stderr);
tinyprint(2, MANUAL, NULL);
exit(1);
}
}
if (optind == argc) {
fputs("error: missing arguments\n", stderr);
tinyprint(2, program_invocation_short_name, ": missing arguments\n", NULL);
exit(1);
}
/*
* extend limits for slow UBSAN in particular
*/
// extend limits for slow UBSAN in particular
if (!strcmp(mode, "dbg") || !strcmp(mode, "ubsan")) {
cpuquota *= 2;
fszquota *= 2;
stkquota *= 2;
memquota *= 2;
timeout *= 2;
}
@ -964,18 +939,14 @@ int main(int argc, char *argv[]) {
ispkg = true;
}
/*
* ingest arguments
*/
// ingest arguments
for (i = optind; i < argc; ++i) {
/*
* replace output filename argument
*
* some commands (e.g. ar) don't use the `-o PATH` notation. in that
* case we assume the output path was passed to compile.com -TTARGET
* which means we can replace the appropriate command line argument.
*/
// replace output filename argument
//
// some commands (e.g. ar) don't use the `-o PATH` notation. in that
// case we assume the output path was passed to compile.com -TTARGET
// which means we can replace the appropriate command line argument.
if (!noworkaround && //
!movepath && //
!outpath && //
@ -1232,9 +1203,7 @@ int main(int argc, char *argv[]) {
exit(7);
}
/*
* append special args
*/
// append special args
if (iscc) {
if (isclang) {
AddArg("-Wno-unused-command-line-argument");
@ -1290,9 +1259,7 @@ int main(int argc, char *argv[]) {
}
}
/*
* scrub environment for determinism and great justice
*/
// scrub environment for determinism and great justice
for (envp = environ; *envp; ++envp) {
if (startswith(*envp, "MODE=")) {
mode = *envp + 5;
@ -1304,9 +1271,7 @@ int main(int argc, char *argv[]) {
AddEnv("LC_ALL=C");
AddEnv("SOURCE_DATE_EPOCH=0");
/*
* ensure output directory exists
*/
// ensure output directory exists
if (outpath) {
outdir = xdirname(outpath);
if (!isdirectory(outdir)) {
@ -1314,49 +1279,40 @@ int main(int argc, char *argv[]) {
}
}
/*
* make sense of standard i/o file descriptors
* we want to permit pipelines but prevent talking to terminal
*/
// make sense of standard i/o file descriptors
// we want to permit pipelines but prevent talking to terminal
stdoutmustclose = fstat(1, &st) == -1 || S_ISCHR(st.st_mode);
if (fstat(0, &st) == -1 || S_ISCHR(st.st_mode)) {
close(0);
open("/dev/null", O_RDONLY);
}
/*
* SIGINT (CTRL-C) and SIGQUIT (CTRL-\) are delivered to process group
* so the correct thing to do is to do nothing, and wait for the child
* to die as a result of those signals. SIGPIPE shouldn't happen until
* the very end since we buffer so it is safe to let it kill the prog.
* Most importantly we need SIGCHLD to interrupt the read() operation!
*/
sigfillset(&mask);
sigdelset(&mask, SIGILL);
sigdelset(&mask, SIGBUS);
sigdelset(&mask, SIGPIPE);
sigdelset(&mask, SIGALRM);
sigdelset(&mask, SIGSEGV);
sigdelset(&mask, SIGCHLD);
// SIGINT (CTRL-C) and SIGQUIT (CTRL-\) are delivered to the child
// process, so we should ignore it and wait for the child to die.
// SIGPIPE shouldn't happen until the very end since we buffer so it
// is safe to let it kill the prog.
sigemptyset(&mask);
sigaddset(&mask, SIGINT);
sigaddset(&mask, SIGQUIT);
sigprocmask(SIG_BLOCK, &mask, &savemask);
// we want SIGCHLD to interrupt the read() operation
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_NOCLDSTOP | SA_SIGINFO;
sa.sa_sigaction = OnChld;
if (sigaction(SIGCHLD, &sa, 0) == -1) exit(83);
sigaction(SIGCHLD, &sa, 0);
// set a death clock if requested
if (timeout > 0) {
sa.sa_flags = 0;
sa.sa_handler = OnAlrm;
sigaction(SIGALRM, &sa, 0);
}
/*
* run command
*/
// run command
ws = Launch();
/*
* propagate exit
*/
// propagate exit
if (ws != -1) {
if (WIFEXITED(ws)) {
if (!(exitcode = WEXITSTATUS(ws)) || exitcode == 254) {
@ -1423,9 +1379,7 @@ int main(int argc, char *argv[]) {
exitcode = 89;
}
/*
* describe command that was run
*/
// describe command that was run
if (!exitcode || exitcode == 254) {
if (exitcode == 254) {
exitcode = 0;
@ -1576,9 +1530,7 @@ int main(int argc, char *argv[]) {
ReportResources();
}
/*
* flush output
*/
// flush output
if (WriteAllUntilSignalledOrError(2, output, appendz(output).i) == -1) {
if (errno == EINTR) {
s = "notice: compile.com output truncated\n";

View file

@ -16,6 +16,7 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/runtime/runtime.h"
#include "libc/stdio/stdio.h"
#include "libc/str/str.h"

View file

@ -44,6 +44,7 @@ TOOL_BUILD_LIB_A_DIRECTDEPS = \
LIBC_STR \
LIBC_SYSV \
LIBC_SYSV_CALLS \
LIBC_THREAD \
LIBC_TIME \
LIBC_TINYMATH \
LIBC_X \

View file

@ -17,11 +17,13 @@
PERFORMANCE OF THIS SOFTWARE.
*/
#include "tool/build/lib/eztls.h"
#include "libc/assert.h"
#include "libc/calls/calls.h"
#include "libc/calls/struct/iovec.h"
#include "libc/errno.h"
#include "libc/log/log.h"
#include "libc/intrin/kprintf.h"
#include "libc/macros.internal.h"
#include "libc/runtime/syslib.internal.h"
#include "libc/sysv/consts/sig.h"
#include "libc/x/x.h"
#include "libc/x/xsigaction.h"
@ -29,12 +31,30 @@
#include "third_party/mbedtls/net_sockets.h"
#include "third_party/mbedtls/ssl.h"
struct EzTlsBio ezbio;
mbedtls_ssl_config ezconf;
mbedtls_ssl_context ezssl;
mbedtls_ctr_drbg_context ezrng;
_Thread_local int mytid;
_Thread_local struct EzTlsBio ezbio;
_Thread_local mbedtls_ssl_config ezconf;
_Thread_local mbedtls_ssl_context ezssl;
_Thread_local mbedtls_ctr_drbg_context ezrng;
void EzSanity(void) {
unassert(mytid);
unassert(mytid == gettid());
}
void EzTlsDie(const char *s, int r) {
EzSanity();
if (IsTiny()) {
kprintf("error: %s (-0x%04x %s)\n", s, -r, GetTlsError(r));
} else {
kprintf("error: %s (grep -0x%04x)\n", s, -r);
}
EzDestroy();
pthread_exit(0);
}
static ssize_t EzWritevAll(int fd, struct iovec *iov, int iovlen) {
EzSanity();
int i;
ssize_t rc;
size_t wrote, total;
@ -58,7 +78,7 @@ static ssize_t EzWritevAll(int fd, struct iovec *iov, int iovlen) {
}
} while (wrote);
} else {
WARNF("writev() failed %m");
// WARNF("writev() failed %m");
if (errno != EINTR) {
return total ? total : -1;
}
@ -68,6 +88,7 @@ static ssize_t EzWritevAll(int fd, struct iovec *iov, int iovlen) {
}
int EzTlsFlush(struct EzTlsBio *bio, const unsigned char *buf, size_t len) {
EzSanity();
struct iovec v[2];
if (len || bio->c > 0) {
v[0].iov_base = bio->u;
@ -81,7 +102,7 @@ int EzTlsFlush(struct EzTlsBio *bio, const unsigned char *buf, size_t len) {
} else if (errno == EPIPE || errno == ECONNRESET || errno == ENETRESET) {
return MBEDTLS_ERR_NET_CONN_RESET;
} else {
WARNF("EzTlsSend error %s", strerror(errno));
// WARNF("EzTlsSend error %s", strerror(errno));
return MBEDTLS_ERR_NET_SEND_FAILED;
}
}
@ -89,6 +110,7 @@ int EzTlsFlush(struct EzTlsBio *bio, const unsigned char *buf, size_t len) {
}
static int EzTlsSend(void *ctx, const unsigned char *buf, size_t len) {
EzSanity();
int rc;
struct EzTlsBio *bio = ctx;
if (bio->c >= 0 && bio->c + len <= sizeof(bio->u)) {
@ -101,6 +123,7 @@ static int EzTlsSend(void *ctx, const unsigned char *buf, size_t len) {
}
static int EzTlsRecvImpl(void *ctx, unsigned char *p, size_t n, uint32_t o) {
EzSanity();
int r;
struct iovec v[2];
struct EzTlsBio *bio = ctx;
@ -116,7 +139,7 @@ static int EzTlsRecvImpl(void *ctx, unsigned char *p, size_t n, uint32_t o) {
v[1].iov_base = bio->t;
v[1].iov_len = sizeof(bio->t);
while ((r = readv(bio->fd, v, 2)) == -1) {
WARNF("tls read() error %s", strerror(errno));
// WARNF("tls read() error %s", strerror(errno));
if (errno == EINTR) {
return MBEDTLS_ERR_SSL_WANT_READ;
} else if (errno == EAGAIN) {
@ -132,60 +155,79 @@ static int EzTlsRecvImpl(void *ctx, unsigned char *p, size_t n, uint32_t o) {
}
static int EzTlsRecv(void *ctx, unsigned char *buf, size_t len, uint32_t tmo) {
EzSanity();
return EzTlsRecvImpl(ctx, buf, len, tmo);
}
void EzFd(int fd) {
EzSanity();
mbedtls_ssl_session_reset(&ezssl);
mbedtls_platform_zeroize(&ezbio, sizeof(ezbio));
ezbio.fd = fd;
}
void EzHandshake(void) {
EzSanity();
int rc;
while ((rc = mbedtls_ssl_handshake(&ezssl))) {
if (rc != MBEDTLS_ERR_SSL_WANT_READ) {
TlsDie("handshake failed", rc);
EzTlsDie("handshake failed", rc);
}
}
while ((rc = EzTlsFlush(&ezbio, 0, 0))) {
if (rc != MBEDTLS_ERR_SSL_WANT_READ) {
TlsDie("handshake flush failed", rc);
EzTlsDie("handshake flush failed", rc);
}
}
}
int EzHandshake2(void) {
EzSanity();
int rc;
while ((rc = mbedtls_ssl_handshake(&ezssl))) {
if (rc == MBEDTLS_ERR_NET_CONN_RESET) {
return rc;
} else if (rc != MBEDTLS_ERR_SSL_WANT_READ) {
TlsDie("handshake failed", rc);
EzTlsDie("handshake failed", rc);
}
}
while ((rc = EzTlsFlush(&ezbio, 0, 0))) {
if (rc == MBEDTLS_ERR_NET_CONN_RESET) {
return rc;
} else if (rc != MBEDTLS_ERR_SSL_WANT_READ) {
TlsDie("handshake flush failed", rc);
EzTlsDie("handshake flush failed", rc);
}
}
return 0;
}
void EzInitialize(void) {
xsigaction(SIGPIPE, SIG_IGN, 0, 0, 0);
ezconf.disable_compression = 1; /* TODO(jart): Why does it behave weirdly? */
unassert(!mytid);
mytid = gettid();
mbedtls_ssl_init(&ezssl);
mbedtls_ssl_config_init(&ezconf);
mbedtls_platform_zeroize(&ezbio, sizeof(ezbio));
ezconf.disable_compression = 1;
InitializeRng(&ezrng);
}
void EzSetup(char psk[32]) {
int rc;
EzSanity();
mbedtls_ssl_conf_rng(&ezconf, mbedtls_ctr_drbg_random, &ezrng);
if ((rc = mbedtls_ssl_conf_psk(&ezconf, psk, 32, "runit", 5)) ||
(rc = mbedtls_ssl_setup(&ezssl, &ezconf))) {
TlsDie("EzSetup", rc);
if ((rc = mbedtls_ssl_conf_psk(&ezconf, psk, 32, "runit", 5))) {
EzTlsDie("EzSetup mbedtls_ssl_conf_psk", rc);
}
if ((rc = mbedtls_ssl_setup(&ezssl, &ezconf))) {
EzTlsDie("EzSetup mbedtls_ssl_setup", rc);
}
mbedtls_ssl_set_bio(&ezssl, &ezbio, EzTlsSend, 0, EzTlsRecv);
}
void EzDestroy(void) {
if (!mytid) return;
EzSanity();
mbedtls_ssl_free(&ezssl);
mbedtls_ctr_drbg_free(&ezrng);
mbedtls_ssl_config_free(&ezconf);
mytid = 0;
}

View file

@ -13,16 +13,19 @@ struct EzTlsBio {
unsigned char u[1430];
};
extern struct EzTlsBio ezbio;
extern mbedtls_ssl_config ezconf;
extern mbedtls_ssl_context ezssl;
extern mbedtls_ctr_drbg_context ezrng;
extern _Thread_local struct EzTlsBio ezbio;
extern _Thread_local mbedtls_ssl_config ezconf;
extern _Thread_local mbedtls_ssl_context ezssl;
extern _Thread_local mbedtls_ctr_drbg_context ezrng;
void EzFd(int);
void EzSanity(void);
void EzDestroy(void);
void EzHandshake(void);
int EzHandshake2(void);
void EzSetup(char[32]);
void EzInitialize(void);
void EzTlsDie(const char *, int);
int EzTlsFlush(struct EzTlsBio *, const unsigned char *, size_t);
/*

View file

@ -27,11 +27,14 @@
#include "libc/fmt/conv.h"
#include "libc/fmt/libgen.h"
#include "libc/intrin/bits.h"
#include "libc/intrin/kprintf.h"
#include "libc/intrin/safemacros.internal.h"
#include "libc/limits.h"
#include "libc/log/check.h"
#include "libc/log/log.h"
#include "libc/macros.internal.h"
#include "libc/mem/gc.h"
#include "libc/mem/gc.internal.h"
#include "libc/mem/mem.h"
#include "libc/runtime/runtime.h"
#include "libc/sock/ipclassify.internal.h"
@ -50,6 +53,7 @@
#include "libc/time/time.h"
#include "libc/x/x.h"
#include "libc/x/xasprintf.h"
#include "libc/x/xsigaction.h"
#include "net/https/https.h"
#include "third_party/mbedtls/ssl.h"
#include "third_party/zlib/zlib.h"
@ -271,64 +275,74 @@ void RelayRequest(void) {
for (i = 0; i < have; i += rc) {
rc = mbedtls_ssl_write(&ezssl, buf + i, have - i);
if (rc <= 0) {
TlsDie("relay request failed", rc);
EzTlsDie("relay request failed", rc);
}
}
}
CHECK_NE(0, transferred);
rc = EzTlsFlush(&ezbio, 0, 0);
if (rc < 0) {
TlsDie("relay request failed to flush", rc);
EzTlsDie("relay request failed to flush", rc);
}
close(13);
}
bool Recv(unsigned char *p, size_t n) {
size_t i, rc;
bool Recv(char *p, int n) {
int i, rc;
for (i = 0; i < n; i += rc) {
do {
rc = mbedtls_ssl_read(&ezssl, p + i, n - i);
} while (rc == MBEDTLS_ERR_SSL_WANT_READ);
do rc = mbedtls_ssl_read(&ezssl, p + i, n - i);
while (rc == MBEDTLS_ERR_SSL_WANT_READ);
if (!rc) return false;
if (rc < 0) {
TlsDie("read response failed", rc);
}
if (rc < 0) EzTlsDie("read response failed", rc);
}
return true;
}
int ReadResponse(void) {
int res;
size_t n;
uint32_t size;
unsigned char b[512];
for (res = -1; res == -1;) {
if (!Recv(b, 5)) break;
CHECK_EQ(RUNITD_MAGIC, READ32BE(b), "%#.5s", b);
switch (b[4]) {
case kRunitExit:
if (!Recv(b, 1)) break;
if ((res = *b)) {
WARNF("%s on %s exited with %d", g_prog, g_hostname, res);
}
int exitcode;
for (;;) {
char msg[5];
if (!Recv(msg, 5)) {
WARNF("%s didn't report status of %s", g_hostname, g_prog);
exitcode = 200;
break;
}
if (READ32BE(msg) != RUNITD_MAGIC) {
WARNF("%s sent corrupted data stream after running %s", g_hostname,
g_prog);
exitcode = 201;
break;
}
if (msg[4] == kRunitExit) {
if (!Recv(msg, 1)) {
TruncatedMessage:
WARNF("%s sent truncated message running %s", g_hostname, g_prog);
exitcode = 202;
break;
case kRunitStderr:
if (!Recv(b, 4)) break;
size = READ32BE(b);
for (; size; size -= n) {
n = MIN(size, sizeof(b));
if (!Recv(b, n)) goto drop;
CHECK_EQ(n, write(2, b, n));
}
break;
default:
fprintf(stderr, "error: received invalid runit command\n");
_exit(1);
}
exitcode = *msg;
if (exitcode) {
WARNF("%s says %s exited with %d", g_hostname, g_prog, exitcode);
} else {
VERBOSEF("%s says %s exited with %d", g_hostname, g_prog, exitcode);
}
mbedtls_ssl_close_notify(&ezssl);
break;
} else if (msg[4] == kRunitStdout || msg[4] == kRunitStderr) {
if (!Recv(msg, 4)) goto TruncatedMessage;
int n = READ32BE(msg);
char *s = malloc(n);
if (!Recv(s, n)) goto TruncatedMessage;
write(2, s, n);
free(s);
} else {
WARNF("%s sent message with unknown command %d after running %s",
g_hostname, msg[4], g_prog);
exitcode = 203;
break;
}
}
drop:
close(g_sock);
return res;
return exitcode;
}
static inline bool IsElf(const char *p, size_t n) {
@ -340,23 +354,28 @@ static inline bool IsMachO(const char *p, size_t n) {
}
int RunOnHost(char *spec) {
int rc;
int err;
char *p;
for (p = spec; *p; ++p) {
if (*p == ':') *p = ' ';
}
CHECK_GE(sscanf(spec, "%100s %hu %hu", g_hostname, &g_runitdport, &g_sshport),
1);
int got =
sscanf(spec, "%100s %hu %hu", g_hostname, &g_runitdport, &g_sshport);
if (got < 1) {
kprintf("what on earth %#s -> %d\n", spec, got);
exit(1);
}
if (!strchr(g_hostname, '.')) strcat(g_hostname, ".test.");
DEBUGF("connecting to %s port %d", g_hostname, g_runitdport);
for (;;) {
Connect();
EzFd(g_sock);
if (!(rc = EzHandshake2())) {
break;
}
WARNF("got reset in handshake -0x%04x", rc);
err = EzHandshake2();
if (!err) break;
WARNF("handshake with %s:%d failed -0x%04x (%s)", //
g_hostname, g_runitdport, err, GetTlsError(err));
close(g_sock);
return 1;
}
RelayRequest();
return ReadResponse();
@ -454,6 +473,7 @@ int SpawnSubprocesses(int argc, char *argv[]) {
int main(int argc, char *argv[]) {
ShowCrashReports();
signal(SIGPIPE, SIG_IGN);
if (getenv("DEBUG")) {
__log_level = kLogDebug;
}

View file

@ -16,8 +16,12 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/assert.h"
#include "libc/atomic.h"
#include "libc/calls/calls.h"
#include "libc/calls/struct/rusage.h"
#include "libc/calls/struct/sigaction.h"
#include "libc/calls/struct/sigset.h"
#include "libc/calls/struct/stat.h"
#include "libc/calls/struct/timespec.h"
#include "libc/calls/struct/timeval.h"
@ -26,19 +30,27 @@
#include "libc/fmt/conv.h"
#include "libc/fmt/libgen.h"
#include "libc/intrin/bits.h"
#include "libc/intrin/kprintf.h"
#include "libc/log/appendresourcereport.internal.h"
#include "libc/log/check.h"
#include "libc/log/log.h"
#include "libc/macros.internal.h"
#include "libc/mem/gc.h"
#include "libc/mem/gc.internal.h"
#include "libc/mem/mem.h"
#include "libc/nexgen32e/crc32.h"
#include "libc/runtime/runtime.h"
#include "libc/runtime/syslib.internal.h"
#include "libc/sock/sock.h"
#include "libc/sock/struct/pollfd.h"
#include "libc/sock/struct/sockaddr.h"
#include "libc/stdio/append.h"
#include "libc/stdio/posix_spawn.h"
#include "libc/stdio/rand.h"
#include "libc/stdio/stdio.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/af.h"
#include "libc/sysv/consts/at.h"
#include "libc/sysv/consts/clock.h"
#include "libc/sysv/consts/ex.h"
#include "libc/sysv/consts/exit.h"
#include "libc/sysv/consts/f.h"
@ -48,15 +60,21 @@
#include "libc/sysv/consts/itimer.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/poll.h"
#include "libc/sysv/consts/posix.h"
#include "libc/sysv/consts/sa.h"
#include "libc/sysv/consts/sig.h"
#include "libc/sysv/consts/so.h"
#include "libc/sysv/consts/sock.h"
#include "libc/sysv/consts/sol.h"
#include "libc/sysv/consts/w.h"
#include "libc/temp.h"
#include "libc/thread/thread.h"
#include "libc/thread/thread2.h"
#include "libc/time/struct/tm.h"
#include "libc/time/time.h"
#include "libc/x/x.h"
#include "libc/x/xasprintf.h"
#include "libc/x/xsigaction.h"
#include "net/http/escape.h"
#include "net/https/https.h"
#include "third_party/getopt/getopt.internal.h"
#include "third_party/mbedtls/ssl.h"
@ -104,45 +122,64 @@
#define kLogFile "o/runitd.log"
#define kLogMaxBytes (2 * 1000 * 1000)
#define LOG_LEVEL_WARN 0
#define LOG_LEVEL_INFO 1
#define LOG_LEVEL_VERB 3
#define LOG_LEVEL_DEBU 3
#define DEBUF(FMT, ...) LOGF(DEBU, FMT, ##__VA_ARGS__)
#define VERBF(FMT, ...) LOGF(VERB, FMT, ##__VA_ARGS__)
#define INFOF(FMT, ...) LOGF(INFO, FMT, ##__VA_ARGS__)
#define WARNF(FMT, ...) LOGF(WARN, FMT, ##__VA_ARGS__)
#define LOGF(LVL, FMT, ...) \
do { \
if (g_log_level >= LOG_LEVEL_##LVL) { \
kprintf("%r" #LVL " %6P %'18T %s:%d " FMT "\n", __FILE__, __LINE__, \
##__VA_ARGS__); \
} \
} while (0)
struct Client {
int fd;
int pid;
int pipe[2];
pthread_t th;
uint32_t addrsize;
struct sockaddr_in addr;
bool once;
int zstatus;
z_stream zs;
struct {
size_t off;
size_t len;
size_t cap;
char *data;
} rbuf;
char *output;
char exepath[128];
char buf[32768];
};
char *g_psk;
int g_log_level;
bool use_ftrace;
bool use_strace;
char *g_exepath;
unsigned char g_buf[4096];
volatile bool g_interrupted;
char g_hostname[256];
int g_bogusfd, g_servfd;
atomic_bool g_interrupted;
struct sockaddr_in g_servaddr;
bool g_daemonize, g_sendready;
int g_timeout, g_bogusfd, g_servfd, g_clifd, g_exefd;
void OnInterrupt(int sig) {
g_interrupted = true;
}
void OnChildTerminated(int sig) {
int e, ws, pid;
sigset_t ss, oldss;
e = errno; // SIGCHLD can be called asynchronously
sigfillset(&ss);
sigdelset(&ss, SIGTERM);
sigprocmask(SIG_BLOCK, &ss, &oldss);
for (;;) {
if ((pid = waitpid(-1, &ws, WNOHANG)) != -1) {
if (pid) {
if (WIFEXITED(ws)) {
DEBUGF("worker %d exited with %d", pid, WEXITSTATUS(ws));
} else {
DEBUGF("worker %d terminated with %s", pid, strsignal(WTERMSIG(ws)));
}
} else {
break;
}
} else {
if (errno == EINTR) continue;
if (errno == ECHILD) break;
FATALF("waitpid failed in sigchld");
}
void Close(int *fd) {
if (*fd > 0) {
close(*fd);
*fd = -1; // poll ignores -1
}
sigprocmask(SIG_SETMASK, &oldss, 0);
errno = e;
}
wontreturn void ShowUsage(FILE *f, int rc) {
@ -151,9 +188,18 @@ wontreturn void ShowUsage(FILE *f, int rc) {
exit(rc);
}
char *DescribeAddress(struct sockaddr_in *addr) {
static _Thread_local char res[64];
char ip4buf[64];
sprintf(res, "%s:%hu",
inet_ntop(addr->sin_family, &addr->sin_addr.s_addr, ip4buf,
sizeof(ip4buf)),
ntohs(addr->sin_port));
return res;
}
void GetOpts(int argc, char *argv[]) {
int opt;
g_timeout = RUNITD_TIMEOUT_MS;
g_servaddr.sin_family = AF_INET;
g_servaddr.sin_port = htons(RUNITD_PORT);
g_servaddr.sin_addr.s_addr = INADDR_ANY;
@ -166,10 +212,10 @@ void GetOpts(int argc, char *argv[]) {
use_strace = true;
break;
case 'q':
--__log_level;
--g_log_level;
break;
case 'v':
++__log_level;
++g_log_level;
break;
case 'd':
g_daemonize = true;
@ -178,56 +224,44 @@ void GetOpts(int argc, char *argv[]) {
g_sendready = true;
break;
case 't':
g_timeout = atoi(optarg);
break;
case 'p':
CHECK_NE(0xFFFF, (g_servaddr.sin_port = htons(parseport(optarg))));
g_servaddr.sin_port = htons(parseport(optarg));
break;
case 'l':
CHECK_EQ(1, inet_pton(AF_INET, optarg, &g_servaddr.sin_addr));
inet_pton(AF_INET, optarg, &g_servaddr.sin_addr);
break;
case 'h':
ShowUsage(stdout, EXIT_SUCCESS);
__builtin_unreachable();
default:
ShowUsage(stderr, EX_USAGE);
__builtin_unreachable();
}
}
}
__wur char *DescribeAddress(struct sockaddr_in *addr) {
char ip4buf[16];
return xasprintf("%s:%hu",
inet_ntop(addr->sin_family, &addr->sin_addr.s_addr, ip4buf,
sizeof(ip4buf)),
ntohs(addr->sin_port));
}
void StartTcpServer(void) {
int yes = true;
uint32_t asize;
/*
* TODO: How can we make close(serversocket) on Windows go fast?
* That way we can put back SOCK_CLOEXEC.
*/
CHECK_NE(-1, (g_servfd =
socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, IPPROTO_TCP)));
struct timeval timeo = {30};
g_servfd = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, IPPROTO_TCP);
if (g_servfd == -1) {
fprintf(stderr, program_invocation_short_name,
": socket failed: ", strerror(errno), "\n", NULL);
exit(1);
}
struct timeval timeo = {DEATH_CLOCK_SECONDS / 10};
setsockopt(g_servfd, SOL_SOCKET, SO_RCVTIMEO, &timeo, sizeof(timeo));
setsockopt(g_servfd, SOL_SOCKET, SO_SNDTIMEO, &timeo, sizeof(timeo));
LOGIFNEG1(setsockopt(g_servfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)));
setsockopt(g_servfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));
if (bind(g_servfd, (struct sockaddr *)&g_servaddr, sizeof(g_servaddr)) ==
-1) {
FATALF("bind failed %m");
fprintf(stderr, program_invocation_short_name,
": bind failed: ", strerror(errno), "\n", NULL);
exit(1);
}
CHECK_NE(-1, listen(g_servfd, 10));
unassert(!listen(g_servfd, 10));
asize = sizeof(g_servaddr);
CHECK_NE(-1, getsockname(g_servfd, (struct sockaddr *)&g_servaddr, &asize));
INFOF("%s:%s", "listening on tcp", _gc(DescribeAddress(&g_servaddr)));
unassert(!getsockname(g_servfd, (struct sockaddr *)&g_servaddr, &asize));
INFOF("listening on tcp:%s", DescribeAddress(&g_servaddr));
if (g_sendready) {
printf("ready %hu\n", ntohs(g_servaddr.sin_port));
fflush(stdout);
@ -237,22 +271,28 @@ void StartTcpServer(void) {
}
void SendExitMessage(int rc) {
EzSanity();
int res;
unsigned char msg[4 + 1 + 1];
DEBUF("SendExitMessage");
msg[0 + 0] = (RUNITD_MAGIC & 0xff000000) >> 030;
msg[0 + 1] = (RUNITD_MAGIC & 0x00ff0000) >> 020;
msg[0 + 2] = (RUNITD_MAGIC & 0x0000ff00) >> 010;
msg[0 + 3] = (RUNITD_MAGIC & 0x000000ff) >> 000;
msg[4] = kRunitExit;
msg[5] = rc;
INFOF("mbedtls_ssl_write");
CHECK_EQ(sizeof(msg), mbedtls_ssl_write(&ezssl, msg, sizeof(msg)));
CHECK_EQ(0, EzTlsFlush(&ezbio, 0, 0));
DEBUF("mbedtls_ssl_write");
if (sizeof(msg) != (res = mbedtls_ssl_write(&ezssl, msg, sizeof(msg)))) {
EzTlsDie("SendExitMessage mbedtls_ssl_write failed", res);
}
if ((res = EzTlsFlush(&ezbio, 0, 0))) {
EzTlsDie("SendExitMessage EzTlsFlush failed", res);
}
}
void SendOutputFragmentMessage(enum RunitCommand kind, unsigned char *buf,
size_t size) {
void SendOutputFragmentMessage(enum RunitCommand kind, char *buf, size_t size) {
EzSanity();
ssize_t rc;
size_t sent;
unsigned char msg[4 + 1 + 4];
msg[0 + 0] = (RUNITD_MAGIC & 0xff000000) >> 030;
msg[0 + 1] = (RUNITD_MAGIC & 0x00ff0000) >> 020;
@ -263,309 +303,451 @@ void SendOutputFragmentMessage(enum RunitCommand kind, unsigned char *buf,
msg[5 + 1] = (size & 0x00ff0000) >> 020;
msg[5 + 2] = (size & 0x0000ff00) >> 010;
msg[5 + 3] = (size & 0x000000ff) >> 000;
INFOF("mbedtls_ssl_write");
CHECK_EQ(sizeof(msg), mbedtls_ssl_write(&ezssl, msg, sizeof(msg)));
while (size) {
CHECK_NE(-1, (rc = mbedtls_ssl_write(&ezssl, buf, size)));
CHECK_LE((sent = (size_t)rc), size);
size -= sent;
buf += sent;
DEBUF("mbedtls_ssl_write");
if (sizeof(msg) != (rc = mbedtls_ssl_write(&ezssl, msg, sizeof(msg)))) {
EzTlsDie("SendOutputFragmentMessage mbedtls_ssl_write failed", rc);
}
while (size) {
if ((rc = mbedtls_ssl_write(&ezssl, buf, size)) <= 0) {
EzTlsDie("SendOutputFragmentMessage mbedtls_ssl_write #2 failed", rc);
}
size -= rc;
buf += rc;
}
if ((rc = EzTlsFlush(&ezbio, 0, 0))) {
EzTlsDie("SendOutputFragmentMessage EzTlsFlush failed", rc);
}
CHECK_EQ(0, EzTlsFlush(&ezbio, 0, 0));
}
void Recv(void *output, size_t outputsize) {
void Recv(struct Client *client, void *output, size_t outputsize) {
EzSanity();
ssize_t chunk, received, totalgot;
static bool once;
static int zstatus;
static char buf[32768];
static z_stream zs;
static struct {
size_t off;
size_t len;
size_t cap;
char *data;
} rbuf;
if (!once) {
CHECK_EQ(Z_OK, inflateInit(&zs));
once = true;
if (!client->once) {
unassert(Z_OK == inflateInit(&client->zs));
client->once = true;
}
totalgot = 0;
for (;;) {
if (rbuf.len >= outputsize) {
memcpy(output, rbuf.data + rbuf.off, outputsize);
rbuf.len -= outputsize;
rbuf.off += outputsize;
if (client->rbuf.len >= outputsize) {
memcpy(output, client->rbuf.data + client->rbuf.off, outputsize);
client->rbuf.len -= outputsize;
client->rbuf.off += outputsize;
// trim dymanic buffer once it empties
if (!rbuf.len) {
rbuf.off = 0;
rbuf.cap = 4096;
rbuf.data = realloc(rbuf.data, rbuf.cap);
if (!client->rbuf.len) {
client->rbuf.off = 0;
client->rbuf.cap = 4096;
client->rbuf.data = realloc(client->rbuf.data, client->rbuf.cap);
}
return;
}
if (zstatus == Z_STREAM_END) {
close(g_clifd);
FATALF("recv zlib unexpected eof");
if (client->zstatus == Z_STREAM_END) {
WARNF("recv zlib unexpected eof");
pthread_exit(0);
}
// get another fixed-size data packet from network
// pass along error conditions to caller
// pass along eof condition to zlib
received = mbedtls_ssl_read(&ezssl, buf, sizeof(buf));
received = mbedtls_ssl_read(&ezssl, client->buf, sizeof(client->buf));
if (!received) {
close(g_clifd);
TlsDie("got unexpected eof", received);
EzTlsDie("got unexpected eof", received);
}
if (received < 0) {
close(g_clifd);
TlsDie("read failed", received);
EzTlsDie("read failed", received);
}
totalgot += received;
// decompress packet completely
// into a dynamical size buffer
zs.avail_in = received;
zs.next_in = (unsigned char *)buf;
CHECK_EQ(Z_OK, zstatus);
client->zs.avail_in = received;
client->zs.next_in = (unsigned char *)client->buf;
unassert(Z_OK == client->zstatus);
do {
// make sure we have a reasonable capacity for zlib output
if (rbuf.cap - (rbuf.off + rbuf.len) < sizeof(buf)) {
rbuf.cap += sizeof(buf);
rbuf.data = realloc(rbuf.data, rbuf.cap);
if (client->rbuf.cap - (client->rbuf.off + client->rbuf.len) <
sizeof(client->buf)) {
client->rbuf.cap += sizeof(client->buf);
client->rbuf.data = realloc(client->rbuf.data, client->rbuf.cap);
}
// inflate packet, which naturally can be much larger
// permit zlib no delay flushes that come from sender
zs.next_out = (unsigned char *)rbuf.data + (rbuf.off + rbuf.len);
zs.avail_out = chunk = rbuf.cap - (rbuf.off + rbuf.len);
zstatus = inflate(&zs, Z_SYNC_FLUSH);
CHECK_NE(Z_STREAM_ERROR, zstatus);
switch (zstatus) {
client->zs.next_out = (unsigned char *)client->rbuf.data +
(client->rbuf.off + client->rbuf.len);
client->zs.avail_out = chunk =
client->rbuf.cap - (client->rbuf.off + client->rbuf.len);
client->zstatus = inflate(&client->zs, Z_SYNC_FLUSH);
unassert(Z_STREAM_ERROR != client->zstatus);
switch (client->zstatus) {
case Z_NEED_DICT:
WARNF("tls recv Z_NEED_DICT %ld total %ld", received, totalgot);
exit(1);
pthread_exit(0);
case Z_DATA_ERROR:
WARNF("tls recv Z_DATA_ERROR %ld total %ld", received, totalgot);
exit(1);
pthread_exit(0);
case Z_MEM_ERROR:
WARNF("tls recv Z_MEM_ERROR %ld total %ld", received, totalgot);
exit(1);
pthread_exit(0);
case Z_BUF_ERROR:
zstatus = Z_OK; // harmless? nothing for inflate to do
break; // it probably just our wraparound eof
client->zstatus = Z_OK; // harmless? nothing for inflate to do
break; // it probably just our wraparound eof
default:
rbuf.len += chunk - zs.avail_out;
client->rbuf.len += chunk - client->zs.avail_out;
break;
}
} while (!zs.avail_out);
} while (!client->zs.avail_out);
}
}
void HandleClient(void) {
ssize_t got;
void SendProgramOutut(struct Client *client) {
if (client->output) {
SendOutputFragmentMessage(kRunitStderr, client->output,
appendz(client->output).i);
}
}
void PrintProgramOutput(struct Client *client) {
if (client->output) {
char *p = client->output;
size_t z = appendz(p).i;
if ((p = IndentLines(p, z, &z, 2))) {
fwrite(p, 1, z, stderr);
free(p);
}
}
}
void FreeClient(struct Client *client) {
DEBUF("FreeClient");
if (client->pid) {
kill(client->pid, SIGHUP);
waitpid(client->pid, 0, 0);
}
Close(&client->fd);
if (*client->exepath) {
unlink(client->exepath);
}
if (client->once) {
inflateEnd(&client->zs);
}
EzDestroy();
free(client->rbuf.data);
free(client->output);
free(client);
VERBF("---------------");
}
void *ClientWorker(void *arg) {
uint32_t crc;
sigset_t sigmask;
struct sockaddr_in addr;
struct timespec now, deadline;
int events, wstatus;
struct Client *client = arg;
uint32_t namesize, filesize;
char *addrstr, *exename, *exe;
unsigned char msg[4 + 1 + 4 + 4 + 4];
uint32_t addrsize, namesize, filesize;
int events, exitcode, wstatus, child, pipefds[2];
/* read request to run program */
addrsize = sizeof(addr);
INFOF("accept");
do {
g_clifd =
accept4(g_servfd, (struct sockaddr *)&addr, &addrsize, SOCK_CLOEXEC);
} while (g_clifd == -1 && errno == EAGAIN);
CHECK_NE(-1, g_clifd);
if (fork()) {
close(g_clifd);
return;
}
EzFd(g_clifd);
INFOF("EzHandshake");
SetupPresharedKeySsl(MBEDTLS_SSL_IS_SERVER, g_psk);
defer(FreeClient, client);
// read request to run program
EzFd(client->fd);
DEBUF("EzHandshake");
EzHandshake();
addrstr = _gc(DescribeAddress(&addr));
DEBUGF("%s %s %s", _gc(DescribeAddress(&g_servaddr)), "accepted", addrstr);
addrstr = DescribeAddress(&client->addr);
DEBUF("%s %s %s", DescribeAddress(&g_servaddr), "accepted", addrstr);
Recv(msg, sizeof(msg));
CHECK_EQ(RUNITD_MAGIC, READ32BE(msg));
CHECK_EQ(kRunitExecute, msg[4]);
// get the executable
Recv(client, msg, sizeof(msg));
if (READ32BE(msg) != RUNITD_MAGIC) {
WARNF("%s magic mismatch!", addrstr);
pthread_exit(0);
}
if (msg[4] != kRunitExecute) {
WARNF("%s unknown command!", addrstr);
pthread_exit(0);
}
namesize = READ32BE(msg + 5);
filesize = READ32BE(msg + 9);
crc = READ32BE(msg + 13);
exename = _gc(calloc(1, namesize + 1));
Recv(exename, namesize);
g_exepath = _gc(xasprintf("o/%d.%s", getpid(), basename(exename)));
INFOF("%s asked we run %`'s (%,u bytes @ %`'s)", addrstr, exename, filesize,
g_exepath);
exe = malloc(filesize);
Recv(exe, filesize);
exename = gc(calloc(1, namesize + 1));
Recv(client, exename, namesize);
INFOF("%s sent %#s (%'u bytes @ %#s)", addrstr, exename, filesize,
client->exepath);
exe = gc(malloc(filesize));
Recv(client, exe, filesize);
if (crc32_z(0, exe, filesize) != crc) {
FATALF("%s crc mismatch! %`'s", addrstr, exename);
WARNF("%s crc mismatch! %#s", addrstr, exename);
pthread_exit(0);
}
CHECK_NE(-1, (g_exefd = creat(g_exepath, 0700)));
LOGIFNEG1(ftruncate(g_exefd, filesize));
CHECK_NE(-1, xwrite(g_exefd, exe, filesize));
LOGIFNEG1(close(g_exefd));
/* run program, tee'ing stderr to both log and client */
DEBUGF("spawning %s", exename);
// create the executable file
// if another thread vforks while we're writing it then a race
// condition can happen, where etxtbsy is raised by our execve
// we're using o_cloexec so it's guaranteed to fix itself fast
// thus we use an optimistic approach to avoid expensive locks
sprintf(client->exepath, "o/%s.XXXXXX.com", basename(exename));
int exefd = openatemp(AT_FDCWD, client->exepath, 4, O_CLOEXEC, 0700);
ftruncate(exefd, filesize);
if (write(exefd, exe, filesize) != filesize) {
WARNF("%s failed to write %#s", addrstr, exename);
close(exefd);
pthread_exit(0);
}
if (close(exefd)) {
WARNF("%s failed to close %#s", addrstr, exename);
pthread_exit(0);
}
// do the args
int i = 0;
char *args[8] = {0};
if (!IsXnuSilicon()) {
exe = client->exepath;
} else {
exe = "ape-m1.com";
args[i++] = (char *)exe;
args[i++] = "-";
args[i++] = client->exepath;
}
args[i++] = client->exepath;
if (use_strace) args[i++] = "--strace";
if (use_ftrace) args[i++] = "--ftrace";
// run program, tee'ing stderr to both log and client
DEBUF("spawning %s", client->exepath);
sigemptyset(&sigmask);
sigaddset(&sigmask, SIGINT);
sigaddset(&sigmask, SIGQUIT);
sigaddset(&sigmask, SIGCHLD);
sigprocmask(SIG_BLOCK, &sigmask, 0);
CHECK_NE(-1, pipe2(pipefds, O_CLOEXEC));
CHECK_NE(-1, (child = fork()));
if (!child) {
dup2(g_bogusfd, 0);
dup2(pipefds[1], 1);
dup2(pipefds[1], 2);
sigemptyset(&sigmask);
sigprocmask(SIG_SETMASK, &sigmask, 0);
int i = 0;
const char *exe;
char *args[8] = {0};
if (!IsXnuSilicon()) {
exe = g_exepath;
} else {
exe = "ape-m1.com";
args[i++] = (char *)exe;
args[i++] = "-";
args[i++] = g_exepath;
// spawn the program
int etxtbsy_tries = 0;
RetryOnEtxtbsyRaceCondition:
if (etxtbsy_tries++) {
if (etxtbsy_tries == 24) { // ~30 seconds
WARNF("%s failed to spawn on %s due because either (1) the ETXTBSY race "
"condition kept happening or (2) the program in question actually "
"is crashing with SIGVTALRM, without printing anything to out/err!",
exename, g_hostname);
pthread_exit(0);
}
if (usleep(1u << etxtbsy_tries)) {
INFOF("interrupted exponential spawn backoff");
pthread_exit(0);
}
args[i++] = g_exepath;
if (use_strace) args[i++] = "--strace";
if (use_ftrace) args[i++] = "--ftrace";
execvp(exe, args);
_Exit(127);
}
signal(SIGINT, SIG_IGN);
signal(SIGQUIT, SIG_IGN);
close(pipefds[1]);
DEBUGF("communicating %s[%d]", exename, child);
deadline =
errno_t err;
posix_spawnattr_t spawnattr;
posix_spawn_file_actions_t spawnfila;
sigemptyset(&sigmask);
pipe2(client->pipe, O_CLOEXEC);
posix_spawnattr_init(&spawnattr);
posix_spawnattr_setflags(&spawnattr, POSIX_SPAWN_SETPGROUP);
posix_spawnattr_setsigmask(&spawnattr, &sigmask);
posix_spawn_file_actions_init(&spawnfila);
posix_spawn_file_actions_adddup2(&spawnfila, g_bogusfd, 0);
posix_spawn_file_actions_adddup2(&spawnfila, client->pipe[1], 1);
posix_spawn_file_actions_adddup2(&spawnfila, client->pipe[1], 2);
err = posix_spawn(&client->pid, exe, &spawnfila, &spawnattr, args, environ);
if (err) {
Close(&client->pipe[1]);
Close(&client->pipe[0]);
if (err == ETXTBSY) {
goto RetryOnEtxtbsyRaceCondition;
}
WARNF("%s failed to spawn on %s due to %s", exename, g_hostname,
strerror(err));
pthread_exit(0);
}
posix_spawn_file_actions_destroy(&spawnfila);
posix_spawnattr_destroy(&spawnattr);
Close(&client->pipe[1]);
DEBUF("communicating %s[%d]", exename, client->pid);
struct timespec deadline =
timespec_add(timespec_real(), timespec_fromseconds(DEATH_CLOCK_SECONDS));
for (;;) {
now = timespec_real();
if (timespec_cmp(now, deadline) >= 0) {
WARNF("%s worker timed out", exename);
if (g_interrupted) {
WARNF("killing %d %s and hanging up %d due to interrupt", client->fd,
exename, client->pid);
HangupClientAndTerminateJob:
SendProgramOutut(client);
mbedtls_ssl_close_notify(&ezssl);
TerminateJob:
LOGIFNEG1(kill(child, 9));
LOGIFNEG1(waitpid(child, 0, 0));
LOGIFNEG1(close(g_clifd));
LOGIFNEG1(close(pipefds[0]));
LOGIFNEG1(unlink(g_exepath));
_exit(1);
PrintProgramOutput(client);
pthread_exit(0);
}
struct timespec now = timespec_real();
if (timespec_cmp(now, deadline) >= 0) {
WARNF("killing %s (pid %d) which timed out after %d seconds", exename,
client->pid, DEATH_CLOCK_SECONDS);
goto HangupClientAndTerminateJob;
}
struct pollfd fds[2];
fds[0].fd = g_clifd;
fds[0].fd = client->fd;
fds[0].events = POLLIN;
fds[1].fd = pipefds[0];
fds[1].fd = client->pipe[0];
fds[1].events = POLLIN;
int waitms = timespec_tomillis(timespec_sub(deadline, now));
INFOF("polling for %d ms", waitms);
events = poll(fds, ARRAYLEN(fds), waitms);
CHECK_NE(-1, events); // EINTR shouldn't be possible
events = poll(fds, ARRAYLEN(fds),
timespec_tomillis(timespec_sub(deadline, now)));
if (events == -1) {
if (errno == EINTR) {
INFOF("poll interrupted");
continue;
} else {
WARNF("killing %d %s and hanging up %d because poll failed", client->fd,
exename, client->pid);
goto HangupClientAndTerminateJob;
}
}
if (events) {
if (fds[0].revents) {
int received;
char buf[512];
INFOF("mbedtls_ssl_read");
received = mbedtls_ssl_read(&ezssl, buf, sizeof(buf));
if (!received) {
WARNF("%s client disconnected so killing worker %d", exename, child);
WARNF("%s client disconnected so killing worker %d", exename,
client->pid);
goto TerminateJob;
}
if (received > 0) {
WARNF("%s client sent %d unexpected bytes so killing job", exename,
received);
goto TerminateJob;
goto HangupClientAndTerminateJob;
}
if (received != MBEDTLS_ERR_SSL_WANT_READ) {
WARNF("%s client ssl read failed with -0x%04x so killing job",
exename, -received);
goto TerminateJob;
if (received == MBEDTLS_ERR_SSL_WANT_READ) { // EAGAIN SO_RCVTIMEO
WARNF("%s (pid %d) is taking a really long time", exename,
client->pid);
continue;
}
INFOF("got spurious ssl data");
WARNF("client ssl read failed with -0x%04x (%s) so killing %s",
-received, GetTlsError(received), exename);
goto TerminateJob;
}
if (fds[1].revents) {
INFOF("read");
got = read(pipefds[0], g_buf, sizeof(g_buf));
CHECK_NE(-1, got); // EINTR shouldn't be possible
char buf[512];
ssize_t got = read(client->pipe[0], buf, sizeof(buf));
if (got == -1) {
WARNF("got %s reading %s output", strerror(errno), exename);
goto HangupClientAndTerminateJob;
}
if (!got) {
LOGIFNEG1(close(pipefds[0]));
VERBF("got eof reading %s output", exename);
Close(&client->pipe[0]);
break;
}
fwrite(g_buf, got, 1, stderr);
SendOutputFragmentMessage(kRunitStderr, g_buf, got);
DEBUF("got %ld bytes reading %s output", got, exename);
appendd(&client->output, buf, got);
}
}
}
INFOF("waitpid");
CHECK_NE(-1, waitpid(child, &wstatus, 0)); // EINTR shouldn't be possible
WaitAgain:
DEBUF("waitpid");
struct rusage rusage;
int wrc = wait4(client->pid, &wstatus, 0, &rusage);
if (wrc == -1) {
if (errno == EINTR) {
WARNF("waitpid interrupted; killing %s pid %d", exename, client->pid);
kill(client->pid, SIGINT);
goto WaitAgain;
}
WARNF("waitpid failed %m");
client->pid = 0;
goto HangupClientAndTerminateJob;
}
client->pid = 0;
int exitcode;
if (WIFEXITED(wstatus)) {
if (WEXITSTATUS(wstatus)) {
WARNF("%s exited with %d", exename, WEXITSTATUS(wstatus));
WARNF("%s on %s exited with %d", exename, g_hostname,
WEXITSTATUS(wstatus));
appendf(&client->output, "------ %s %s $?=%d (0x%08x) ------\n",
g_hostname, exename, WEXITSTATUS(wstatus), wstatus);
} else {
VERBOSEF("%s exited with %d", exename, WEXITSTATUS(wstatus));
VERBF("%s on %s exited with %d", exename, g_hostname,
WEXITSTATUS(wstatus));
}
exitcode = WEXITSTATUS(wstatus);
} else {
WARNF("%s terminated with %s", exename, strsignal(WTERMSIG(wstatus)));
} else if (WIFSIGNALED(wstatus)) {
if (WTERMSIG(wstatus) == SIGVTALRM && !client->output) {
free(client->output);
client->output = 0;
goto RetryOnEtxtbsyRaceCondition;
}
WARNF("%s on %s terminated with %s", exename, g_hostname,
strsignal(WTERMSIG(wstatus)));
exitcode = 128 + WTERMSIG(wstatus);
appendf(&client->output, "------ %s %s $?=%s (0x%08x) ------\n", g_hostname,
exename, strsignal(WTERMSIG(wstatus)), wstatus);
} else {
WARNF("%s on %s died with wait status 0x%08x", exename, g_hostname,
wstatus);
exitcode = 127;
}
LOGIFNEG1(unlink(g_exepath));
if (wstatus) {
AppendResourceReport(&client->output, &rusage, "\n");
PrintProgramOutput(client);
}
SendProgramOutut(client);
SendExitMessage(exitcode);
INFOF("mbedtls_ssl_close_notify");
mbedtls_ssl_close_notify(&ezssl);
LOGIFNEG1(close(g_clifd));
_exit(0);
if (etxtbsy_tries) {
VERBF("encountered %d ETXTBSY race conditions spawning %s", etxtbsy_tries,
exename);
}
pthread_exit(0);
}
int Poll(void) {
int i, wait, evcount;
struct pollfd fds[1];
TryAgain:
if (g_interrupted) return 0;
fds[0].fd = g_servfd;
fds[0].events = POLLIN | POLLERR | POLLHUP;
wait = MIN(1000, g_timeout);
evcount = poll(fds, ARRAYLEN(fds), wait);
if (!evcount) g_timeout -= wait;
if (evcount == -1 && errno == EINTR) goto TryAgain;
CHECK_NE(-1, evcount);
for (i = 0; i < evcount; ++i) {
CHECK(fds[i].revents & POLLIN);
HandleClient();
void HandleClient(void) {
struct Client *client;
client = calloc(1, sizeof(struct Client));
client->addrsize = sizeof(client->addr);
for (;;) {
if (g_interrupted) {
free(client);
return;
}
// poll() because we use SA_RESTART and accept() is @restartable
if (poll(&(struct pollfd){g_servfd, POLLIN}, 1, -1) > 0) {
client->fd = accept4(g_servfd, (struct sockaddr *)&client->addr,
&client->addrsize, SOCK_CLOEXEC);
if (client->fd != -1) {
VERBF("accepted client fd %d", client->fd);
break;
} else if (errno != EINTR && errno != EAGAIN) {
WARNF("accept4 failed %m");
}
} else if (errno != EINTR && errno != EAGAIN) {
WARNF("poll failed %m");
}
}
/* manually do this because of nt */
while (waitpid(-1, NULL, WNOHANG) > 0) donothing;
return evcount;
sigset_t mask;
pthread_attr_t attr;
sigfillset(&mask);
pthread_attr_init(&attr);
pthread_attr_setsigmask_np(&attr, &mask);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_create(&client->th, &attr, ClientWorker, client);
pthread_attr_destroy(&attr);
}
int Serve(void) {
sigset_t mask;
StartTcpServer();
sigaction(SIGINT, (&(struct sigaction){.sa_handler = OnInterrupt}), 0);
sigaction(SIGCHLD,
(&(struct sigaction){.sa_handler = OnChildTerminated,
.sa_flags = SA_RESTART}),
0);
for (;;) {
if (!Poll() && (!g_timeout || g_interrupted)) break;
sigemptyset(&mask);
sigaddset(&mask, SIGCHLD);
signal(SIGINT, OnInterrupt);
sigprocmask(SIG_BLOCK, &mask, 0);
while (!g_interrupted) {
HandleClient();
}
if (g_interrupted) {
WARNF("got ctrl-c, shutting down...");
}
WARNF("server exiting");
close(g_servfd);
if (!g_timeout) {
INFOF("timeout expired, shutting down");
} else {
INFOF("got ctrl-c, shutting down");
}
return 0;
}
void Daemonize(void) {
VERBF("Daemonize");
struct stat st;
if (fork() > 0) _exit(0);
setsid();
@ -579,19 +761,29 @@ void Daemonize(void) {
}
int main(int argc, char *argv[]) {
int i;
SetupPresharedKeySsl(MBEDTLS_SSL_IS_SERVER, GetRunitPsk());
__log_level = kLogWarn;
#if IsModeDbg()
ShowCrashReports();
#endif
GetOpts(argc, argv);
for (i = 3; i < 16; ++i) close(i);
g_psk = GetRunitPsk();
signal(SIGPIPE, SIG_IGN);
setenv("TZ", "PST", true);
gethostname(g_hostname, sizeof(g_hostname));
for (int i = 3; i < 16; ++i) close(i);
errno = 0;
// poll()'ing /dev/null stdin file descriptor on xnu returns POLLNVAL?!
if (IsWindows()) {
CHECK_EQ(3, (g_bogusfd = open("/dev/null", O_RDONLY | O_CLOEXEC)));
g_bogusfd = open("/dev/null", O_RDONLY | O_CLOEXEC);
} else {
CHECK_EQ(3, (g_bogusfd = open("/dev/zero", O_RDONLY | O_CLOEXEC)));
g_bogusfd = open("/dev/zero", O_RDONLY | O_CLOEXEC);
}
if (!isdirectory("o")) CHECK_NE(-1, mkdir("o", 0700));
if (g_daemonize) Daemonize();
return Serve();
mkdir("o", 0700);
Serve();
free(g_psk);
#if IsModeDbg()
void CheckForMemoryLeaks(void);
CheckForMemoryLeaks();
#endif
pthread_exit(0);
}

View file

@ -7267,7 +7267,7 @@ function unix.tiocgwinsz(fd) end
--- This creates a secure temporary file inside `$TMPDIR`. If it isn't
--- defined, then `/tmp` is used on UNIX and GetTempPath() is used on
--- the New Technology. This resolution of `$TMPDIR` happens once in a
--- ctor, which is copied to the `kTmpDir` global.
--- ctor, which is copied to the `kTmpPath` global.
---
--- Once close() is called, the returned file is guaranteed to be
--- deleted automatically. On UNIX the file is unlink()'d before this

View file

@ -4711,7 +4711,7 @@ UNIX MODULE
This creates a secure temporary file inside `$TMPDIR`. If it isn't
defined, then `/tmp` is used on UNIX and GetTempPath() is used on
the New Technology. This resolution of `$TMPDIR` happens once in a
ctor, which is copied to the `kTmpDir` global.
ctor, which is copied to the `kTmpPath` global.
Once close() is called, the returned file is guaranteed to be
deleted automatically. On UNIX the file is unlink()'d before this

View file

@ -7,7 +7,6 @@ o/$(MODE)/tool: \
o/$(MODE)/tool/build \
o/$(MODE)/tool/curl \
o/$(MODE)/tool/decode \
o/$(MODE)/tool/hello \
o/$(MODE)/tool/lambda \
o/$(MODE)/tool/net \
o/$(MODE)/tool/plinko \