mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-01-31 11:37:35 +00:00
c776a32f75
This change might cause ABI breakages for /opt/cosmos. It's needed to help us better conform to header declaration practices.
1581 lines
43 KiB
C
1581 lines
43 KiB
C
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
|
|
│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
|
|
╞══════════════════════════════════════════════════════════════════════════════╡
|
|
│ Copyright 2021 Justine Alexandra Roberts Tunney │
|
|
│ │
|
|
│ Permission to use, copy, modify, and/or distribute this software for │
|
|
│ any purpose with or without fee is hereby granted, provided that the │
|
|
│ above copyright notice and this permission notice appear in all copies. │
|
|
│ │
|
|
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │
|
|
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │
|
|
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │
|
|
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │
|
|
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │
|
|
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │
|
|
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
|
│ PERFORMANCE OF THIS SOFTWARE. │
|
|
╚─────────────────────────────────────────────────────────────────────────────*/
|
|
#include "libc/calls/calls.h"
|
|
#include "libc/calls/copyfile.h"
|
|
#include "libc/calls/struct/itimerval.h"
|
|
#include "libc/calls/struct/rlimit.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/timeval.h"
|
|
#include "libc/calls/struct/winsize.h"
|
|
#include "libc/calls/termios.h"
|
|
#include "libc/dce.h"
|
|
#include "libc/errno.h"
|
|
#include "libc/fmt/conv.h"
|
|
#include "libc/fmt/itoa.h"
|
|
#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"
|
|
#include "libc/log/color.internal.h"
|
|
#include "libc/log/log.h"
|
|
#include "libc/macros.internal.h"
|
|
#include "libc/math.h"
|
|
#include "libc/mem/alg.h"
|
|
#include "libc/mem/gc.internal.h"
|
|
#include "libc/mem/mem.h"
|
|
#include "libc/nexgen32e/kcpuids.h"
|
|
#include "libc/nexgen32e/x86feature.h"
|
|
#include "libc/nexgen32e/x86info.h"
|
|
#include "libc/runtime/runtime.h"
|
|
#include "libc/stdio/append.h"
|
|
#include "libc/stdio/stdio.h"
|
|
#include "libc/str/str.h"
|
|
#include "libc/sysv/consts/auxv.h"
|
|
#include "libc/sysv/consts/clock.h"
|
|
#include "libc/sysv/consts/itimer.h"
|
|
#include "libc/sysv/consts/madv.h"
|
|
#include "libc/sysv/consts/o.h"
|
|
#include "libc/sysv/consts/rlimit.h"
|
|
#include "libc/sysv/consts/s.h"
|
|
#include "libc/sysv/consts/sa.h"
|
|
#include "libc/sysv/consts/sig.h"
|
|
#include "libc/sysv/consts/termios.h"
|
|
#include "libc/time/time.h"
|
|
#include "libc/x/x.h"
|
|
#include "third_party/getopt/getopt.internal.h"
|
|
|
|
#ifndef NDEBUG
|
|
__static_yoink("zipos");
|
|
#endif
|
|
|
|
#define MANUAL \
|
|
"\
|
|
SYNOPSIS\n\
|
|
\n\
|
|
compile.com [FLAGS] COMMAND [ARGS...]\n\
|
|
\n\
|
|
OVERVIEW\n\
|
|
\n\
|
|
Build Command Harness\n\
|
|
\n\
|
|
DESCRIPTION\n\
|
|
\n\
|
|
This is a generic command wrapper, e.g.\n\
|
|
\n\
|
|
compile.com gcc -o program program.c\n\
|
|
\n\
|
|
This wrapper provides the following services:\n\
|
|
\n\
|
|
- Logging latency and memory usage\n\
|
|
- Ensures the output directory exists\n\
|
|
- Discarding stderr when commands succeed\n\
|
|
- Imposing cpu / memory / file size quotas\n\
|
|
- Mapping stdin to /dev/null when not a file or fifo\n\
|
|
- Buffering stderr to minimize build log interleaving\n\
|
|
- Schlepping stdout into stderr when not a file or fifo\n\
|
|
- Magic filtering of GCC vs. Clang flag incompatibilities\n\
|
|
- Echo the launched subcommand (silent mode supported if V=0)\n\
|
|
- Unzips the vendored GCC toolchain if it hasn't happened yet\n\
|
|
- Making temporary copies of APE executables w/o side-effects\n\
|
|
- Truncating long lines in \"TERM=dumb\" terminals like emacs\n\
|
|
\n\
|
|
Programs running under make that don't wish to have their output\n\
|
|
suppressed (e.g. unit tests with the -b benchmarking flag) shall\n\
|
|
use the exit code `254` which is remapped to `0` with the output\n\
|
|
\n\
|
|
FLAGS\n\
|
|
\n\
|
|
-t touch target on success\n\
|
|
-T TARGET specifies target name for V=0 logging\n\
|
|
-A ACTION specifies short command name for V=0 logging\n\
|
|
-V NUMBER specifies compiler version\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\
|
|
-M BYTES set mem limit [default 512m]\n\
|
|
-F BYTES set fsz limit [default 256m]\n\
|
|
-O BYTES set out limit [default 1m]\n\
|
|
-s decrement verbosity [default 4]\n\
|
|
-v increments verbosity [default 4]\n\
|
|
-n do nothing (prime ape executable)\n\
|
|
-w disable landlock tmp workaround\n\
|
|
-h print help\n\
|
|
\n\
|
|
ENVIRONMENT\n\
|
|
\n\
|
|
V=0 print shortened ephemerally\n\
|
|
V=1 print shortened\n\
|
|
V=2 print command\n\
|
|
V=3 print shortened w/ wall+cpu+mem usage\n\
|
|
V=4 print command w/ wall+cpu+mem usage\n\
|
|
V=5 print output when exitcode is zero\n\
|
|
COLUMNS=INT explicitly set terminal width for output truncation\n\
|
|
TERM=dumb disable ansi x3.64 sequences and thousands separators\n\
|
|
\n"
|
|
|
|
struct Strings {
|
|
size_t n;
|
|
char **p;
|
|
};
|
|
|
|
bool isar;
|
|
bool iscc;
|
|
bool ispkg;
|
|
bool isgcc;
|
|
bool isbfd;
|
|
bool wantpg;
|
|
bool wantnop;
|
|
bool isclang;
|
|
bool wantnopg;
|
|
bool wantasan;
|
|
bool wantframe;
|
|
bool wantubsan;
|
|
bool wantfentry;
|
|
bool wantrecord;
|
|
bool fulloutput;
|
|
bool touchtarget;
|
|
bool noworkaround;
|
|
bool wantnoredzone;
|
|
bool stdoutmustclose;
|
|
bool no_sanitize_null;
|
|
bool no_sanitize_alignment;
|
|
bool no_sanitize_pointer_overflow;
|
|
|
|
int verbose;
|
|
int timeout;
|
|
int gotalrm;
|
|
int gotchld;
|
|
int ccversion;
|
|
int pipefds[2];
|
|
|
|
long cpuquota;
|
|
long fszquota;
|
|
long memquota;
|
|
long proquota;
|
|
long outquota;
|
|
|
|
char *cmd;
|
|
char *mode;
|
|
char *outdir;
|
|
char *action;
|
|
char *target;
|
|
char *output;
|
|
char *outpath;
|
|
char *command;
|
|
char *movepath;
|
|
char *shortened;
|
|
char *colorflag;
|
|
char ccpath[PATH_MAX];
|
|
|
|
struct stat st;
|
|
struct Strings env;
|
|
struct Strings args;
|
|
struct sigaction sa;
|
|
struct rusage usage;
|
|
struct timespec start;
|
|
struct timespec finish;
|
|
struct itimerval timer;
|
|
struct timespec signalled;
|
|
|
|
sigset_t mask;
|
|
char buf[4096];
|
|
sigset_t savemask;
|
|
char tmpout[PATH_MAX];
|
|
|
|
char *g_tmpout;
|
|
const char *g_tmpout_original;
|
|
|
|
const char *const kSafeEnv[] = {
|
|
"ADDR2LINE", // needed by GetAddr2linePath
|
|
"HOME", // needed by ~/.runit.psk
|
|
"HOMEDRIVE", // needed by ~/.runit.psk
|
|
"HOMEPATH", // needed by ~/.runit.psk
|
|
"MAKEFLAGS", // needed by IsRunningUnderMake
|
|
"MODE", // needed by test scripts
|
|
"PATH", // needed by clang
|
|
"PWD", // just seems plain needed
|
|
"STRACE", // useful for troubleshooting
|
|
"TERM", // needed to detect colors
|
|
"TMPDIR", // needed by compiler
|
|
"SYSTEMROOT", // needed by socket()
|
|
};
|
|
|
|
const char *const kGccOnlyFlags[] = {
|
|
"--nocompress-debug-sections",
|
|
"--noexecstack",
|
|
"-Wa,--nocompress-debug-sections",
|
|
"-Wa,--noexecstack",
|
|
"-Wa,-msse2avx",
|
|
"-Wno-unused-but-set-variable",
|
|
"-Wunsafe-loop-optimizations",
|
|
"-fbranch-target-load-optimize",
|
|
"-fcx-limited-range",
|
|
"-fdelete-dead-exceptions",
|
|
"-femit-struct-debug-baseonly",
|
|
"-ffp-int-builtin-inexact",
|
|
"-finline-functions-called-once",
|
|
"-fipa-pta",
|
|
"-fivopts",
|
|
"-flimit-function-alignment",
|
|
"-fmerge-constants",
|
|
"-fmodulo-sched",
|
|
"-fmodulo-sched-allow-regmoves",
|
|
"-fno-align-jumps",
|
|
"-fno-align-labels",
|
|
"-fno-align-loops",
|
|
"-fno-cx-limited-range",
|
|
"-fno-fp-int-builtin-inexact",
|
|
"-fno-gnu-unique",
|
|
"-fno-gnu-unique",
|
|
"-fno-inline-functions-called-once",
|
|
"-fno-instrument-functions",
|
|
"-fno-schedule-insns2",
|
|
"-fno-whole-program",
|
|
"-fopt-info-vec",
|
|
"-fopt-info-vec-missed",
|
|
"-freg-struct-return",
|
|
"-freschedule-modulo-scheduled-loops",
|
|
"-frounding-math",
|
|
"-fsched2-use-superblocks",
|
|
"-fschedule-insns",
|
|
"-fschedule-insns2",
|
|
"-fshrink-wrap",
|
|
"-fshrink-wrap-separate",
|
|
"-fsignaling-nans",
|
|
"-fstack-clash-protection",
|
|
"-ftracer",
|
|
"-ftrapv",
|
|
"-ftree-loop-im",
|
|
"-ftree-loop-vectorize",
|
|
"-funsafe-loop-optimizations",
|
|
"-fversion-loops-for-strides",
|
|
"-fwhole-program",
|
|
"-gdescribe-dies",
|
|
"-gstabs",
|
|
"-mcall-ms2sysv-xlogues",
|
|
"-mdispatch-scheduler",
|
|
"-mfpmath=sse+387",
|
|
"-mmitigate-rop",
|
|
"-mno-fentry",
|
|
};
|
|
|
|
void OnAlrm(int sig) {
|
|
++gotalrm;
|
|
}
|
|
|
|
void OnChld(int sig, siginfo_t *si, void *ctx) {
|
|
if (!gotchld++) {
|
|
clock_gettime(CLOCK_MONOTONIC, &signalled);
|
|
}
|
|
}
|
|
|
|
void PrintBold(void) {
|
|
if (!__nocolor) {
|
|
appends(&output, "\e[1m");
|
|
}
|
|
}
|
|
|
|
void PrintRed(void) {
|
|
if (!__nocolor) {
|
|
appends(&output, "\e[91;1m");
|
|
}
|
|
}
|
|
|
|
void PrintReset(void) {
|
|
if (!__nocolor) {
|
|
appends(&output, "\e[0m");
|
|
}
|
|
}
|
|
|
|
void PrintMakeCommand(void) {
|
|
const char *s;
|
|
appends(&output, "make MODE=");
|
|
appends(&output, mode);
|
|
appends(&output, " -j");
|
|
appendd(&output, buf, FormatUint64(buf, _getcpucount()) - buf);
|
|
appendw(&output, ' ');
|
|
appends(&output, target);
|
|
}
|
|
|
|
uint64_t GetTimevalMicros(struct timeval tv) {
|
|
return tv.tv_sec * 1000000ull + tv.tv_usec;
|
|
}
|
|
|
|
uint64_t GetTimespecMicros(struct timespec ts) {
|
|
return ts.tv_sec * 1000000ull + ts.tv_nsec / 1000;
|
|
}
|
|
|
|
ssize_t WriteAllUntilSignalledOrError(int fd, const char *p, size_t n) {
|
|
ssize_t rc;
|
|
size_t i, got;
|
|
for (i = 0; i < n; i += got) {
|
|
if ((rc = write(fd, p + i, n - i)) != -1) {
|
|
got = rc;
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
return i;
|
|
}
|
|
|
|
int GetTerminalWidth(void) {
|
|
char *s;
|
|
struct winsize ws;
|
|
if ((s = getenv("COLUMNS"))) {
|
|
return atoi(s);
|
|
} else {
|
|
ws.ws_col = 0;
|
|
tcgetwinsize(2, &ws);
|
|
return ws.ws_col;
|
|
}
|
|
}
|
|
|
|
int GetLineWidth(bool *isineditor) {
|
|
char *s;
|
|
struct winsize ws = {0};
|
|
s = getenv("COLUMNS");
|
|
if (isineditor) {
|
|
*isineditor = !!s;
|
|
}
|
|
if (s) {
|
|
return atoi(s);
|
|
} else if (tcgetwinsize(2, &ws) != -1) {
|
|
if (ws.ws_col && ws.ws_row) {
|
|
return ws.ws_col * ws.ws_row / 3;
|
|
} else {
|
|
return 2048;
|
|
}
|
|
} else {
|
|
return INT_MAX;
|
|
}
|
|
}
|
|
|
|
bool IsSafeEnv(const char *s) {
|
|
const char *p;
|
|
int n, m, l, r, x;
|
|
p = strchr(s, '=');
|
|
n = p ? p - s : -1;
|
|
l = 0;
|
|
r = ARRAYLEN(kSafeEnv) - 1;
|
|
while (l <= r) {
|
|
m = (l & r) + ((l ^ r) >> 1); // floor((a+b)/2)
|
|
if (IsWindows()) {
|
|
x = strncasecmp(s, kSafeEnv[m], n);
|
|
} else {
|
|
x = strncmp(s, kSafeEnv[m], n);
|
|
}
|
|
if (x < 0) {
|
|
r = m - 1;
|
|
} else if (x > 0) {
|
|
l = m + 1;
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool IsGccOnlyFlag(const char *s) {
|
|
int m, l, r, x;
|
|
l = 0;
|
|
r = ARRAYLEN(kGccOnlyFlags) - 1;
|
|
while (l <= r) {
|
|
m = (l & r) + ((l ^ r) >> 1); // floor((a+b)/2)
|
|
x = strcmp(s, kGccOnlyFlags[m]);
|
|
if (x < 0) {
|
|
r = m - 1;
|
|
} else if (x > 0) {
|
|
l = m + 1;
|
|
} else {
|
|
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;
|
|
return false;
|
|
}
|
|
|
|
bool FileExistsAndIsNewerThan(const char *filepath, const char *thanpath) {
|
|
struct stat st1, st2;
|
|
if (stat(filepath, &st1) == -1) return false;
|
|
if (stat(thanpath, &st2) == -1) return false;
|
|
if (st1.st_mtim.tv_sec < st2.st_mtim.tv_sec) return false;
|
|
if (st1.st_mtim.tv_sec > st2.st_mtim.tv_sec) return true;
|
|
return st1.st_mtim.tv_nsec >= st2.st_mtim.tv_nsec;
|
|
}
|
|
|
|
static size_t TallyArgs(char **p) {
|
|
size_t n;
|
|
for (n = 0; *p; ++p) {
|
|
n += sizeof(*p);
|
|
n += strlen(*p) + 1;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
void AddEnv(char *s) {
|
|
AddStr(&env, s);
|
|
}
|
|
|
|
char *StripPrefix(char *s, char *p) {
|
|
if (startswith(s, p)) {
|
|
return s + strlen(p);
|
|
} else {
|
|
return s;
|
|
}
|
|
}
|
|
|
|
void AddArg(char *actual) {
|
|
const char *s;
|
|
if (actual == g_tmpout) {
|
|
s = g_tmpout_original;
|
|
} else {
|
|
s = actual;
|
|
}
|
|
if (args.n) {
|
|
appendw(&command, ' ');
|
|
}
|
|
appends(&command, s);
|
|
if (!args.n) {
|
|
appends(&shortened, StripPrefix(basename(s), "x86_64-linux-musl-"));
|
|
} else if (*s != '-') {
|
|
appendw(&shortened, ' ');
|
|
if ((isar || isbfd || ispkg) &&
|
|
(strcmp(args.p[args.n - 1], "-o") &&
|
|
(endswith(s, ".o") || endswith(s, ".pkg") ||
|
|
(endswith(s, ".a") && !isar)))) {
|
|
appends(&shortened, basename(s));
|
|
} else {
|
|
appends(&shortened, s);
|
|
}
|
|
} else if (/*
|
|
* a in ('-', '--') or
|
|
* a.startswith('-o') or
|
|
* c == 'ld' and a == '-T' or
|
|
* c == 'cc' and a.startswith('-O') or
|
|
* c == 'cc' and a.startswith('-x') or
|
|
* c == 'cc' and a in ('-c', '-E', '-S')
|
|
*/
|
|
s[0] == '-' && (!s[1] || s[1] == 'o' || (s[1] == '-' && !s[2]) ||
|
|
(isbfd && (s[1] == 'T' && !s[2])) ||
|
|
(iscc && (s[1] == 'O' || s[1] == 'x' ||
|
|
(!s[2] && (s[1] == 'c' || s[1] == 'E' ||
|
|
s[1] == 'S')))))) {
|
|
appendw(&shortened, ' ');
|
|
appends(&shortened, s);
|
|
}
|
|
AddStr(&args, actual);
|
|
}
|
|
|
|
static int GetBaseCpuFreqMhz(void) {
|
|
return KCPUIDS(16H, EAX) & 0x7fff;
|
|
}
|
|
|
|
void SetCpuLimit(int secs) {
|
|
int mhz, lim;
|
|
struct rlimit rlim;
|
|
if (secs <= 0) return;
|
|
if (IsWindows()) return;
|
|
#ifdef __x86_64__
|
|
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);
|
|
}
|
|
}
|
|
#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);
|
|
}
|
|
}
|
|
|
|
void SetMemLimit(long n) {
|
|
struct rlimit rlim = {n, n};
|
|
if (n <= 0) return;
|
|
if (IsWindows() || IsXnu()) return;
|
|
setrlimit(RLIMIT_AS, &rlim);
|
|
}
|
|
|
|
void SetProLimit(long n) {
|
|
struct rlimit rlim = {n, n};
|
|
if (n <= 0) return;
|
|
setrlimit(RLIMIT_NPROC, &rlim);
|
|
}
|
|
|
|
bool ArgNeedsShellQuotes(const char *s) {
|
|
if (*s) {
|
|
for (;;) {
|
|
switch (*s++ & 255) {
|
|
case 0:
|
|
return false;
|
|
case '-':
|
|
case '.':
|
|
case '/':
|
|
case '_':
|
|
case '=':
|
|
case ':':
|
|
case '0' ... '9':
|
|
case 'A' ... 'Z':
|
|
case 'a' ... 'z':
|
|
break;
|
|
default:
|
|
return true;
|
|
}
|
|
}
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
char *AddShellQuotes(const char *s) {
|
|
char *p, *q;
|
|
size_t i, j, n;
|
|
n = strlen(s);
|
|
p = malloc(1 + n * 5 + 1 + 1);
|
|
j = 0;
|
|
p[j++] = '\'';
|
|
for (i = 0; i < n; ++i) {
|
|
if (s[i] != '\'') {
|
|
p[j++] = s[i];
|
|
} else {
|
|
p[j + 0] = '\'';
|
|
p[j + 1] = '"';
|
|
p[j + 2] = '\'';
|
|
p[j + 3] = '"';
|
|
p[j + 4] = '\'';
|
|
j += 5;
|
|
}
|
|
}
|
|
p[j++] = '\'';
|
|
p[j] = 0;
|
|
if ((q = realloc(p, j + 1))) p = q;
|
|
return p;
|
|
}
|
|
|
|
void MakeDirs(const char *path, int mode) {
|
|
if (makedirs(path, mode)) {
|
|
kprintf("error: makedirs(%#s) failed\n", path);
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
int Launch(void) {
|
|
size_t got;
|
|
ssize_t rc;
|
|
int ws, pid;
|
|
uint64_t us;
|
|
gotchld = 0;
|
|
|
|
if (pipe2(pipefds, O_CLOEXEC) == -1) {
|
|
kprintf("pipe2 failed: %s\n", _strerrno(errno));
|
|
exit(1);
|
|
}
|
|
|
|
clock_gettime(CLOCK_MONOTONIC, &start);
|
|
if (timeout > 0) {
|
|
timer.it_value.tv_sec = timeout;
|
|
timer.it_interval.tv_sec = timeout;
|
|
setitimer(ITIMER_REAL, &timer, 0);
|
|
}
|
|
|
|
pid = vfork();
|
|
if (pid == -1) {
|
|
kprintf("vfork failed: %s\n", _strerrno(errno));
|
|
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);
|
|
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);
|
|
}
|
|
close(pipefds[1]);
|
|
|
|
for (;;) {
|
|
if (gotalrm) {
|
|
PrintRed();
|
|
appends(&output, "\n\n`");
|
|
PrintMakeCommand();
|
|
appends(&output, "` timed out after ");
|
|
appendd(&output, buf, FormatInt64(buf, timeout) - buf);
|
|
appends(&output, " seconds! ");
|
|
PrintReset();
|
|
kill(pid, SIGXCPU);
|
|
rc = -1;
|
|
break;
|
|
}
|
|
if ((rc = read(pipefds[0], buf, sizeof(buf))) != -1) {
|
|
if (!(got = rc)) break;
|
|
appendd(&output, buf, got);
|
|
if (outquota > 0 && appendz(output).i > outquota) {
|
|
kill(pid, SIGXFSZ);
|
|
PrintRed();
|
|
appendw(&output, '`');
|
|
PrintMakeCommand();
|
|
appends(&output, "` printed ");
|
|
appendd(&output, buf,
|
|
FormatUint64Thousands(buf, appendz(output).i) - buf);
|
|
appends(&output, " bytes of output which exceeds the limit ");
|
|
appendd(&output, buf, FormatUint64Thousands(buf, outquota) - buf);
|
|
appendw(&output, READ16LE("! "));
|
|
PrintReset();
|
|
rc = -1;
|
|
break;
|
|
}
|
|
} else if (errno == EINTR) {
|
|
continue;
|
|
} else {
|
|
/* this should never happen */
|
|
PrintRed();
|
|
appends(&output, "error: compile.com read() failed w/ ");
|
|
appendd(&output, buf, FormatInt64(buf, errno) - buf);
|
|
PrintReset();
|
|
appendw(&output, READ16LE(": "));
|
|
kill(pid, SIGTERM);
|
|
break;
|
|
}
|
|
}
|
|
close(pipefds[0]);
|
|
while (wait4(pid, &ws, 0, &usage) == -1) {
|
|
if (errno == EINTR) {
|
|
if (gotalrm > 1) {
|
|
PrintRed();
|
|
appends(&output, "and it willfully ignored our SIGXCPU signal! ");
|
|
PrintReset();
|
|
kill(pid, SIGKILL);
|
|
gotalrm = 1;
|
|
}
|
|
} else if (errno == ECHILD) {
|
|
break;
|
|
} else {
|
|
/* this should never happen */
|
|
PrintRed();
|
|
appends(&output, "error: compile.com wait4() failed w/ ");
|
|
appendd(&output, buf, FormatInt64(buf, errno) - buf);
|
|
PrintReset();
|
|
appendw(&output, READ16LE(": "));
|
|
ws = -1;
|
|
break;
|
|
}
|
|
}
|
|
clock_gettime(CLOCK_MONOTONIC, &finish);
|
|
if (gotchld) {
|
|
us = GetTimespecMicros(finish) - GetTimespecMicros(signalled);
|
|
if (us > 1000000) {
|
|
appends(&output, "wut: compile.com needed ");
|
|
appendd(&output, buf, FormatUint64Thousands(buf, us) - buf);
|
|
appends(&output, "µs to wait() for zombie ");
|
|
rc = -1;
|
|
}
|
|
if (gotchld > 1) {
|
|
appends(&output, "wut: compile.com got multiple sigchld?! ");
|
|
rc = -1;
|
|
}
|
|
}
|
|
return ws | rc;
|
|
}
|
|
|
|
void ReportResources(void) {
|
|
uint64_t us;
|
|
appendw(&output, '\n');
|
|
if ((us = GetTimespecMicros(finish) - GetTimespecMicros(start))) {
|
|
appends(&output, "consumed ");
|
|
appendd(&output, buf, FormatUint64Thousands(buf, us) - buf);
|
|
appends(&output, "µs wall time\n");
|
|
}
|
|
AppendResourceReport(&output, &usage, "\n");
|
|
appendw(&output, '\n');
|
|
}
|
|
|
|
bool MovePreservingDestinationInode(const char *from, const char *to) {
|
|
bool res;
|
|
ssize_t rc;
|
|
size_t remain;
|
|
struct stat st;
|
|
int fdin, fdout;
|
|
if ((fdin = open(from, O_RDONLY)) == -1) {
|
|
return false;
|
|
}
|
|
fstat(fdin, &st);
|
|
if ((fdout = creat(to, st.st_mode)) == -1) {
|
|
close(fdin);
|
|
return false;
|
|
}
|
|
fadvise(fdin, 0, st.st_size, MADV_SEQUENTIAL);
|
|
ftruncate(fdout, st.st_size);
|
|
for (res = true, remain = st.st_size; remain;) {
|
|
rc = copy_file_range(fdin, 0, fdout, 0, remain, 0);
|
|
if (rc != -1) {
|
|
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;
|
|
}
|
|
res = copyfd(fdin, fdout, -1) != -1;
|
|
break;
|
|
} else {
|
|
res = false;
|
|
break;
|
|
}
|
|
}
|
|
close(fdin);
|
|
close(fdout);
|
|
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;
|
|
char *e = tmpout + sizeof(tmpout) - 1;
|
|
g_tmpout_original = path;
|
|
p = stpcpy(p, kTmpPath);
|
|
while ((c = *path++)) {
|
|
if (c == '/') c = '_';
|
|
if (p == e) {
|
|
kprintf("MakeTmpOut path too long: %s\n", tmpout);
|
|
exit(1);
|
|
}
|
|
*p++ = c;
|
|
}
|
|
*p = 0;
|
|
g_tmpout = tmpout;
|
|
return tmpout;
|
|
}
|
|
|
|
int main(int argc, char *argv[]) {
|
|
int columns;
|
|
uint64_t us;
|
|
bool isineditor;
|
|
size_t i, j, n, m;
|
|
bool isproblematic;
|
|
int ws, opt, exitcode;
|
|
char *s, *p, *q, **envp;
|
|
|
|
#ifndef NDEBUG
|
|
ShowCrashReports();
|
|
#endif
|
|
|
|
mode = firstnonnull(getenv("MODE"), MODE);
|
|
|
|
/*
|
|
* parse prefix arguments
|
|
*/
|
|
verbose = 4;
|
|
timeout = 90; /* secs */
|
|
cpuquota = 32; /* secs */
|
|
proquota = 2048; /* procs */
|
|
fszquota = 256 * 1000 * 1000; /* bytes */
|
|
memquota = 512 * 1024 * 1024; /* bytes */
|
|
if ((s = getenv("V"))) verbose = atoi(s);
|
|
while ((opt = getopt(argc, argv, "hnstvwA:C:F:L:M:O:P:T:V:")) != -1) {
|
|
switch (opt) {
|
|
case 'n':
|
|
exit(0);
|
|
case 's':
|
|
--verbose;
|
|
break;
|
|
case 'v':
|
|
++verbose;
|
|
break;
|
|
case 'A':
|
|
action = optarg;
|
|
break;
|
|
case 'T':
|
|
target = optarg;
|
|
break;
|
|
case 't':
|
|
touchtarget = true;
|
|
break;
|
|
case 'w':
|
|
noworkaround = true;
|
|
break;
|
|
case 'L':
|
|
timeout = atoi(optarg);
|
|
break;
|
|
case 'C':
|
|
cpuquota = atoi(optarg);
|
|
break;
|
|
case 'V':
|
|
ccversion = atoi(optarg);
|
|
break;
|
|
case 'P':
|
|
proquota = atoi(optarg);
|
|
break;
|
|
case 'F':
|
|
fszquota = sizetol(optarg, 1000);
|
|
break;
|
|
case 'M':
|
|
memquota = sizetol(optarg, 1024);
|
|
break;
|
|
case 'O':
|
|
outquota = sizetol(optarg, 1024);
|
|
break;
|
|
case 'h':
|
|
fputs(MANUAL, stdout);
|
|
exit(0);
|
|
default:
|
|
fputs(MANUAL, stderr);
|
|
exit(1);
|
|
}
|
|
}
|
|
if (optind == argc) {
|
|
fputs("error: missing arguments\n", stderr);
|
|
exit(1);
|
|
}
|
|
|
|
/*
|
|
* extend limits for slow UBSAN in particular
|
|
*/
|
|
if (!strcmp(mode, "dbg") || !strcmp(mode, "ubsan")) {
|
|
cpuquota *= 2;
|
|
fszquota *= 2;
|
|
memquota *= 2;
|
|
timeout *= 2;
|
|
}
|
|
|
|
cmd = argv[optind];
|
|
if (!strchr(cmd, '/')) {
|
|
if (!(cmd = commandv(cmd, ccpath, sizeof(ccpath)))) exit(127);
|
|
}
|
|
|
|
s = basename(strdup(cmd));
|
|
if (strstr(s, "gcc") || strstr(s, "g++")) {
|
|
iscc = true;
|
|
isgcc = true;
|
|
} else if (strstr(s, "clang") || strstr(s, "clang++")) {
|
|
iscc = true;
|
|
isclang = true;
|
|
} else if (strstr(s, "ld.bfd")) {
|
|
isbfd = true;
|
|
} else if (strstr(s, "ar.com")) {
|
|
isar = true;
|
|
} else if (strstr(s, "package.com")) {
|
|
ispkg = true;
|
|
}
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
if (!noworkaround && //
|
|
!movepath && //
|
|
!outpath && //
|
|
target && //
|
|
!strcmp(target, argv[i])) {
|
|
AddArg(MakeTmpOut(argv[i]));
|
|
outpath = target;
|
|
movepath = target;
|
|
MovePreservingDestinationInode(target, tmpout);
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* capture arguments
|
|
*/
|
|
if (argv[i][0] != '-') {
|
|
AddArg(argv[i]);
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* capture flags
|
|
*/
|
|
if (startswith(argv[i], "-o")) {
|
|
if (!strcmp(argv[i], "-o")) {
|
|
outpath = argv[++i];
|
|
} else {
|
|
outpath = argv[i] + 2;
|
|
}
|
|
AddArg("-o");
|
|
if (noworkaround) {
|
|
AddArg(outpath);
|
|
} else {
|
|
movepath = outpath;
|
|
AddArg(MakeTmpOut(outpath));
|
|
}
|
|
continue;
|
|
}
|
|
if (!iscc) {
|
|
AddArg(argv[i]);
|
|
continue;
|
|
}
|
|
if (isclang && IsGccOnlyFlag(argv[i])) {
|
|
continue;
|
|
}
|
|
if (!X86_HAVE(AVX) &&
|
|
(!strcmp(argv[i], "-msse2avx") || !strcmp(argv[i], "-Wa,-msse2avx"))) {
|
|
// Avoid any chance of people using Intel's older or low power
|
|
// CPUs encountering a SIGILL error due to these awesome flags
|
|
continue;
|
|
}
|
|
if (!strcmp(argv[i], "-w")) {
|
|
AddArg(argv[i]);
|
|
AddArg("-D__W__");
|
|
} else if (!strcmp(argv[i], "-Oz")) {
|
|
if (isclang) {
|
|
AddArg(argv[i]);
|
|
} else {
|
|
AddArg("-Os");
|
|
}
|
|
} else if (!strcmp(argv[i], "-pg")) {
|
|
wantpg = true;
|
|
} else if (!strcmp(argv[i], "-x-no-pg")) {
|
|
wantnopg = true;
|
|
} else if (!strcmp(argv[i], "-mfentry")) {
|
|
wantfentry = true;
|
|
} else if (!strcmp(argv[i], "-mnop-mcount")) {
|
|
wantnop = true;
|
|
} else if (!strcmp(argv[i], "-mrecord-mcount")) {
|
|
wantrecord = true;
|
|
} else if (!strcmp(argv[i], "-fno-omit-frame-pointer")) {
|
|
wantframe = true;
|
|
} else if (!strcmp(argv[i], "-fomit-frame-pointer")) {
|
|
wantframe = false;
|
|
} else if (!strcmp(argv[i], "-mno-red-zone")) {
|
|
wantnoredzone = true;
|
|
} else if (!strcmp(argv[i], "-mred-zone")) {
|
|
wantnoredzone = false;
|
|
} else if (!strcmp(argv[i], "-mno-vzeroupper")) {
|
|
if (isgcc) {
|
|
AddArg("-Wa,-msse2avx");
|
|
AddArg("-D__MNO_VZEROUPPER__");
|
|
} else if (isclang) {
|
|
AddArg("-mllvm");
|
|
AddArg("-x86-use-vzeroupper=0");
|
|
}
|
|
} else if (!strcmp(argv[i], "-msse2avx")) {
|
|
if (isgcc) {
|
|
AddArg(argv[i]);
|
|
}
|
|
|
|
#ifdef __x86_64__
|
|
} else if (!strcmp(argv[i], "-march=native")) {
|
|
struct X86ProcessorModel *model;
|
|
if (X86_HAVE(XOP)) AddArg("-mxop");
|
|
if (X86_HAVE(SSE4A)) AddArg("-msse4a");
|
|
if (X86_HAVE(SSE3)) AddArg("-msse3");
|
|
if (X86_HAVE(SSSE3)) AddArg("-mssse3");
|
|
if (X86_HAVE(SSE4_1)) AddArg("-msse4.1");
|
|
if (X86_HAVE(SSE4_2)) AddArg("-msse4.2");
|
|
if (X86_HAVE(AVX)) AddArg("-mavx");
|
|
if (X86_HAVE(AVX2)) {
|
|
AddArg("-mavx2");
|
|
if (isgcc) {
|
|
AddArg("-msse2avx");
|
|
AddArg("-Wa,-msse2avx");
|
|
}
|
|
}
|
|
if (X86_HAVE(AVX512F)) AddArg("-mavx512f");
|
|
if (X86_HAVE(AVX512PF)) AddArg("-mavx512pf");
|
|
if (X86_HAVE(AVX512ER)) AddArg("-mavx512er");
|
|
if (X86_HAVE(AVX512CD)) AddArg("-mavx512cd");
|
|
if (X86_HAVE(AVX512VL)) AddArg("-mavx512vl");
|
|
if (X86_HAVE(AVX512BW)) AddArg("-mavx512bw");
|
|
if (X86_HAVE(AVX512DQ)) AddArg("-mavx512dq");
|
|
if (X86_HAVE(AVX512IFMA)) AddArg("-mavx512ifma");
|
|
if (X86_HAVE(AVX512VBMI)) AddArg("-mavx512vbmi");
|
|
if (X86_HAVE(SHA)) AddArg("-msha");
|
|
if (X86_HAVE(AES)) AddArg("-maes");
|
|
if (X86_HAVE(VAES)) AddArg("-mvaes");
|
|
if (X86_HAVE(PCLMUL)) AddArg("-mpclmul");
|
|
if (X86_HAVE(FSGSBASE)) AddArg("-mfsgsbase");
|
|
if (X86_HAVE(F16C)) AddArg("-mf16c");
|
|
if (X86_HAVE(FMA)) AddArg("-mfma");
|
|
if (X86_HAVE(POPCNT)) AddArg("-mpopcnt");
|
|
if (X86_HAVE(BMI)) AddArg("-mbmi");
|
|
if (X86_HAVE(BMI2)) AddArg("-mbmi2");
|
|
if (X86_HAVE(ADX)) AddArg("-madx");
|
|
if (X86_HAVE(FXSR)) AddArg("-mfxsr");
|
|
if ((model = getx86processormodel(kX86ProcessorModelKey))) {
|
|
switch (model->march) {
|
|
case X86_MARCH_CORE2:
|
|
AddArg("-march=core2");
|
|
break;
|
|
case X86_MARCH_NEHALEM:
|
|
AddArg("-march=nehalem");
|
|
break;
|
|
case X86_MARCH_WESTMERE:
|
|
AddArg("-march=westmere");
|
|
break;
|
|
case X86_MARCH_SANDYBRIDGE:
|
|
AddArg("-march=sandybridge");
|
|
break;
|
|
case X86_MARCH_IVYBRIDGE:
|
|
AddArg("-march=ivybridge");
|
|
break;
|
|
case X86_MARCH_HASWELL:
|
|
AddArg("-march=haswell");
|
|
break;
|
|
case X86_MARCH_BROADWELL:
|
|
AddArg("-march=broadwell");
|
|
break;
|
|
case X86_MARCH_SKYLAKE:
|
|
case X86_MARCH_KABYLAKE:
|
|
AddArg("-march=skylake");
|
|
break;
|
|
case X86_MARCH_CANNONLAKE:
|
|
AddArg("-march=cannonlake");
|
|
break;
|
|
case X86_MARCH_ICELAKE:
|
|
if (model->grade >= X86_GRADE_SERVER) {
|
|
AddArg("-march=icelake-server");
|
|
} else {
|
|
AddArg("-march=icelake-client");
|
|
}
|
|
break;
|
|
case X86_MARCH_TIGERLAKE:
|
|
AddArg("-march=tigerlake");
|
|
break;
|
|
case X86_MARCH_BONNELL:
|
|
case X86_MARCH_SALTWELL:
|
|
AddArg("-march=bonnell");
|
|
break;
|
|
case X86_MARCH_SILVERMONT:
|
|
case X86_MARCH_AIRMONT:
|
|
AddArg("-march=silvermont");
|
|
break;
|
|
case X86_MARCH_GOLDMONT:
|
|
AddArg("-march=goldmont");
|
|
break;
|
|
case X86_MARCH_GOLDMONTPLUS:
|
|
AddArg("-march=goldmont-plus");
|
|
break;
|
|
case X86_MARCH_TREMONT:
|
|
AddArg("-march=tremont");
|
|
break;
|
|
case X86_MARCH_KNIGHTSLANDING:
|
|
AddArg("-march=knl");
|
|
break;
|
|
case X86_MARCH_KNIGHTSMILL:
|
|
AddArg("-march=knm");
|
|
break;
|
|
}
|
|
}
|
|
#endif /* __x86_64__ */
|
|
|
|
} else if (!strcmp(argv[i], "-fsanitize=address")) {
|
|
if (isgcc && ccversion >= 6) wantasan = true;
|
|
} else if (!strcmp(argv[i], "-fsanitize=undefined")) {
|
|
if (isgcc && ccversion >= 6) wantubsan = true;
|
|
} else if (!strcmp(argv[i], "-fno-sanitize=address")) {
|
|
wantasan = false;
|
|
} else if (!strcmp(argv[i], "-fno-sanitize=undefined")) {
|
|
wantubsan = false;
|
|
} else if (!strcmp(argv[i], "-fno-sanitize=all")) {
|
|
wantasan = false;
|
|
wantubsan = false;
|
|
} else if (!strcmp(argv[i], "-fno-sanitize=null")) {
|
|
if (isgcc && ccversion >= 6) no_sanitize_null = true;
|
|
} else if (!strcmp(argv[i], "-fno-sanitize=alignment")) {
|
|
if (isgcc && ccversion >= 6) no_sanitize_alignment = true;
|
|
} else if (!strcmp(argv[i], "-fno-sanitize=pointer-overflow")) {
|
|
if (isgcc && ccversion >= 6) no_sanitize_pointer_overflow = true;
|
|
} else if (startswith(argv[i], "-fsanitize=implicit") &&
|
|
strstr(argv[i], "integer")) {
|
|
if (isgcc) AddArg(argv[i]);
|
|
} else if (strstr(argv[i], "stack-protector")) {
|
|
if (isclang || (isgcc && ccversion >= 6)) {
|
|
AddArg(argv[i]);
|
|
}
|
|
} else if (startswith(argv[i], "-fvect-cost") ||
|
|
startswith(argv[i], "-mstringop") ||
|
|
startswith(argv[i], "-gz") || strstr(argv[i], "sanitize") ||
|
|
startswith(argv[i], "-fvect-cost") ||
|
|
startswith(argv[i], "-fvect-cost")) {
|
|
if (isgcc && ccversion >= 6) {
|
|
AddArg(argv[i]);
|
|
}
|
|
} else if (startswith(argv[i], "-fdiagnostic-color=")) {
|
|
colorflag = argv[i];
|
|
} else if (startswith(argv[i], "-R") ||
|
|
!strcmp(argv[i], "-fsave-optimization-record")) {
|
|
if (isclang) AddArg(argv[i]);
|
|
} else if (isclang && startswith(argv[i], "--debug-prefix-map")) {
|
|
/* llvm doesn't provide a gas interface so simulate w/ clang */
|
|
AddArg(xstrcat("-f", argv[i] + 2));
|
|
} else if (isgcc && (!strcmp(argv[i], "-Os") || !strcmp(argv[i], "-O2") ||
|
|
!strcmp(argv[i], "-O3"))) {
|
|
/* https://gcc.gnu.org/bugzilla/show_bug.cgi?id=97623 */
|
|
AddArg(argv[i]);
|
|
AddArg("-fno-code-hoisting");
|
|
} else {
|
|
AddArg(argv[i]);
|
|
}
|
|
}
|
|
if (outpath) {
|
|
if (!target) {
|
|
target = outpath;
|
|
}
|
|
} else if (target) {
|
|
outpath = target;
|
|
} else {
|
|
fputs("error: compile.com needs -TTARGET or -oOUTPATH\n", stderr);
|
|
exit(7);
|
|
}
|
|
|
|
/*
|
|
* append special args
|
|
*/
|
|
if (iscc) {
|
|
if (isclang) {
|
|
AddArg("-Wno-unused-command-line-argument");
|
|
AddArg("-Wno-incompatible-pointer-types-discards-qualifiers");
|
|
}
|
|
if (!__nocolor) {
|
|
AddArg(firstnonnull(colorflag, "-fdiagnostics-color=always"));
|
|
}
|
|
if (wantpg && !wantnopg) {
|
|
AddArg("-pg");
|
|
AddArg("-D__PG__");
|
|
if (wantnop && !isclang) {
|
|
AddArg("-mnop-mcount");
|
|
AddArg("-D__MNOP_MCOUNT__");
|
|
}
|
|
if (wantrecord) {
|
|
AddArg("-mrecord-mcount");
|
|
AddArg("-D__MRECORD_MCOUNT__");
|
|
}
|
|
if (wantfentry) {
|
|
AddArg("-mfentry");
|
|
AddArg("-D__MFENTRY__");
|
|
}
|
|
}
|
|
if (wantasan) {
|
|
AddArg("-fsanitize=address");
|
|
/* compiler adds this by default */
|
|
/* AddArg("-D__SANITIZE_ADDRESS__"); */
|
|
}
|
|
if (wantubsan) {
|
|
AddArg("-fsanitize=undefined");
|
|
AddArg("-fno-data-sections");
|
|
AddArg("-D__SANITIZE_UNDEFINED__");
|
|
}
|
|
if (no_sanitize_null) {
|
|
AddArg("-fno-sanitize=null");
|
|
}
|
|
if (no_sanitize_alignment) {
|
|
AddArg("-fno-sanitize=alignment");
|
|
}
|
|
if (no_sanitize_pointer_overflow) {
|
|
AddArg("-fno-sanitize=pointer-overflow");
|
|
}
|
|
if (wantnoredzone) {
|
|
AddArg("-mno-red-zone");
|
|
AddArg("-D__MNO_RED_ZONE__");
|
|
}
|
|
if (wantframe) {
|
|
AddArg("-fno-omit-frame-pointer");
|
|
AddArg("-D__FNO_OMIT_FRAME_POINTER__");
|
|
} else {
|
|
AddArg("-fomit-frame-pointer");
|
|
}
|
|
}
|
|
|
|
/*
|
|
* scrub environment for determinism and great justice
|
|
*/
|
|
for (envp = environ; *envp; ++envp) {
|
|
if (startswith(*envp, "MODE=")) {
|
|
mode = *envp + 5;
|
|
}
|
|
if (IsSafeEnv(*envp)) {
|
|
AddEnv(*envp);
|
|
}
|
|
}
|
|
AddEnv("LC_ALL=C");
|
|
AddEnv("SOURCE_DATE_EPOCH=0");
|
|
|
|
/*
|
|
* ensure output directory exists
|
|
*/
|
|
if (outpath) {
|
|
outdir = xdirname(outpath);
|
|
if (!isdirectory(outdir)) {
|
|
MakeDirs(outdir, 0755);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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);
|
|
sigprocmask(SIG_BLOCK, &mask, &savemask);
|
|
sigemptyset(&sa.sa_mask);
|
|
sa.sa_flags = SA_NOCLDSTOP | SA_SIGINFO;
|
|
sa.sa_sigaction = OnChld;
|
|
if (sigaction(SIGCHLD, &sa, 0) == -1) exit(83);
|
|
if (timeout > 0) {
|
|
sa.sa_flags = 0;
|
|
sa.sa_handler = OnAlrm;
|
|
sigaction(SIGALRM, &sa, 0);
|
|
}
|
|
|
|
/*
|
|
* run command
|
|
*/
|
|
ws = Launch();
|
|
|
|
/*
|
|
* propagate exit
|
|
*/
|
|
if (ws != -1) {
|
|
if (WIFEXITED(ws)) {
|
|
if (!(exitcode = WEXITSTATUS(ws)) || exitcode == 254) {
|
|
if (touchtarget && target) {
|
|
MakeDirs(xdirname(target), 0755);
|
|
if (touch(target, 0644)) {
|
|
exitcode = 90;
|
|
appends(&output, "\nfailed to touch output file\n");
|
|
}
|
|
}
|
|
if (movepath) {
|
|
if (!MovePreservingDestinationInode(tmpout, movepath)) {
|
|
unlink(tmpout);
|
|
exitcode = 90;
|
|
appends(&output, "\nfailed to move output file\n");
|
|
appends(&output, tmpout);
|
|
appends(&output, "\n");
|
|
appends(&output, movepath);
|
|
appends(&output, "\n");
|
|
} else {
|
|
unlink(tmpout);
|
|
}
|
|
}
|
|
} else {
|
|
appendw(&output, '\n');
|
|
PrintRed();
|
|
appendw(&output, '`');
|
|
PrintMakeCommand();
|
|
appends(&output, "` exited with ");
|
|
appendd(&output, buf, FormatUint64(buf, exitcode) - buf);
|
|
PrintReset();
|
|
appendw(&output, READ16LE(":\n"));
|
|
}
|
|
} else if (WTERMSIG(ws) == SIGINT) {
|
|
appendr(&output, 0);
|
|
appends(&output, "\rinterrupted: ");
|
|
PrintMakeCommand();
|
|
appendw(&output, '\n');
|
|
WriteAllUntilSignalledOrError(2, output, appendz(output).i);
|
|
return 128 + SIGINT;
|
|
} else {
|
|
exitcode = 128 + WTERMSIG(ws);
|
|
appendw(&output, '\n');
|
|
PrintRed();
|
|
appendw(&output, '`');
|
|
PrintMakeCommand();
|
|
appends(&output, "` terminated by ");
|
|
appends(&output, strsignal(WTERMSIG(ws)));
|
|
PrintReset();
|
|
appendw(&output, READ16LE(":\n"));
|
|
appends(&output, "env -i ");
|
|
for (i = 0; i < env.n; ++i) {
|
|
if (ArgNeedsShellQuotes(env.p[i])) {
|
|
q = AddShellQuotes(env.p[i]);
|
|
appends(&output, q);
|
|
free(q);
|
|
} else {
|
|
appends(&output, env.p[i]);
|
|
}
|
|
appendw(&output, ' ');
|
|
}
|
|
}
|
|
} else {
|
|
exitcode = 89;
|
|
}
|
|
|
|
/*
|
|
* describe command that was run
|
|
*/
|
|
if (!exitcode || exitcode == 254) {
|
|
if (exitcode == 254) {
|
|
exitcode = 0;
|
|
fulloutput = true;
|
|
} else if (verbose < 5) {
|
|
appendr(&output, 0);
|
|
fulloutput = false;
|
|
} else {
|
|
fulloutput = !!appendz(output).i;
|
|
}
|
|
if (fulloutput) {
|
|
ReportResources();
|
|
}
|
|
if (!__nocolor && ischardev(2)) {
|
|
/* clear line forward */
|
|
appendw(&output, READ32LE("\e[K"));
|
|
}
|
|
if (verbose < 1) {
|
|
/* make silent mode, i.e. `V=0 make o//target` */
|
|
appendr(&command, 0);
|
|
if (!action) action = "BUILD";
|
|
if (!outpath) outpath = shortened;
|
|
n = strlen(action);
|
|
appends(&command, action);
|
|
do appendw(&command, ' '), ++n;
|
|
while (n < 15);
|
|
appends(&command, outpath);
|
|
n += strlen(outpath);
|
|
m = GetTerminalWidth();
|
|
if (m > 3 && n > m) {
|
|
appendd(&output, command, m - 3);
|
|
appendw(&output, READ32LE("..."));
|
|
} else {
|
|
if (n < m && (__nocolor || !ischardev(2))) {
|
|
while (n < m) appendw(&command, ' '), ++n;
|
|
}
|
|
appendd(&output, command, n);
|
|
}
|
|
appendw(&output, m > 0 ? '\r' : '\n');
|
|
} else {
|
|
n = 0;
|
|
if (verbose >= 3) {
|
|
/* make bonus mode (shows resource usage) */
|
|
if (timeout > 0) {
|
|
us = GetTimespecMicros(finish) - GetTimespecMicros(start);
|
|
i = FormatUint64Thousands(buf, us) - buf;
|
|
j = ceil(log10(timeout));
|
|
j += (j - 1) / 3;
|
|
j += 1 + 3;
|
|
j += 1 + 3;
|
|
while (i < j) appendw(&output, ' '), ++i;
|
|
if (us > timeout * 1000000ull / 2) {
|
|
if (us > timeout * 1000000ull) {
|
|
PrintRed();
|
|
} else {
|
|
PrintBold();
|
|
}
|
|
appends(&output, buf);
|
|
PrintReset();
|
|
} else {
|
|
appends(&output, buf);
|
|
}
|
|
appendw(&output, READ32LE("⏰ "));
|
|
n += i + 2 + 1;
|
|
}
|
|
if (cpuquota > 0) {
|
|
if (!(us = GetTimevalMicros(usage.ru_utime) +
|
|
GetTimevalMicros(usage.ru_stime))) {
|
|
us = GetTimespecMicros(finish) - GetTimespecMicros(start);
|
|
}
|
|
i = FormatUint64Thousands(buf, us) - buf;
|
|
j = ceil(log10(cpuquota));
|
|
j += (j - 1) / 3;
|
|
j += 1 + 3;
|
|
j += 1 + 3;
|
|
while (i < j) appendw(&output, ' '), ++i;
|
|
if ((isproblematic = us > cpuquota * 1000000ull / 2)) {
|
|
if (us > cpuquota * 1000000ull - (cpuquota * 1000000ull) / 5) {
|
|
PrintRed();
|
|
} else {
|
|
PrintBold();
|
|
}
|
|
}
|
|
appends(&output, buf);
|
|
appendw(&output, READ32LE("⏳ "));
|
|
if (isproblematic) {
|
|
PrintReset();
|
|
}
|
|
n += i + 2 + 1;
|
|
}
|
|
if (memquota > 0) {
|
|
i = FormatUint64Thousands(buf, usage.ru_maxrss) - buf;
|
|
j = ceil(log10(memquota / 1024));
|
|
j += (j - 1) / 3;
|
|
while (i < j) appendw(&output, ' '), ++i;
|
|
if ((isproblematic = usage.ru_maxrss * 1024 > memquota / 2)) {
|
|
if (usage.ru_maxrss * 1024 > memquota - memquota / 5) {
|
|
PrintRed();
|
|
} else {
|
|
PrintBold();
|
|
}
|
|
}
|
|
appends(&output, buf);
|
|
appendw(&output, READ16LE("k "));
|
|
if (isproblematic) {
|
|
PrintReset();
|
|
}
|
|
n += i + 1 + 1;
|
|
}
|
|
if (fszquota > 0) {
|
|
us = usage.ru_inblock + usage.ru_oublock;
|
|
i = FormatUint64Thousands(buf, us) - buf;
|
|
while (i < 7) appendw(&output, ' '), ++i;
|
|
appends(&output, buf);
|
|
appendw(&output, READ32LE("iop "));
|
|
n += i + 4;
|
|
}
|
|
}
|
|
/* make normal mode (echos run commands) */
|
|
if (verbose < 2 || verbose == 3) {
|
|
command = shortened;
|
|
}
|
|
m = GetLineWidth(&isineditor);
|
|
if (m > n + 3 && appendz(command).i > m - n) {
|
|
if (isineditor) {
|
|
if (m > n + 3 && appendz(shortened).i > m - n) {
|
|
appendd(&output, shortened, m - n - 3);
|
|
appendw(&output, READ32LE("..."));
|
|
} else {
|
|
appendd(&output, shortened, appendz(shortened).i);
|
|
}
|
|
} else {
|
|
appendd(&output, command, m - n - 3);
|
|
appendw(&output, READ32LE("..."));
|
|
}
|
|
} else {
|
|
appendd(&output, command, appendz(command).i);
|
|
}
|
|
appendw(&output, '\n');
|
|
}
|
|
} else {
|
|
n = appendz(command).i;
|
|
if (n > 2048) {
|
|
appendr(&command, (n = 2048));
|
|
appendw(&command, READ32LE("..."));
|
|
}
|
|
appendd(&output, command, n);
|
|
ReportResources();
|
|
}
|
|
|
|
/*
|
|
* flush output
|
|
*/
|
|
if (WriteAllUntilSignalledOrError(2, output, appendz(output).i) == -1) {
|
|
if (errno == EINTR) {
|
|
s = "notice: compile.com output truncated\n";
|
|
} else {
|
|
if (!exitcode) exitcode = 55;
|
|
s = "error: compile.com failed to write result\n";
|
|
}
|
|
write(2, s, strlen(s));
|
|
}
|
|
|
|
/*
|
|
* all done!
|
|
*/
|
|
return exitcode;
|
|
}
|