Initial import

This commit is contained in:
Justine Tunney
2020-06-15 07:18:57 -07:00
commit c91b3c5006
14915 changed files with 590219 additions and 0 deletions

82
tool/build/build.mk Normal file
View File

@@ -0,0 +1,82 @@
#-*-mode:makefile-gmake;indent-tabs-mode:t;tab-width:8;coding:utf-8-*-┐
#───vi: set et ft=make ts=8 tw=8 fenc=utf-8 :vi───────────────────────┘
PKGS += TOOL_BUILD
TOOL_BUILD_FILES := $(wildcard tool/build/*)
TOOL_BUILD_SRCS = $(filter %.c,$(TOOL_BUILD_FILES))
TOOL_BUILD_HDRS = $(filter %.h,$(TOOL_BUILD_FILES))
TOOL_BUILD_COMS = $(TOOL_BUILD_OBJS:%.o=%.com)
TOOL_BUILD_BINS = $(TOOL_BUILD_COMS) $(TOOL_BUILD_COMS:%=%.dbg)
TOOL_BUILD_OBJS = \
$(TOOL_BUILD_SRCS:%=o/$(MODE)/%.zip.o) \
$(TOOL_BUILD_SRCS:%.c=o/$(MODE)/%.o)
TOOL_BUILD_LINK = \
$(TOOL_BUILD_DEPS) \
o/$(MODE)/tool/build/%.o \
$(CRT) \
$(APE)
TOOL_BUILD_DIRECTDEPS = \
DSP_CORE \
DSP_SCALE \
LIBC_ALG \
LIBC_CALLS \
LIBC_CALLS_HEFTY \
LIBC_CONV \
LIBC_DNS \
LIBC_ELF \
LIBC_FMT \
LIBC_LOG \
LIBC_TINYMATH \
LIBC_MEM \
LIBC_NEXGEN32E \
LIBC_RUNTIME \
LIBC_SOCK \
LIBC_STDIO \
LIBC_STR \
LIBC_RAND \
LIBC_STUBS \
LIBC_SYSV \
LIBC_SYSV_CALLS \
LIBC_TESTLIB \
LIBC_TIME \
LIBC_UNICODE \
LIBC_X \
TOOL_BUILD_LIB \
THIRD_PARTY_DTOA \
THIRD_PARTY_GETOPT \
THIRD_PARTY_XED \
THIRD_PARTY_ZLIB \
THIRD_PARTY_STB
TOOL_BUILD_DEPS := \
$(call uniq,$(foreach x,$(TOOL_BUILD_DIRECTDEPS),$($(x))))
o/$(MODE)/tool/build/build.pkg: \
$(TOOL_BUILD_OBJS) \
$(foreach x,$(TOOL_BUILD_DIRECTDEPS),$($(x)_A).pkg)
o/$(MODE)/tool/build/%.com.dbg: \
$(TOOL_BUILD_DEPS) \
o/$(MODE)/tool/build/build.pkg \
o/$(MODE)/tool/build/%.o \
$(CRT) \
$(APE)
-@$(APELINK)
o/$(MODE)/tool/build/mkdeps.o: tool/build/mkdeps.c
-@ACTION=OBJECTIFY.c build/compile $(OBJECTIFY.c) $(OUTPUT_OPTION) $<
o/$(MODE)/tool/build/generatematrix.o \
o/$(MODE)/tool/build/mkdeps.o: \
OVERRIDE_COPTS += \
-O2
.PHONY: o/$(MODE)/tool/build
o/$(MODE)/tool/build: \
o/$(MODE)/tool/build/lib \
$(TOOL_BUILD_BINS) \
$(TOOL_BUILD_CHECKS)

230
tool/build/coefficients.c Normal file
View File

@@ -0,0 +1,230 @@
/*-*- 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 2020 Justine Alexandra Roberts Tunney │
│ │
│ This program is free software; you can redistribute it and/or modify │
│ it under the terms of the GNU General Public License as published by │
│ the Free Software Foundation; version 2 of the License. │
│ │
│ This program is distributed in the hope that it will be useful, but │
│ WITHOUT ANY WARRANTY; without even the implied warranty of │
│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
│ General Public License for more details. │
│ │
│ You should have received a copy of the GNU General Public License │
│ along with this program; if not, write to the Free Software │
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "dsp/core/half.h"
#include "dsp/core/q.h"
#include "libc/conv/conv.h"
#include "libc/log/check.h"
#include "libc/log/log.h"
#include "libc/macros.h"
#include "libc/math.h"
#include "libc/mem/mem.h"
#include "libc/runtime/gc.h"
#include "libc/runtime/runtime.h"
#include "libc/stdio/stdio.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/ex.h"
#include "libc/sysv/consts/exit.h"
#include "libc/x/x.h"
#include "third_party/dtoa/dtoa.h"
#include "third_party/getopt/getopt.h"
#define USAGE \
" [FLAGS] [INT|FLOAT...]\n\
\n\
Example:\n\
\n\
coefficients -n -- 1 4 6 4 1 # Gaussian Blur\n\
coefficients -L16 -H235 -- .299 .587 .114 # BT.601 RGB→Y\n\
\n\
Flags:\n\
-v verbose\n\
-n normalize\n\
-m FLEX explicit Q(bits)\n\
-L FLEX low data value [default 0]\n\
-H FLEX high data value [default 255]\n\
-? shows this information\n\
\n"
static struct Flags {
bool n;
long L, H, m;
} flags_ = {
.L = 0,
.H = 255,
};
static noreturn void PrintUsage(int rc, FILE *f) {
fprintf(f, "Usage: %s%s", program_invocation_name, USAGE);
exit(rc);
}
static void GetOpts(int argc, char *argv[]) {
int opt;
while ((opt = getopt(argc, argv, "?nvrL:H:m:")) != -1) {
switch (opt) {
case 'v':
g_loglevel++;
break;
case 'n':
flags_.n = true;
break;
case 'L':
flags_.L = strtol(optarg, NULL, 0);
break;
case 'H':
flags_.H = strtol(optarg, NULL, 0);
break;
case 'm':
flags_.m = strtol(optarg, NULL, 0);
break;
case '?':
PrintUsage(EXIT_SUCCESS, stdout);
default:
PrintUsage(EX_USAGE, stderr);
}
}
}
static void *Normalize(int n, double A[static 8]) {
int i;
double sum, rnorm;
for (sum = i = 0; i < n; ++i) {
sum += A[i];
}
if (fabs(sum - 1) > DBL_MIN * n) {
rnorm = 1 / sum;
for (i = 0; i < n; ++i) {
A[i] *= rnorm;
}
}
return A;
}
static void GetLimits(int n, const long I[static 8], long m, long L, long H,
long res[2][2]) {
int i, j[8];
long x, p[2] = {L, H};
DCHECK(0 < n && n <= 8);
memset(res, 0, sizeof(long) * 2 * 2);
for (j[0] = 0; j[0] < ARRAYLEN(p); ++j[0]) {
for (j[1] = 0; j[1] < ARRAYLEN(p); ++j[1]) {
for (j[2] = 0; j[2] < ARRAYLEN(p); ++j[2]) {
for (j[3] = 0; j[3] < ARRAYLEN(p); ++j[3]) {
for (j[4] = 0; j[4] < ARRAYLEN(p); ++j[4]) {
for (j[5] = 0; j[5] < ARRAYLEN(p); ++j[5]) {
for (j[6] = 0; j[6] < ARRAYLEN(p); ++j[6]) {
for (j[7] = 0; j[7] < ARRAYLEN(p); ++j[7]) {
x = 0;
for (i = 0; i < ARRAYLEN(j); ++i) {
x += p[j[i]] * I[i];
if (x < res[0][0]) res[0][0] = x;
if (x > res[0][1]) res[0][1] = x;
}
x += 1l << (m - 1);
if (x < res[0][0]) res[0][0] = x;
if (x > res[0][1]) res[0][1] = x;
x >>= m;
if (x < res[1][0]) res[1][0] = x;
if (x > res[1][1]) res[1][1] = x;
}
}
}
}
}
}
}
}
}
static const char *GetFittingMachineWord(long L, long H) {
if (-128 <= L && H <= 127) return "int8";
if (0 <= L && H <= 255) return "uint8";
if (-0x8000 <= L && H <= 0x7fff) return "int16";
if (~0x7fffffff <= L && H <= 0x7fffffff) return "int32";
if (0 <= L && H <= 0xffffffff) return "uint32";
return "INT64";
}
static char *DescribeMachineWord(long L, long H) {
return xasprintf("%s[%,ld..%,ld]", GetFittingMachineWord(L, H), L, H);
}
static void ShowBetterCoefficients(int n, double C[static 8], long L, long H) {
long err, I[8], lim[2][2];
char buf[32], kAlphabet[] = "abcdefgh";
int i, j, m, emitted, count, indices[8];
CHECK_LT(L, H);
DCHECK(0 < n && n <= 8);
for (m = 2; m < 40; ++m) {
memset(I, 0, sizeof(I));
if (C[6] || C[7]) {
err = GetIntegerCoefficients8(I, C, m, L, H);
} else {
err = GetIntegerCoefficients(I, C, m, L, H);
}
GetLimits(n, I, m, L, H, lim);
for (count = i = 0; i < n; ++i) {
if (I[i]) {
indices[count++] = i;
}
}
if (count) {
emitted = 0;
if (m) emitted += printf("(");
for (i = 0; i < count; ++i) {
if (i) emitted += printf(" + ");
if (I[indices[i]] != 1) {
emitted += printf("%ld*%c", I[indices[i]], kAlphabet[indices[i]]);
} else {
emitted += printf("%c", kAlphabet[indices[i]]);
}
}
if (m) {
if (m > 1) {
emitted += printf(" + %ld", 1l << (m - 1));
}
emitted += printf(")>>%d", m);
}
printf("%*s", MAX(0, 80 - emitted), " ");
printf("/* %s %s %s ε=%,ld */\n", gc(DescribeMachineWord(L, H)),
gc(DescribeMachineWord(lim[0][0], lim[0][1])),
gc(DescribeMachineWord(lim[1][0], lim[1][1])), err);
}
}
}
static int ReadIdealCoefficients(double C[static 8], int sn, char *S[sn]) {
int i, n;
if ((n = MIN(8, sn)) > 0) {
C[0] = C[1] = C[2] = C[3] = C[4] = C[5] = 0;
for (i = 0; i < n; ++i) {
C[i] = strtod(S[i], NULL);
}
}
return n;
}
int ToolBuildCoefficients(int argc, char *argv[]) {
int n;
double C[8];
GetOpts(argc, argv);
setvbuf(stdout, malloc(PAGESIZE), _IOLBF, PAGESIZE);
if ((n = ReadIdealCoefficients(C, argc - optind, argv + optind))) {
if (flags_.n) Normalize(n, C);
ShowBetterCoefficients(n, C, flags_.L, flags_.H);
}
return 0;
}
int main(int argc, char *argv[]) {
g_loglevel = kLogWarn;
showcrashreports();
return ToolBuildCoefficients(argc, argv);
}

267
tool/build/img2code.c Normal file
View File

@@ -0,0 +1,267 @@
/*-*- 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 2020 Justine Alexandra Roberts Tunney │
│ │
│ This program is free software; you can redistribute it and/or modify │
│ it under the terms of the GNU General Public License as published by │
│ the Free Software Foundation; version 2 of the License. │
│ │
│ This program is distributed in the hope that it will be useful, but │
│ WITHOUT ANY WARRANTY; without even the implied warranty of │
│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
│ General Public License for more details. │
│ │
│ You should have received a copy of the GNU General Public License │
│ along with this program; if not, write to the Free Software │
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "dsp/scale/scale.h"
#include "libc/calls/calls.h"
#include "libc/calls/struct/stat.h"
#include "libc/conv/conv.h"
#include "libc/fmt/bing.h"
#include "libc/limits.h"
#include "libc/log/check.h"
#include "libc/log/log.h"
#include "libc/macros.h"
#include "libc/math.h"
#include "libc/mem/mem.h"
#include "libc/runtime/runtime.h"
#include "libc/stdio/stdio.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/ex.h"
#include "libc/sysv/consts/exit.h"
#include "libc/sysv/consts/map.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/prot.h"
#include "libc/testlib/testlib.h"
#include "third_party/dtoa/dtoa.h"
#include "third_party/getopt/getopt.h"
#include "third_party/stb/stb_image.h"
#include "third_party/xed/x86.h"
#define USAGE \
" [FLAGS] [PATH]\n\
\n\
Example:\n\
\n\
img2code -cw79 -p2 foo.png\n\
\n\
Flags:\n\
-c compress range\n\
-o PATH out\n\
-r FLEX ratio\n\
-w FLEX new width\n\
-h FLEX new height\n\
-p FLEX pixel y ratio\n\
-? shows this information\n\
\n"
static struct Flags {
const char *o;
double r, p;
int w, h;
bool c;
} flags_ = {
.o = "-",
.p = 1,
};
static noreturn void PrintUsage(int rc, FILE *f) {
fprintf(f, "Usage: %s%s", program_invocation_name, USAGE);
exit(rc);
}
static void GetOpts(int *argc, char *argv[]) {
int opt;
if (*argc == 2 &&
(strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-help") == 0)) {
PrintUsage(EXIT_SUCCESS, stdout);
}
while ((opt = getopt(*argc, argv, "?co:r:w:h:p:")) != -1) {
switch (opt) {
case 'c':
flags_.c = true;
break;
case 'o':
flags_.o = optarg;
break;
case 'r':
flags_.r = strtod(optarg, NULL);
break;
case 'p':
flags_.p = strtod(optarg, NULL);
break;
case 'w':
flags_.w = strtol(optarg, NULL, 0);
break;
case 'h':
flags_.h = strtol(optarg, NULL, 0);
break;
case '?':
PrintUsage(EXIT_SUCCESS, stdout);
default:
PrintUsage(EX_USAGE, stderr);
}
}
if (optind == *argc) {
argv[(*argc)++] = "-";
}
}
static void GetOutputGeometry(long syn, long sxn, long *out_dyn, long *out_dxn,
double *out_ry, double *out_rx) {
double ry, rx;
long dyn, dxn;
if (!flags_.h && !flags_.w) {
if (flags_.r) {
ry = flags_.r * flags_.p;
rx = flags_.r;
} else {
ry = flags_.p;
rx = 1;
}
dyn = round(syn / ry);
dxn = round(sxn / rx);
} else if (flags_.w && !flags_.h) {
if (flags_.r) {
rx = 1. * sxn / flags_.w;
ry = flags_.r * flags_.p;
dxn = flags_.w;
dyn = round(syn / ry);
} else {
rx = 1. * sxn / flags_.w;
ry = flags_.p * rx;
dxn = flags_.w;
dyn = round(syn / ry);
}
} else if (flags_.h && !flags_.w) {
if (flags_.r) {
rx = flags_.r;
ry = flags_.p * syn / flags_.h;
dxn = flags_.w;
dyn = round(syn / ry);
} else {
ry = flags_.p * syn / flags_.h;
rx = ry;
dyn = flags_.h;
dxn = round(syn / rx);
}
} else {
ry = flags_.p;
rx = 1;
dyn = round(flags_.h / ry);
dxn = flags_.w;
}
*out_dyn = dyn;
*out_dxn = dxn;
*out_ry = ry;
*out_rx = rx;
}
static void *Deinterlace(long dcn, long dyn, long dxn,
unsigned char dst[dcn][dyn][dxn], long syw, long sxw,
long scw, const unsigned char src[syw][sxw][scw],
long syn, long sxn, long sy0, long sx0, long sc0) {
long y, x, c;
for (y = 0; y < dyn; ++y) {
for (x = 0; x < dxn; ++x) {
for (c = 0; c < dcn; ++c) {
dst[c][y][x] = src[sy0 + y][sx0 + x][sc0 + c];
}
}
}
return dst;
}
static void CompressRange(long cn, long yn, long xn,
unsigned char img[cn][yn][xn]) {
static const char R[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
double f;
long c, y, x, L, H;
L = R[0], H = R[strlen(R) - 1];
for (c = 0; c < cn; ++c) {
for (y = 0; y < yn; ++y) {
for (x = 0; x < xn; ++x) {
f = img[c][y][x];
f /= 255;
f *= H - L;
f += L;
img[c][y][x] = MIN(H, MAX(L, lround(f)));
}
}
}
}
static void PrintCode(FILE *f, long cn, long yn, long xn,
unsigned char img[cn][yn][xn]) {
long c, y, x;
if (flags_.c) CompressRange(cn, yn, xn, img);
for (c = 0; c < cn; ++c) {
fputc('\n', f);
for (y = 0; y < yn; ++y) {
fputc('\n', f);
for (x = 0; x < xn; ++x) {
fputwc(bing(img[c][y][x], 0), f);
}
}
}
fputc('\n', f);
}
static void ProcessImage(const char *path, long scn, long syn, long sxn,
unsigned char img[syn][sxn][scn], FILE *f) {
double ry, rx;
long dyn, dxn, cn;
GetOutputGeometry(syn, sxn, &dyn, &dxn, &ry, &rx);
if (dyn == syn && dxn == sxn) {
/* TODO(jart): Why doesn't Gyarados no-op? */
PrintCode(f, scn, dyn, dxn,
Deinterlace(scn, syn, sxn, gc(memalign(32, scn * syn * sxn)), syn,
sxn, scn, img, syn, sxn, 0, 0, 0));
} else {
PrintCode(
f, scn, dyn, dxn,
EzGyarados(scn, dyn, dxn, gc(memalign(32, scn * dyn * dxn)), scn, syn,
sxn,
Deinterlace(scn, syn, sxn, gc(memalign(32, scn * syn * sxn)),
syn, sxn, scn, img, syn, sxn, 0, 0, 0),
0, scn, dyn, dxn, syn, sxn, ry, rx, 0, 0));
}
}
static void WithImageFile(const char *path, FILE *f,
void fn(const char *path, long cn, long yn, long xn,
unsigned char src[yn][xn][cn], FILE *f)) {
struct stat st;
void *map, *data;
int fd, yn, xn, cn;
CHECK_NE(-1, (fd = open(path, O_RDONLY)), "%s", path);
CHECK_NE(-1, fstat(fd, &st));
CHECK_GT(st.st_size, 0);
CHECK_LE(st.st_size, INT_MAX);
CHECK_NE(MAP_FAILED,
(map = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0)));
CHECK_NOTNULL(
(data = stbi_load_from_memory(map, st.st_size, &xn, &yn, &cn, 0)), "%s",
path);
CHECK_NE(-1, munmap(map, st.st_size));
CHECK_NE(-1, close(fd));
fn(path, cn, yn, xn, data, f);
free(data);
}
int main(int argc, char *argv[]) {
int i;
FILE *f;
showcrashreports();
GetOpts(&argc, argv);
stbi_set_unpremultiply_on_load(true);
CHECK_NOTNULL((f = fopen(flags_.o, "w")));
for (i = optind; i < argc; ++i) {
WithImageFile(argv[i], f, ProcessImage);
}
return fclose(f);
}

View File

@@ -0,0 +1,58 @@
#-*-mode:makefile-gmake;indent-tabs-mode:t;tab-width:8;coding:utf-8-*-┐
#───vi: set et ft=make ts=8 tw=8 fenc=utf-8 :vi───────────────────────┘
PKGS += TOOL_BUILD_LIB
TOOL_BUILD_LIB_ARTIFACTS += TOOL_BUILD_LIB_A
TOOL_BUILD_LIB = $(TOOL_BUILD_LIB_A_DEPS) $(TOOL_BUILD_LIB_A)
TOOL_BUILD_LIB_A = o/$(MODE)/tool/build/lib/buildlib.a
TOOL_BUILD_LIB_A_FILES := $(wildcard tool/build/lib/*)
TOOL_BUILD_LIB_A_HDRS = $(filter %.h,$(TOOL_BUILD_LIB_A_FILES))
TOOL_BUILD_LIB_A_SRCS_S = $(filter %.S,$(TOOL_BUILD_LIB_A_FILES))
TOOL_BUILD_LIB_A_SRCS_C = $(filter %.c,$(TOOL_BUILD_LIB_A_FILES))
TOOL_BUILD_LIB_A_CHECKS = $(TOOL_BUILD_LIB_A).pkg
TOOL_BUILD_LIB_A_SRCS = \
$(TOOL_BUILD_LIB_A_SRCS_S) \
$(TOOL_BUILD_LIB_A_SRCS_C)
TOOL_BUILD_LIB_A_OBJS = \
$(TOOL_BUILD_LIB_A_SRCS:%=o/$(MODE)/%.zip.o) \
$(TOOL_BUILD_LIB_A_SRCS_S:%.S=o/$(MODE)/%.o) \
$(TOOL_BUILD_LIB_A_SRCS_C:%.c=o/$(MODE)/%.o)
TOOL_BUILD_LIB_A_DIRECTDEPS = \
LIBC_CALLS \
LIBC_CALLS_HEFTY \
LIBC_FMT \
LIBC_MEM \
LIBC_NEXGEN32E \
LIBC_RUNTIME \
LIBC_STUBS \
LIBC_SYSV \
LIBC_SYSV_CALLS \
LIBC_LOG \
LIBC_X
TOOL_BUILD_LIB_A_DEPS := \
$(call uniq,$(foreach x,$(TOOL_BUILD_LIB_A_DIRECTDEPS),$($(x))))
$(TOOL_BUILD_LIB_A): \
tool/build/lib/ \
$(TOOL_BUILD_LIB_A).pkg \
$(TOOL_BUILD_LIB_A_OBJS)
$(TOOL_BUILD_LIB_A).pkg: \
$(TOOL_BUILD_LIB_A_OBJS) \
$(foreach x,$(TOOL_BUILD_LIB_A_DIRECTDEPS),$($(x)_A).pkg)
TOOL_BUILD_LIB_LIBS = $(foreach x,$(TOOL_BUILD_LIB_ARTIFACTS),$($(x)))
TOOL_BUILD_LIB_SRCS = $(foreach x,$(TOOL_BUILD_LIB_ARTIFACTS),$($(x)_SRCS))
TOOL_BUILD_LIB_HDRS = $(foreach x,$(TOOL_BUILD_LIB_ARTIFACTS),$($(x)_HDRS))
TOOL_BUILD_LIB_BINS = $(foreach x,$(TOOL_BUILD_LIB_ARTIFACTS),$($(x)_BINS))
TOOL_BUILD_LIB_CHECKS = $(foreach x,$(TOOL_BUILD_LIB_ARTIFACTS),$($(x)_CHECKS))
TOOL_BUILD_LIB_OBJS = $(foreach x,$(TOOL_BUILD_LIB_ARTIFACTS),$($(x)_OBJS))
TOOL_BUILD_LIB_TESTS = $(foreach x,$(TOOL_BUILD_LIB_ARTIFACTS),$($(x)_TESTS))
.PHONY: o/$(MODE)/tool/build/lib
o/$(MODE)/tool/build/lib: $(TOOL_BUILD_LIB_CHECKS)

259
tool/build/lib/elfwriter.c Normal file
View File

@@ -0,0 +1,259 @@
/*-*- 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 2020 Justine Alexandra Roberts Tunney │
│ │
│ This program is free software; you can redistribute it and/or modify │
│ it under the terms of the GNU General Public License as published by │
│ the Free Software Foundation; version 2 of the License. │
│ │
│ This program is distributed in the hope that it will be useful, but │
│ WITHOUT ANY WARRANTY; without even the implied warranty of │
│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
│ General Public License for more details. │
│ │
│ You should have received a copy of the GNU General Public License │
│ along with this program; if not, write to the Free Software │
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/alg/arraylist.h"
#include "libc/assert.h"
#include "libc/bits/safemacros.h"
#include "libc/calls/calls.h"
#include "libc/log/check.h"
#include "libc/macros.h"
#include "libc/mem/mem.h"
#include "libc/runtime/gc.h"
#include "libc/runtime/mappings.h"
#include "libc/runtime/runtime.h"
#include "libc/sysv/consts/map.h"
#include "libc/sysv/consts/mremap.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/ok.h"
#include "libc/sysv/consts/prot.h"
#include "libc/x/x.h"
#include "tool/build/lib/elfwriter.h"
#include "tool/build/lib/interner.h"
static const Elf64_Ehdr kObjHeader = {
.e_ident = {ELFMAG0, ELFMAG1, ELFMAG2, ELFMAG3, ELFCLASS64, ELFDATA2LSB, 1,
ELFOSABI_NONE},
.e_type = ET_REL,
.e_machine = EM_NEXGEN32E,
.e_version = 1,
.e_ehsize = sizeof(Elf64_Ehdr),
.e_shentsize = sizeof(Elf64_Shdr)};
static size_t AppendSection(struct ElfWriter *elf, const char *name,
int sh_type, int sh_flags) {
ssize_t section =
append(elf->shdrs,
(&(Elf64_Shdr){.sh_type = sh_type,
.sh_flags = sh_flags,
.sh_entsize = elf->entsize,
.sh_addralign = elf->addralign,
.sh_offset = sh_type != SHT_NULL ? elf->wrote : 0,
.sh_name = intern(elf->shstrtab, name)}));
CHECK_NE(-1, section);
return section;
}
static size_t FinishSection(struct ElfWriter *elf) {
size_t section = elf->shdrs->i - 1;
elf->shdrs->p[section].sh_size =
elf->wrote - elf->shdrs->p[section].sh_offset;
return section;
}
static struct ElfWriterSymRef AppendSymbol(struct ElfWriter *elf,
const char *name, int st_info,
int st_other, size_t st_value,
size_t st_size, size_t st_shndx,
enum ElfWriterSymOrder slg) {
ssize_t sym =
append(elf->syms[slg], (&(Elf64_Sym){.st_info = st_info,
.st_size = st_size,
.st_value = st_value,
.st_other = st_other,
.st_name = intern(elf->strtab, name),
.st_shndx = st_shndx}));
CHECK_NE(-1, sym);
return (struct ElfWriterSymRef){.slg = slg, .sym = sym};
}
static void MakeRelaSection(struct ElfWriter *elf, size_t section) {
size_t shdr, size;
size = (elf->relas->i - elf->relas->j) * sizeof(Elf64_Rela);
elfwriter_align(elf, alignof(Elf64_Rela), sizeof(Elf64_Rela));
shdr = elfwriter_startsection(
elf,
gc(xasprintf("%s%s", ".rela",
&elf->shstrtab->p[elf->shdrs->p[section].sh_name])),
SHT_RELA, SHF_INFO_LINK);
elf->shdrs->p[shdr].sh_info = section;
elfwriter_reserve(elf, size);
elfwriter_commit(elf, size);
FinishSection(elf);
elf->relas->j = elf->relas->i;
}
static void WriteRelaSections(struct ElfWriter *elf, size_t symtab) {
uint32_t sym;
size_t i, j, k;
Elf64_Rela *rela;
for (j = 0, i = 0; i < elf->shdrs->i; ++i) {
if (elf->shdrs->p[i].sh_type == SHT_RELA) {
elf->shdrs->p[i].sh_link = symtab;
for (rela = (Elf64_Rela *)((char *)elf->map + elf->shdrs->p[i].sh_offset);
rela <
(Elf64_Rela *)((char *)elf->map + (elf->shdrs->p[i].sh_offset +
elf->shdrs->p[i].sh_size));
rela++, j++) {
sym = elf->relas->p[j].symkey.sym;
for (k = 0; k < elf->relas->p[j].symkey.slg; ++k) {
sym += elf->syms[k]->i;
}
rela->r_offset = elf->relas->p[j].offset;
rela->r_info = ELF64_R_INFO(sym, elf->relas->p[j].type);
rela->r_addend = elf->relas->p[j].addend;
}
}
}
assert(j == elf->relas->i);
}
static size_t FlushStrtab(struct ElfWriter *elf, const char *name,
struct Interner *strtab) {
size_t size = strtab->i * sizeof(strtab->p[0]);
elfwriter_align(elf, 1, 0);
AppendSection(elf, ".strtab", SHT_STRTAB, 0);
mempcpy(elfwriter_reserve(elf, size), strtab->p, size);
elfwriter_commit(elf, size);
return FinishSection(elf);
}
static void FlushTables(struct ElfWriter *elf) {
size_t i, size, symtab;
elfwriter_align(elf, alignof(Elf64_Sym), sizeof(Elf64_Sym));
symtab = AppendSection(elf, ".symtab", SHT_SYMTAB, 0);
for (i = 0; i < ARRAYLEN(elf->syms); ++i) {
size = elf->syms[i]->i * sizeof(Elf64_Sym);
memcpy(elfwriter_reserve(elf, size), elf->syms[i]->p, size);
elfwriter_commit(elf, size);
}
FinishSection(elf);
elf->shdrs->p[symtab].sh_link = FlushStrtab(elf, ".strtab", elf->strtab);
elf->ehdr->e_shstrndx = FlushStrtab(elf, ".shstrtab", elf->shstrtab);
WriteRelaSections(elf, symtab);
size = elf->shdrs->i * sizeof(elf->shdrs->p[0]);
elfwriter_align(elf, alignof(elf->shdrs->p[0]), sizeof(elf->shdrs->p[0]));
elf->ehdr->e_shoff = elf->wrote;
elf->ehdr->e_shnum = elf->shdrs->i;
elf->shdrs->p[symtab].sh_info =
elf->syms[kElfWriterSymSection]->i + elf->syms[kElfWriterSymLocal]->i;
mempcpy(elfwriter_reserve(elf, size), elf->shdrs->p, size);
elfwriter_commit(elf, size);
}
struct ElfWriter *elfwriter_open(const char *path, int mode) {
struct ElfWriter *elf;
CHECK_NOTNULL((elf = calloc(1, sizeof(struct ElfWriter))));
CHECK_NOTNULL((elf->path = strdup(path)));
CHECK_NE(-1, asprintf(&elf->tmppath, "%s.%d", elf->path, getpid()));
CHECK_NE(-1, (elf->fd = open(elf->tmppath,
O_CREAT | O_TRUNC | O_RDWR | O_EXCL, mode)));
CHECK_NE(-1, ftruncate(elf->fd, (elf->mapsize = FRAMESIZE)));
CHECK_NE(MAP_FAILED, (elf->map = mmap((void *)(intptr_t)kFixedMappingsStart,
elf->mapsize, PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_FIXED, elf->fd, 0)));
elf->ehdr = memcpy(elf->map, &kObjHeader, (elf->wrote = sizeof(kObjHeader)));
elf->strtab = newinterner();
elf->shstrtab = newinterner();
return elf;
}
void elfwriter_close(struct ElfWriter *elf) {
size_t i;
FlushTables(elf);
CHECK_NE(-1, munmap(elf->map, elf->mapsize));
CHECK_NE(-1, ftruncate(elf->fd, elf->wrote));
CHECK_NE(-1, close(elf->fd));
CHECK_NE(-1, rename(elf->tmppath, elf->path));
freeinterner(elf->shstrtab);
freeinterner(elf->strtab);
free(elf->shdrs->p);
free(elf->relas->p);
for (i = 0; i < ARRAYLEN(elf->syms); ++i) free(elf->syms[i]->p);
free(elf);
}
void elfwriter_align(struct ElfWriter *elf, size_t addralign, size_t entsize) {
elf->entsize = entsize;
elf->addralign = addralign;
elf->wrote = roundup(elf->wrote, addralign);
}
size_t elfwriter_startsection(struct ElfWriter *elf, const char *name,
int sh_type, int sh_flags) {
size_t shdr = AppendSection(elf, name, sh_type, sh_flags);
AppendSymbol(elf, "",
sh_type != SHT_NULL ? ELF64_ST_INFO(STB_LOCAL, STT_SECTION)
: ELF64_ST_INFO(STB_LOCAL, STT_NOTYPE),
STV_DEFAULT, 0, 0, shdr, kElfWriterSymSection);
return shdr;
}
void *elfwriter_reserve(struct ElfWriter *elf, size_t size) {
size_t need, greed;
need = elf->wrote + size;
greed = elf->mapsize;
if (need > greed) {
do {
greed = greed + (greed >> 1);
} while (need > greed);
greed = roundup(greed, FRAMESIZE);
CHECK_NE(-1, ftruncate(elf->fd, greed));
CHECK_NE(MAP_FAILED, mmap((char *)elf->map + elf->mapsize,
greed - elf->mapsize, PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_FIXED, elf->fd, elf->mapsize));
elf->mapsize = greed;
}
return (char *)elf->map + elf->wrote;
}
void elfwriter_commit(struct ElfWriter *elf, size_t size) {
elf->wrote += size;
}
void elfwriter_finishsection(struct ElfWriter *elf) {
size_t section = FinishSection(elf);
if (elf->relas->j < elf->relas->i) MakeRelaSection(elf, section);
}
struct ElfWriterSymRef elfwriter_appendsym(struct ElfWriter *elf,
const char *name, int st_info,
int st_other, size_t st_value,
size_t st_size) {
return AppendSymbol(
elf, name, st_info, st_other, st_value, st_size, elf->shdrs->i - 1,
ELF64_ST_BIND(st_info) == STB_LOCAL ? kElfWriterSymLocal
: kElfWriterSymGlobal);
}
struct ElfWriterSymRef elfwriter_linksym(struct ElfWriter *elf,
const char *name, int st_info,
int st_other) {
return AppendSymbol(elf, name, st_info, st_other, 0, 0, 0,
kElfWriterSymGlobal);
}
void elfwriter_appendrela(struct ElfWriter *elf, uint64_t r_offset,
struct ElfWriterSymRef symkey, uint32_t type,
int64_t r_addend) {
CHECK_NE(-1,
append(elf->relas, (&(struct ElfWriterRela){.type = type,
.symkey = symkey,
.offset = r_offset,
.addend = r_addend})));
}

View File

@@ -0,0 +1,67 @@
#ifndef COSMOPOLITAN_TOOL_BUILD_LIB_ELFWRITER_H_
#define COSMOPOLITAN_TOOL_BUILD_LIB_ELFWRITER_H_
#include "libc/elf/struct/ehdr.h"
#include "libc/elf/struct/rela.h"
#include "libc/elf/struct/shdr.h"
#include "libc/elf/struct/sym.h"
#include "tool/build/lib/interner.h"
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
struct ElfWriter {
char *path;
char *tmppath;
int fd;
void *map;
size_t mapsize;
size_t wrote;
size_t entsize;
size_t addralign;
struct Elf64_Ehdr *ehdr;
struct {
size_t i, n;
Elf64_Shdr *p;
} shdrs[1];
struct ElfWriterSyms {
size_t i, n;
Elf64_Sym *p;
} syms[3][1];
struct {
size_t i, j, n;
struct ElfWriterRela {
uint64_t offset;
struct ElfWriterSymRef {
enum ElfWriterSymOrder {
kElfWriterSymSection,
kElfWriterSymLocal,
kElfWriterSymGlobal
} slg;
uint32_t sym;
} symkey;
uint32_t type;
int64_t addend;
} * p;
} relas[1];
struct Interner *strtab;
struct Interner *shstrtab;
};
struct ElfWriter *elfwriter_open(const char *, int) nodiscard;
void elfwriter_cargoculting(struct ElfWriter *);
void elfwriter_close(struct ElfWriter *);
void elfwriter_align(struct ElfWriter *, size_t, size_t);
size_t elfwriter_startsection(struct ElfWriter *, const char *, int, int);
void *elfwriter_reserve(struct ElfWriter *, size_t);
void elfwriter_commit(struct ElfWriter *, size_t);
void elfwriter_finishsection(struct ElfWriter *);
void elfwriter_appendrela(struct ElfWriter *, uint64_t, struct ElfWriterSymRef,
uint32_t, int64_t);
struct ElfWriterSymRef elfwriter_linksym(struct ElfWriter *, const char *, int,
int);
struct ElfWriterSymRef elfwriter_appendsym(struct ElfWriter *, const char *,
int, int, size_t, size_t);
void elfwriter_yoink(struct ElfWriter *, const char *);
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_TOOL_BUILD_LIB_ELFWRITER_H_ */

View File

@@ -0,0 +1,31 @@
/*-*- 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 2020 Justine Alexandra Roberts Tunney │
│ │
│ This program is free software; you can redistribute it and/or modify │
│ it under the terms of the GNU General Public License as published by │
│ the Free Software Foundation; version 2 of the License. │
│ │
│ This program is distributed in the hope that it will be useful, but │
│ WITHOUT ANY WARRANTY; without even the implied warranty of │
│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
│ General Public License for more details. │
│ │
│ You should have received a copy of the GNU General Public License │
│ along with this program; if not, write to the Free Software │
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/elf/def.h"
#include "tool/build/lib/elfwriter.h"
void elfwriter_cargoculting(struct ElfWriter *elf) {
elfwriter_startsection(elf, "", SHT_NULL, 0);
elfwriter_startsection(elf, ".text", SHT_PROGBITS, SHF_ALLOC | SHF_EXECINSTR);
elfwriter_finishsection(elf);
elfwriter_startsection(elf, ".data", SHT_PROGBITS, SHF_ALLOC | SHF_WRITE);
elfwriter_finishsection(elf);
elfwriter_startsection(elf, ".bss", SHT_NOBITS, SHF_ALLOC | SHF_WRITE);
elfwriter_finishsection(elf);
}

View File

@@ -0,0 +1,33 @@
/*-*- 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 2020 Justine Alexandra Roberts Tunney │
│ │
│ This program is free software; you can redistribute it and/or modify │
│ it under the terms of the GNU General Public License as published by │
│ the Free Software Foundation; version 2 of the License. │
│ │
│ This program is distributed in the hope that it will be useful, but │
│ WITHOUT ANY WARRANTY; without even the implied warranty of │
│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
│ General Public License for more details. │
│ │
│ You should have received a copy of the GNU General Public License │
│ along with this program; if not, write to the Free Software │
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/str/str.h"
#include "tool/build/lib/elfwriter.h"
void elfwriter_yoink(struct ElfWriter *elf, const char *symbol) {
unsigned char *p;
struct ElfWriterSymRef sym;
const unsigned char nopl[8] = "\x0f\x1f\x04\x25\x00\x00\x00\x00";
p = elfwriter_reserve(elf, 8);
memcpy(p, nopl, sizeof(nopl));
sym = elfwriter_linksym(elf, symbol, ELF64_ST_INFO(STB_GLOBAL, STT_OBJECT),
STV_HIDDEN);
elfwriter_appendrela(elf, sizeof(nopl) - 4, sym, R_X86_64_32, 0);
elfwriter_commit(elf, sizeof(nopl));
}

134
tool/build/lib/interner.c Normal file
View File

@@ -0,0 +1,134 @@
/*-*- 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 2020 Justine Alexandra Roberts Tunney │
│ │
│ This program is free software; you can redistribute it and/or modify │
│ it under the terms of the GNU General Public License as published by │
│ the Free Software Foundation; version 2 of the License. │
│ │
│ This program is distributed in the hope that it will be useful, but │
│ WITHOUT ANY WARRANTY; without even the implied warranty of │
│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
│ General Public License for more details. │
│ │
│ You should have received a copy of the GNU General Public License │
│ along with this program; if not, write to the Free Software │
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/alg/arraylist.h"
#include "libc/bits/safemacros.h"
#include "libc/mem/mem.h"
#include "libc/str/knuthmultiplicativehash.h"
#include "libc/str/str.h"
#include "libc/x/x.h"
#include "tool/build/lib/interner.h"
#define kInitialItems 16
struct InternerObject {
struct Interner pool;
size_t i, n;
struct InternerHash {
unsigned hash; /* 0 means empty */
unsigned index;
} * p;
};
static void rehash(struct InternerObject *it) {
size_t i, j, n, step;
struct InternerHash *p;
n = it->n;
p = it->p;
it->p = xcalloc((it->n <<= 1), sizeof(struct InternerHash));
for (i = 0; i < n; ++i) {
if (!p[i].hash) continue;
step = 0;
do {
j = (p[i].hash + step * (step + 1) / 2) & (it->n - 1);
step++;
} while (it->p[j].hash);
memcpy(&it->p[j], &p[i], sizeof(p[i]));
}
free_s(&p);
}
/**
* Creates new interner.
*/
struct Interner *newinterner(void) {
struct InternerObject *it;
it = xcalloc(1, sizeof(*it));
it->p = xcalloc((it->n = kInitialItems), sizeof(*it->p));
it->pool.p = xcalloc((it->pool.n = kInitialItems), sizeof(*it->pool.p));
return &it->pool;
}
/**
* Destroys interner.
*/
void freeinterner(struct Interner *t) {
struct InternerObject *it = (struct InternerObject *)t;
if (it) {
free(it->pool.p);
free(it->p);
free(it);
}
}
/**
* Returns number of unique items interned.
*/
size_t interncount(const struct Interner *t) {
struct InternerObject *it = (struct InternerObject *)t;
return it->i;
}
/**
* Interns object.
*
* @return index into 𝑡→𝑝 holding equal item
* @note use consistent size w/ non-string items
*/
size_t internobj(struct Interner *t, const void *data, size_t size) {
struct InternerObject *it = (struct InternerObject *)t;
unsigned hash;
size_t i, step;
unsigned char *item;
step = 0;
item = data;
hash = max(1, KnuthMultiplicativeHash32(data, size));
do {
/* it is written that triangle probe halts iff i<n/2 && popcount(n)==1 */
i = (hash + step * (step + 1) / 2) & (it->n - 1);
if (it->p[i].hash == hash && it->p[i].index + size <= it->pool.n &&
memcmp(item, &it->pool.p[it->p[i].index], size) == 0) {
return it->p[i].index;
}
step++;
} while (it->p[i].hash);
if (++it->i == (it->n >> 1)) {
rehash(it);
step = 0;
do {
i = (hash + step * (step + 1) / 2) & (it->n - 1);
step++;
} while (it->p[i].hash);
}
it->p[i].hash = hash;
return (it->p[i].index = concat(&it->pool, item, size));
}
/**
* Interns string.
*
* The NUL-terminated string 𝑠 is concatenated to the relocatable
* double-NUL terminated string list 𝑡→𝑝 with de-duplication and
* preservation of insertion order.
*
* @return index into 𝑡→𝑝 holding string equal to 𝑠
*/
size_t intern(struct Interner *t, const char *s) {
return internobj(t, s, strlen(s) + 1);
}

20
tool/build/lib/interner.h Normal file
View File

@@ -0,0 +1,20 @@
#ifndef COSMOPOLITAN_TOOL_BUILD_LIB_INTERNER_H_
#define COSMOPOLITAN_TOOL_BUILD_LIB_INTERNER_H_
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
struct Interner {
size_t i; /* byte size of 𝑝 */
size_t n; /* byte capacity of 𝑝 */
char *p; /* will relocate */
};
struct Interner *newinterner(void) returnsnonnull paramsnonnull();
void freeinterner(struct Interner *);
size_t interncount(const struct Interner *) paramsnonnull();
size_t internobj(struct Interner *, const void *, size_t) paramsnonnull();
size_t intern(struct Interner *, const char *) paramsnonnull();
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_TOOL_BUILD_LIB_INTERNER_H_ */

123
tool/build/lib/persist.c Normal file
View File

@@ -0,0 +1,123 @@
/*-*- 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 2020 Justine Alexandra Roberts Tunney │
│ │
│ This program is free software; you can redistribute it and/or modify │
│ it under the terms of the GNU General Public License as published by │
│ the Free Software Foundation; version 2 of the License. │
│ │
│ This program is distributed in the hope that it will be useful, but │
│ WITHOUT ANY WARRANTY; without even the implied warranty of │
│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
│ General Public License for more details. │
│ │
│ You should have received a copy of the GNU General Public License │
│ along with this program; if not, write to the Free Software │
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/calls/calls.h"
#include "libc/calls/struct/iovec.h"
#include "libc/log/check.h"
#include "libc/macros.h"
#include "libc/nexgen32e/bsr.h"
#include "libc/runtime/gc.h"
#include "libc/sock/sock.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/o.h"
#include "libc/x/x.h"
#include "tool/build/lib/persist.h"
static bool IsWithin(size_t sz1, void *vp1, size_t sz2, void *vp2) {
char *p1 = vp1, *p2 = vp2;
return p1 >= p2 && p1 + sz1 <= p2 + sz2;
}
static bool IsOverlapping(void *vx1, void *vy1, void *vx2, void *vy2) {
char *x1 = vx1, *y1 = vy1, *x2 = vx2, *y2 = vy2;
return (x1 >= x2 && x1 <= y2) || (y1 >= x2 && y1 <= y2);
}
static bool IsOverlappingIov(struct iovec *a, struct iovec *b) {
char *ap = a->iov_base, *bp = b->iov_base;
return IsOverlapping(ap, ap + a->iov_len, bp, bp + b->iov_len);
}
/**
* Writes struct w/ dynamic arrays to mappable file, e.g.
*
* PersistObject(path, 64, &(struct ObjectParam){
* sizeof(*o), o, &o->magic, &o->abi,
* &(struct ObjectArrayParam){
* {&o->a1.i, sizeof(o->a1.p[0]), &o->a1.p},
* {&o->a2.i, sizeof(o->a2.p[0]), &o->a2.p},
* {0},
* }});
*
* @param obj->magic needs to be unique for struct
* @param obj->abi monotonically tracks breaking changes
* @param obj->arrays needs sentinel with item size of zero
* @note non-recursive i.e. array elements can't have pointers
* @see MapObject()
*/
void PersistObject(const char *path, size_t align,
const struct ObjectParam *obj) {
struct iovec *iov;
int i, n, fd, iovlen;
const char *tmp, *pad;
long len, size, bytes, filesize;
unsigned char *hdr, *p1, *p2, **pp;
intptr_t arrayptroffset, arraydataoffset;
filesize = 0;
DCHECK_GE(align, 1);
CHECK_GT(*obj->magic, 0);
CHECK_GT(*obj->abi, 0);
CHECK(IsWithin(sizeof(*obj->magic), obj->magic, obj->size, obj->p));
CHECK(IsWithin(sizeof(*obj->abi), obj->abi, obj->size, obj->p));
for (n = i = 0; obj->arrays[i].size; ++i) ++n;
iovlen = (n + 1) * 2;
pad = gc(xcalloc(align, 1));
hdr = gc(xmalloc(obj->size));
iov = gc(xcalloc(iovlen, sizeof(*iov)));
tmp = gc(xasprintf("%s.%d.%s", path, getpid(), "tmp"));
bytes = obj->size;
iov[0].iov_base = memcpy(hdr, obj->p, obj->size);
iov[0].iov_len = bytes;
iov[1].iov_base = pad;
iov[1].iov_len = ROUNDUP(bytes, align) - bytes;
filesize += ROUNDUP(bytes, align);
for (i = 0; i < n; ++i) {
pp = obj->arrays[i].pp;
len = obj->arrays[i].len;
size = obj->arrays[i].size;
if (!*pp || !len) continue;
p1 = obj->p;
p2 = obj->arrays[i].pp;
arrayptroffset = p2 - p1;
arraydataoffset = filesize;
CHECK((!len || bsrl(len) + bsrl(size) < 31),
"path=%s i=%d len=%,lu size=%,lu", path, i, len, size);
CHECK(IsWithin(sizeof(void *), pp, obj->size, obj->p));
CHECK(!IsOverlapping(pp, pp + sizeof(void *), obj->magic,
obj->magic + sizeof(*obj->magic)));
CHECK(!IsOverlapping(pp, pp + sizeof(void *), obj->abi,
obj->abi + sizeof(*obj->abi)));
memcpy(hdr + arrayptroffset, &arraydataoffset, sizeof(intptr_t));
CHECK_LT(filesize + arraydataoffset, 0x7ffff000);
bytes = len * size;
iov[(i + 1) * 2 + 0].iov_base = *pp;
iov[(i + 1) * 2 + 0].iov_len = bytes;
iov[(i + 1) * 2 + 1].iov_base = pad;
iov[(i + 1) * 2 + 1].iov_len = ROUNDUP(bytes, align) - bytes;
filesize += ROUNDUP(bytes, align);
CHECK(!IsOverlappingIov(&iov[(i + 0) * 2], &iov[(i + 1) * 2]),
"iov[%d]={%#p,%#x}, iov[%d]={%#p,%#x} path=%s", (i + 0) * 2,
iov[(i + 0) * 2].iov_base, iov[(i + 0) * 2].iov_len, (i + 1) * 2,
iov[(i + 1) * 2].iov_base, iov[(i + 1) * 2].iov_len, path);
}
CHECK_NE(-1, (fd = open(tmp, O_CREAT | O_WRONLY | O_EXCL, 0644)), "%s", tmp);
CHECK_EQ(filesize, writev(fd, iov, iovlen));
CHECK_NE(-1, close(fd));
CHECK_NE(-1, rename(tmp, path), "%s", path);
}

22
tool/build/lib/persist.h Normal file
View File

@@ -0,0 +1,22 @@
#ifndef COSMOPOLITAN_TOOL_BUILD_LIB_PERSIST_H_
#define COSMOPOLITAN_TOOL_BUILD_LIB_PERSIST_H_
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
struct ObjectParam {
size_t size;
void *p;
uint32_t *magic;
int32_t *abi;
struct ObjectArrayParam {
size_t len;
size_t size;
void *pp;
} * arrays;
};
void PersistObject(const char *, size_t, const struct ObjectParam *);
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_TOOL_BUILD_LIB_PERSIST_H_ */

184
tool/build/lz4toasm.c Normal file
View File

@@ -0,0 +1,184 @@
/*-*- 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 2020 Justine Alexandra Roberts Tunney │
│ │
│ This program is free software; you can redistribute it and/or modify │
│ it under the terms of the GNU General Public License as published by │
│ the Free Software Foundation; version 2 of the License. │
│ │
│ This program is distributed in the hope that it will be useful, but │
│ WITHOUT ANY WARRANTY; without even the implied warranty of │
│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
│ General Public License for more details. │
│ │
│ You should have received a copy of the GNU General Public License │
│ along with this program; if not, write to the Free Software │
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/calls/calls.h"
#include "libc/calls/struct/stat.h"
#include "libc/conv/conv.h"
#include "libc/log/check.h"
#include "libc/log/log.h"
#include "libc/macros.h"
#include "libc/mem/mem.h"
#include "libc/nexgen32e/kompressor.h"
#include "libc/nexgen32e/lz4.h"
#include "libc/runtime/ezmap.h"
#include "libc/runtime/gc.h"
#include "libc/runtime/runtime.h"
#include "libc/stdio/stdio.h"
#include "libc/str/str.h"
#include "libc/x/x.h"
#include "third_party/getopt/getopt.h"
/**
* @fileoverview LZ4 content embedder.
*
* This tool converts an LZ4-compressed file into assembly source code,
* that will appear to C/C++ code as a global constant holding the
* uncompressed contents, which is unpacked automatically.
*
* @note this is a build tool that assumes input data is trustworthy
*/
#define COLS 8
struct stat st_;
size_t extractedsize;
noreturn void usage(char *argv[], FILE *f, int rc) {
fprintf(f, "%s: %s [-o %s] [-s %s] %s\n", "Usage", argv[0], "PATH", "SYMBOL",
"FILE");
exit(rc);
}
int main(int argc, char *argv[]) {
const char *symbol = "kData";
const char *lz4path = "/dev/stdin";
const char *outpath = "/dev/stdout";
const char *initprio = "400,_init_";
const unsigned char *lz4data;
int opt;
FILE *fin, *fout;
showcrashreports();
while ((opt = getopt(argc, argv, "ho:s:z:")) != -1) {
switch (opt) {
case 's':
symbol = optarg;
break;
case 'o':
outpath = optarg;
break;
case 'z':
extractedsize = strtoul(optarg, NULL, 0);
break;
case 'h':
case '?':
usage(argv, stdout, 0);
default:
usage(argv, stderr, 1);
}
}
if (argc - optind) {
lz4path = argv[optind];
}
CHECK_NOTNULL((fin = fopen(lz4path, "r")));
CHECK_NE(-1, fstat(fileno(fin), &st_));
CHECK_NOTNULL((lz4data = malloc(st_.st_size)));
CHECK_EQ(1, fread(lz4data, st_.st_size, 1, fin));
CHECK_NE(-1, fclose(fin));
CHECK_NOTNULL((fout = fopen(outpath, "w")));
CHECK_EQ(LZ4_MAGICNUMBER, LZ4_MAGIC(lz4data));
CHECK_EQ(1, LZ4_FRAME_VERSION(lz4data));
const unsigned char *frame = /* lz4check( */ lz4data /* ) */;
const unsigned char *block1 = frame + LZ4_FRAME_HEADERSIZE(frame);
const unsigned char *block2 = block1 + LZ4_BLOCK_SIZE(frame, block1);
CHECK(LZ4_BLOCK_ISCOMPRESSED(block1));
CHECK(LZ4_BLOCK_ISEOF(block2));
const unsigned char *data = LZ4_BLOCK_DATA(block1);
size_t size = LZ4_BLOCK_DATASIZE(block1);
fprintf(fout,
"/\t%s -o %s -s %s %s\n"
".include \"libc/macros.inc\"\n"
"\n",
argv[0], outpath, symbol, lz4path);
if (!extractedsize) {
if (LZ4_FRAME_BLOCKCONTENTSIZEFLAG(frame)) {
extractedsize = LZ4_FRAME_BLOCKCONTENTSIZE(frame);
} else {
fprintf(stderr, "error: need extractedsize\n");
exit(1);
}
}
size_t bss = ROUNDUP(extractedsize, 8);
size_t misalign = bss - extractedsize;
fprintf(fout, "\t.rodata\n");
fprintf(fout, "\t.align\t4\n");
fprintf(fout, "%sLen:\n", symbol);
fprintf(fout, "\t.long\t%lu\n", extractedsize);
fprintf(fout, "\t.endobj\t%sLen,globl,hidden\n", symbol);
fprintf(fout, "\t.previous\n");
fprintf(fout, "\n");
fprintf(fout, "\t.initbss %s%s\n", initprio, symbol);
fprintf(fout, "%s:\n", symbol);
fprintf(fout, "\t.zero\t%lu\n", bss);
fprintf(fout, "\t.endobj\t%s,globl,hidden\n", symbol);
fprintf(fout, "\t.previous\n");
fprintf(fout, "\n");
fprintf(fout, "\t.init.start %s%s\n", initprio, symbol);
fprintf(fout, "\tpush\t%%rsi\n");
fprintf(fout, "\tmov\t$%u,%%edx\n", size);
fprintf(fout, "\tcall\tlz4cpy\n");
if (misalign) {
fprintf(fout, "\tlea\t%zu(%%rax),%%rdi\n", misalign);
} else {
fprintf(fout, "\tmov\t%%rax,%%rdi\n");
}
fprintf(fout, "\tpop\t%%rsi\n");
fprintf(fout, "\tadd\t$%u,%%rsi\n", ROUNDUP(size, 8));
fprintf(fout, "\t.init.end %s%s\n", initprio, symbol);
fprintf(fout, "\n");
fprintf(fout, "\t.initro %s%s\n", initprio, symbol);
fprintf(fout, "%sLz4:\n", symbol);
int col = 0;
char16_t glyphs[COLS + 1];
for (unsigned i = 0; i < size; ++i) {
unsigned char ch = data[i];
if (col == 0) {
fprintf(fout, "\t.byte\t");
memset(glyphs, 0, sizeof(glyphs));
}
/* TODO(jart): Fix Emacs */
glyphs[col] = kCp437[ch == '"' || ch == '#' ? '.' : ch];
if (col) fputc(',', fout);
fprintf(fout, "0x%02x", ch);
if (++col == COLS) {
col = 0;
fprintf(fout, "\t#%hs\n", glyphs);
}
}
while (col++ != COLS) {
fprintf(fout, ",0x00");
}
fprintf(fout, "\n");
fprintf(fout, "\t.endobj\t%sLz4,globl,hidden\n", symbol);
fprintf(fout, "\t.previous\n");
return fclose(fout);
}

314
tool/build/mkdeps.c Normal file
View File

@@ -0,0 +1,314 @@
/*-*- 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 2020 Justine Alexandra Roberts Tunney │
│ │
│ This program is free software; you can redistribute it and/or modify │
│ it under the terms of the GNU General Public License as published by │
│ the Free Software Foundation; version 2 of the License. │
│ │
│ This program is distributed in the hope that it will be useful, but │
│ WITHOUT ANY WARRANTY; without even the implied warranty of │
│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
│ General Public License for more details. │
│ │
│ You should have received a copy of the GNU General Public License │
│ along with this program; if not, write to the Free Software │
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/alg/alg.h"
#include "libc/alg/arraylist.h"
#include "libc/alg/arraylist2.h"
#include "libc/alg/bisectcarleft.h"
#include "libc/assert.h"
#include "libc/bits/bits.h"
#include "libc/bits/safemacros.h"
#include "libc/calls/calls.h"
#include "libc/errno.h"
#include "libc/fmt/fmt.h"
#include "libc/log/check.h"
#include "libc/log/log.h"
#include "libc/macros.h"
#include "libc/runtime/ezmap.h"
#include "libc/runtime/gc.h"
#include "libc/runtime/mappings.h"
#include "libc/runtime/runtime.h"
#include "libc/stdio/stdio.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/madv.h"
#include "libc/sysv/consts/map.h"
#include "libc/sysv/consts/prot.h"
#include "libc/x/x.h"
#include "third_party/getopt/getopt.h"
/**
* @fileoverview Make dependency generator.
*
* This program generates Makefile code saying which sources include
* which headers, thus improving build invalidation.
*
* The same thing can be accomplished using GCC's -M flag. This tool is
* much faster. It's designed to process over 9,000 sources to generate
* 50k+ lines of make code in ~80ms using one core and a meg of ram.
*/
static const char *const kSourceExts[] = {".s", ".S", ".c", ".cc", ".cpp"};
static alignas(16) const char kIncludePrefix[] = "include \"";
static const char *const kModelessPackages[] = {
"libc/nt/",
"libc/stubs/",
"libc/sysv/",
};
struct Strings {
size_t i, n;
char *p;
};
struct Source {
uint32_t hash; /* 0 means empty w/ triangle probe */
uint32_t name; /* strings.p[name] w/ interning */
uint32_t id; /* rehashing changes indexes */
};
struct Edge {
int32_t from; /* sources.p[from.id] */
int32_t to; /* sources.p[to.id] */
};
struct Sources {
size_t i, n; /* phase 1: hashmap: popcount(n)==1 if n */
struct Source *p; /* phase 2: arraylist sorted by id */
};
struct Edges {
size_t i, n;
struct Edge *p;
};
int g_sourceid;
struct Strings strings;
struct Sources sources;
struct Edges edges;
const char *buildroot;
int *visited;
char *out;
FILE *fout;
static int CompareSourcesById(struct Source *a, struct Source *b) {
return a->id > b->id ? 1 : a->id < b->id ? -1 : 0;
}
static int CompareEdgesByFrom(struct Edge *a, struct Edge *b) {
return a->from > b->from ? 1 : a->from < b->from ? -1 : 0;
}
static uint32_t Hash(const void *s, size_t l) {
return max(1, crc32c(0, s, l));
}
void Crunch(void) {
size_t i, j;
for (i = 0, j = 0; j < sources.n; ++j) {
if (!sources.p[j].hash) continue;
if (i != j) memcpy(&sources.p[i], &sources.p[j], sizeof(sources.p[j]));
i++;
}
sources.i = i;
qsort(sources.p, sources.i, sizeof(*sources.p), (void *)CompareSourcesById);
qsort(edges.p, edges.i, sizeof(*edges.p), (void *)CompareEdgesByFrom);
}
void Rehash(void) {
size_t i, j, step;
struct Sources old;
memcpy(&old, &sources, sizeof(sources));
sources.n = sources.n ? sources.n << 1 : 16;
sources.p = calloc(sources.n, sizeof(struct Source));
for (i = 0; i < old.n; ++i) {
if (!old.p[i].hash) continue;
step = 0;
do {
j = (old.p[i].hash + step * (step + 1) / 2) & (sources.n - 1);
step++;
} while (sources.p[j].hash);
memcpy(&sources.p[j], &old.p[i], sizeof(old.p[i]));
}
free(old.p);
}
uint32_t GetSourceId(const char *name, size_t len) {
size_t i, step;
uint32_t hash = Hash(name, len);
if (sources.n) {
step = 0;
do {
i = (hash + step * (step + 1) / 2) & (sources.n - 1);
if (sources.p[i].hash == hash &&
memcmp(name, &strings.p[sources.p[i].name], len) == 0) {
return sources.p[i].id;
}
step++;
} while (sources.p[i].hash);
}
if (++sources.i >= (sources.n >> 1)) {
Rehash();
step = 0;
do {
i = (hash + step * (step + 1) / 2) & (sources.n - 1);
step++;
} while (sources.p[i].hash);
}
sources.p[i].hash = hash;
sources.p[i].name = concat(&strings, name, len);
strings.p[strings.i++] = '\0';
return (sources.p[i].id = g_sourceid++);
}
void LoadRelationships(int argc, char *argv[]) {
struct MappedFile mf;
const char *p, *pe;
size_t i, linecap = 0;
char *line = NULL;
FILE *finpaths;
for (i = optind; i < argc; ++i) {
CHECK_NOTNULL((finpaths = fopen(argv[i], "r")));
while (getline(&line, &linecap, finpaths) != -1) {
if (mapfileread(chomp(line), &mf) == -1) {
CHECK_EQ(ENOENT, errno, "%s", line);
/*
* This code helps GNU Make automatically fix itself when we
* delete a source file. It removes o/.../srcs.txt or
* o/.../hdrs.txt and exits nonzero. Since we use hyphen
* notation on mkdeps related rules, the build will
* automatically restart itself.
*/
fprintf(stderr, "%s %s...\n", "Refreshing", argv[i]);
unlink(argv[i]);
exit(1);
}
if (mf.size) {
if (mf.size > PAGESIZE) {
madvise(mf.addr, mf.size, MADV_WILLNEED | MADV_SEQUENTIAL);
}
uint32_t sauce = GetSourceId(line, strlen(line));
size_t inclen = strlen(kIncludePrefix);
p = mf.addr;
pe = p + mf.size;
while ((p = strstr(p, kIncludePrefix))) {
const char *path = p + inclen;
const char *pathend = memchr(path, '"', pe - path);
if (pathend && (intptr_t)p > (intptr_t)mf.addr &&
(p[-1] == '#' || p[-1] == '.') &&
(p - 1 == mf.addr || p[-2] == '\n')) {
uint32_t dependency = GetSourceId(path, pathend - path);
struct Edge edge;
edge.from = sauce;
edge.to = dependency;
append(&edges, &edge);
}
p = path;
}
}
CHECK_NE(-1, unmapfile(&mf));
}
CHECK_NE(-1, fclose(finpaths));
}
free_s(&line);
}
void GetOpts(int argc, char *argv[]) {
int opt;
while ((opt = getopt(argc, argv, "ho:r:")) != -1) {
switch (opt) {
case 'o':
out = optarg;
break;
case 'r':
buildroot = optarg;
break;
default:
fprintf(stderr, "%s: %s [-r %s] [-o %s] [%s...]\n", "Usage", argv[0],
"BUILDROOT", "OUTPUT", "PATHSFILE");
exit(1);
}
}
if (isempty(out)) fprintf(stderr, "need -o FILE"), exit(1);
if (isempty(buildroot)) fprintf(stderr, "need -r o/$(MODE)"), exit(1);
}
const char *StripExt(const char *s) {
static bool once;
static size_t i, n;
static char *p, *dot;
if (!once) {
once = true;
__cxa_atexit(free_s, &p, NULL);
}
i = 0;
CONCAT(&p, &i, &n, s, strlen(s));
dot = strrchr(p, '.');
if (dot) *dot = '\0';
return p;
}
bool IsObjectSource(const char *name) {
for (size_t i = 0; i < ARRAYLEN(kSourceExts); ++i) {
if (endswith(name, kSourceExts[i])) return true;
}
return false;
}
void Dive(uint32_t sauce) {
for (uint32_t i = bisectcarleft((const int32_t(*)[2])edges.p, edges.i, sauce);
edges.p[i].from == sauce; ++i) {
int32_t dep = edges.p[i].to;
if (bts(visited, dep)) continue;
fputs(" \\\n\t", fout);
fputs(&strings.p[sources.p[dep].name], fout);
Dive(dep);
}
}
bool IsModeless(const char *path) {
size_t i;
for (i = 0; i < ARRAYLEN(kModelessPackages); ++i) {
if (startswith(path, kModelessPackages[i])) return true;
}
return false;
}
int main(int argc, char *argv[]) {
out = "/dev/stdout";
GetOpts(argc, argv);
char *tmp =
!fileexists(out) || isregularfile(out) ? xasprintf("%s.tmp", out) : NULL;
CHECK_NOTNULL((fout = fopen(tmp ? tmp : out, "w")));
LoadRelationships(argc, argv);
Crunch();
size_t bitmaplen = roundup((sources.i + 8) / 8, 4);
visited = malloc(bitmaplen);
for (size_t i = 0; i < sources.i; ++i) {
const char *path = &strings.p[sources.p[i].name];
if (!IsObjectSource(path)) continue;
bool needprefix = !startswith(path, "o/");
const char *prefix = !needprefix ? "" : IsModeless(path) ? "o/" : buildroot;
fprintf(fout, "\n%s%s.o: \\\n\t%s", prefix, StripExt(path), path);
memset(visited, 0, bitmaplen);
bts(visited, i);
Dive(i);
fprintf(fout, "\n");
}
if (fclose(fout) == -1) perror(out), exit(1);
if (tmp) {
if (rename(tmp, out) == -1) perror(out), exit(1);
}
free_s(&strings.p);
free_s(&sources.p);
free_s(&edges.p);
free_s(&visited);
free_s(&tmp);
return 0;
}

665
tool/build/package.c Normal file
View File

@@ -0,0 +1,665 @@
/*-*- 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 2020 Justine Alexandra Roberts Tunney │
│ │
│ This program is free software; you can redistribute it and/or modify │
│ it under the terms of the GNU General Public License as published by │
│ the Free Software Foundation; version 2 of the License. │
│ │
│ This program is distributed in the hope that it will be useful, but │
│ WITHOUT ANY WARRANTY; without even the implied warranty of │
│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
│ General Public License for more details. │
│ │
│ You should have received a copy of the GNU General Public License │
│ along with this program; if not, write to the Free Software │
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/alg/alg.h"
#include "libc/alg/arraylist.h"
#include "libc/alg/bisect.h"
#include "libc/alg/bisectcarleft.h"
#include "libc/assert.h"
#include "libc/bits/bits.h"
#include "libc/bits/safemacros.h"
#include "libc/calls/calls.h"
#include "libc/calls/struct/stat.h"
#include "libc/conv/conv.h"
#include "libc/conv/sizemultiply.h"
#include "libc/elf/def.h"
#include "libc/elf/elf.h"
#include "libc/elf/struct/rela.h"
#include "libc/errno.h"
#include "libc/log/check.h"
#include "libc/log/log.h"
#include "libc/macros.h"
#include "libc/mem/mem.h"
#include "libc/nexgen32e/bsr.h"
#include "libc/nexgen32e/kompressor.h"
#include "libc/nt/enum/fileflagandattributes.h"
#include "libc/runtime/gc.h"
#include "libc/runtime/runtime.h"
#include "libc/sock/sock.h"
#include "libc/stdio/stdio.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/map.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/prot.h"
#include "libc/time/time.h"
#include "libc/x/x.h"
#include "third_party/getopt/getopt.h"
#include "third_party/xed/x86.h"
#include "third_party/zlib/zlib.h"
#include "tool/build/lib/elfwriter.h"
#include "tool/build/lib/persist.h"
/**
* @fileoverview Build Package Script.
*
* FIRST PURPOSE
*
* This script verifies the well-formedness of dependencies, e.g.
*
* o/tool/build/package.com \
* -o o/libc/stubs/stubs.pkg \
* o/libc/stubs/{a,b,...}.o
*
* o/tool/build/package.com \
* -o o/libc/nexgen32e/nexgen32e.pkg \
* -d o/libc/stubs/stubs.pkg \
* o/libc/nexgen32e/{a,b,...}.o
*
* We want the following:
*
* 1. FOO declares in FOO_DIRECTDEPS where its undefined symbols are.
* 2. FOO_DIRECTDEPS is complete, so FOO FOO_DIRECTDEPS has no UNDEFs.
* 3. FOO_DIRECTDEPS is non-transitive; thus this tool is incremental.
* 4. Package relationships on a whole are acyclic.
*
* These rules help keep the structure of large codebases easy to
* understand. More importantly, it allows us to further optimize
* compiled objects very cheaply as the build progresses.
*
* SECOND PURPOSE
*
* We want all storage to be thread-local storage. So we change
* RIP-relative instructions to be RBX-relative, only when they
* reference sections in the binary mutable after initialization.
*
* This is basically what the Go language does to implement its fiber
* multiprocessing model. We can have this in C by appropriating all the
* work folks put into enriching GNU C with WIN32 and ASLR lool.
*
* THIRD PURPOSE
*
* Compress read-only data sections of particularly low entropy, using
* the most appropriate directly-linked algorithm and then inject code
* into _init() that calls it. If the data is extremely low energy, we
* will inject code for merging page table entries too. The overcommit
* here is limitless.
*/
#define PACKAGE_MAGIC bswap_32(0xBEEFBEEFu)
#define PACKAGE_ABI 1
struct Packages {
size_t i, n;
struct Package {
uint32_t magic;
int32_t abi;
uint32_t path; /* pkg->strings.p[path] */
int64_t fd; /* not persisted */
void *addr; /* not persisted */
size_t size; /* not persisted */
struct Strings {
size_t i, n;
char *p; /* persisted as pkg+RVA */
} strings; /* TODO(jart): interning? */
struct Objects {
size_t i, n;
struct Object {
uint32_t path; /* pkg->strings.p[path] */
int64_t fd; /* not persisted */
struct Elf64_Ehdr *elf; /* not persisted */
size_t size; /* not persisted */
char *strs; /* not persisted */
Elf64_Sym *syms; /* not persisted */
Elf64_Xword symcount; /* not persisted */
struct Sections {
size_t i, n;
struct Section {
enum SectionKind {
kUndef,
kText,
kData,
kPiroRelo,
kPiroData,
kPiroBss,
kBss,
} kind;
struct Ops {
size_t i, n;
struct Op {
int32_t offset;
uint8_t decoded_length;
uint8_t pos_disp;
uint16_t __pad;
} * p;
} ops;
} * p;
} sections; /* not persisted */
} * p; /* persisted as pkg+RVA */
} objects;
struct Symbols {
size_t i, n;
struct Symbol {
uint32_t name; /* pkg->strings.p[name] */
enum SectionKind kind : 8;
uint8_t bind : 4;
uint8_t type : 4;
uint16_t object; /* pkg->objects.p[object] */
} * p; /* persisted as pkg+RVA */
} symbols, undefs; /* TODO(jart): hash undefs? */
} * *p; /* persisted across multiple files */
};
int CompareSymbolName(const struct Symbol *a, const struct Symbol *b,
const char *strs[hasatleast 2]) {
return strcmp(&strs[0][a->name], &strs[1][b->name]);
}
struct Package *LoadPackage(const char *path) {
int fd;
ssize_t i;
struct stat st;
struct Package *pkg;
CHECK(fileexists(path), "%s: %s: %s\n", "error", path, "not found");
CHECK_NE(-1, (fd = open(path, O_RDONLY)), "%s", path);
CHECK_NE(-1, fstat(fd, &st));
CHECK_NE(MAP_FAILED, (pkg = mmap(NULL, st.st_size, PROT_READ | PROT_WRITE,
MAP_PRIVATE, fd, 0)));
CHECK_NE(-1, close(fd));
CHECK_EQ(PACKAGE_MAGIC, pkg->magic, "corrupt package: %`'s", path);
pkg->strings.p = (char *)((intptr_t)pkg->strings.p + (intptr_t)pkg);
pkg->objects.p = (struct Object *)((intptr_t)pkg->objects.p + (intptr_t)pkg);
pkg->symbols.p = (struct Symbol *)((intptr_t)pkg->symbols.p + (intptr_t)pkg);
CHECK(strcmp(path, &pkg->strings.p[pkg->path]) == 0,
"corrupt package: %`'s pkg=%p strings=%p", path, pkg, pkg->strings.p);
pkg->addr = pkg;
pkg->size = st.st_size;
return pkg;
}
void AddDependency(struct Packages *deps, const char *path) {
struct Package *pkg;
pkg = LoadPackage(path);
CHECK_NE(-1, append(deps, &pkg));
}
void WritePackage(struct Package *pkg) {
CHECK_NE(0, PACKAGE_MAGIC);
pkg->magic = PACKAGE_MAGIC;
pkg->abi = PACKAGE_ABI;
DEBUGF("%s has %,ld objects, %,ld symbols, and a %,ld byte string table",
&pkg->strings.p[pkg->path], pkg->objects.i, pkg->symbols.i,
pkg->strings.i);
PersistObject(
&pkg->strings.p[pkg->path], 64,
&(struct ObjectParam){
sizeof(struct Package),
pkg,
&pkg->magic,
&pkg->abi,
(struct ObjectArrayParam[]){
{pkg->strings.i, sizeof(pkg->strings.p[0]), &pkg->strings.p},
{pkg->objects.i, sizeof(pkg->objects.p[0]), &pkg->objects.p},
{pkg->symbols.i, sizeof(pkg->symbols.p[0]), &pkg->symbols.p},
{0},
},
});
}
void GetOpts(struct Package *pkg, struct Packages *deps, int argc,
char *argv[]) {
long i, si, opt;
pkg->path = -1;
while ((opt = getopt(argc, argv, "vho:d:")) != -1) {
switch (opt) {
case 'v':
g_loglevel = kLogDebug;
break;
case 'o':
pkg->path = concat(&pkg->strings, optarg, strlen(optarg) + 1);
break;
case 'd':
AddDependency(deps, optarg);
break;
default:
fprintf(stderr, "%s: %s [%s %s] [%s %s] %s\n", "Usage",
program_invocation_name, "-o", "OUTPACKAGE", "-d", "DEPPACKAGE",
"OBJECT...");
exit(1);
}
}
CHECK_NE(-1, pkg->path);
CHECK_LT(optind, argc,
"no objects passed to package.com; "
"is your foo.mk $(FOO_OBJS) glob broken?");
for (i = optind; i < argc; ++i) {
CHECK_NE(-1, (si = concat(&pkg->strings, argv[i], strlen(argv[i]) + 1)));
CHECK_NE(-1, append(&pkg->objects, (&(struct Object){si})));
}
}
void IndexSections(struct Object *obj) {
size_t i;
struct Op op;
const char *name;
const uint8_t *code;
struct Section sect;
const Elf64_Shdr *shdr;
struct XedDecodedInst xedd;
for (i = 0; i < obj->elf->e_shnum; ++i) {
memset(&sect, 0, sizeof(sect));
CHECK_NOTNULL((shdr = getelfsectionheaderaddress(obj->elf, obj->size, i)));
if (shdr->sh_type != SHT_NULL) {
CHECK_NOTNULL((name = getelfsectionname(obj->elf, obj->size, shdr)));
if (startswith(name, ".sort.")) name += 5;
if ((strcmp(name, ".piro.relo") == 0 ||
startswith(name, ".piro.relo.")) ||
(strcmp(name, ".data.rel.ro") == 0 ||
startswith(name, ".data.rel.ro."))) {
sect.kind = kPiroRelo;
} else if (strcmp(name, ".piro.data") == 0 ||
startswith(name, ".piro.data.")) {
sect.kind = kPiroData;
} else if (strcmp(name, ".piro.bss") == 0 ||
startswith(name, ".piro.bss.")) {
sect.kind = kPiroBss;
} else if (strcmp(name, ".data") == 0 || startswith(name, ".data.")) {
sect.kind = kData;
} else if (strcmp(name, ".bss") == 0 || startswith(name, ".bss.")) {
sect.kind = kBss;
} else {
sect.kind = kText;
}
} else {
sect.kind = kUndef; /* should always and only be section #0 */
}
if (shdr->sh_flags & SHF_EXECINSTR) {
CHECK_NOTNULL((code = getelfsectionaddress(obj->elf, obj->size, shdr)));
for (op.offset = 0; op.offset < shdr->sh_size;
op.offset += op.decoded_length) {
if (xed_instruction_length_decode(
xed_decoded_inst_zero_set_mode(&xedd, XED_MACHINE_MODE_LONG_64),
&code[op.offset],
min(shdr->sh_size - op.offset, XED_MAX_INSTRUCTION_BYTES)) ==
XED_ERROR_NONE) {
op.decoded_length = xedd.decoded_length;
op.pos_disp = xedd.operands.pos_disp;
} else {
op.decoded_length = 1;
op.pos_disp = 0;
}
CHECK_NE(-1, append(&sect.ops, &op));
}
}
CHECK_NE(-1, append(&obj->sections, &sect));
}
}
enum SectionKind ClassifySection(struct Object *obj, uint8_t type,
Elf64_Section shndx) {
if (type == STT_COMMON) return kBss;
if (!obj->sections.i) return kText;
return obj->sections.p[min(max(0, shndx), obj->sections.i - 1)].kind;
}
void LoadSymbols(struct Package *pkg, uint32_t object) {
Elf64_Xword i;
const char *name;
struct Object *obj;
struct Symbol symbol;
obj = &pkg->objects.p[object];
symbol.object = object;
for (i = 0; i < obj->symcount; ++i) {
symbol.bind = ELF64_ST_BIND(obj->syms[i].st_info);
symbol.type = ELF64_ST_TYPE(obj->syms[i].st_info);
if (symbol.bind != STB_LOCAL &&
(symbol.type == STT_OBJECT || symbol.type == STT_FUNC ||
symbol.type == STT_COMMON || symbol.type == STT_NOTYPE)) {
name = getelfstring(obj->elf, obj->size, obj->strs, obj->syms[i].st_name);
DEBUGF("%s", name);
if (strcmp(name, "_GLOBAL_OFFSET_TABLE_") != 0) {
symbol.kind = ClassifySection(obj, symbol.type, obj->syms[i].st_shndx);
CHECK_NE(-1,
(symbol.name = concat(&pkg->strings, name, strlen(name) + 1)));
CHECK_NE(-1,
append(symbol.kind != kUndef ? &pkg->symbols : &pkg->undefs,
&symbol));
}
}
}
}
void OpenObject(struct Package *pkg, struct Object *obj, int mode, int prot,
int flags) {
int fd;
struct stat st;
CHECK_NE(-1, (fd = open(&pkg->strings.p[obj->path], mode)), "path=%`'s",
&pkg->strings.p[obj->path]);
CHECK_NE(-1, fstat(fd, &st));
CHECK_NE(MAP_FAILED, (obj->elf = mmap(NULL, (obj->size = st.st_size), prot,
flags, fd, 0)));
CHECK_NE(-1, close(fd));
CHECK(iself64binary(obj->elf, obj->size));
CHECK_NOTNULL((obj->strs = getelfstringtable(obj->elf, obj->size)));
CHECK_NOTNULL(
(obj->syms = getelfsymboltable(obj->elf, obj->size, &obj->symcount)));
CHECK_NE(0, obj->symcount);
IndexSections(obj);
}
void CloseObject(struct Object *obj) {
CHECK_NE(-1, munmap(obj->elf, obj->size));
}
void LoadObjects(struct Package *pkg) {
size_t i;
struct Object *obj;
for (i = 0; i < pkg->objects.i; ++i) {
obj = &pkg->objects.p[i];
OpenObject(pkg, obj, O_RDONLY, PROT_READ, MAP_SHARED);
LoadSymbols(pkg, i);
CloseObject(obj);
}
qsort_r(&pkg->symbols.p[0], pkg->symbols.i, sizeof(pkg->symbols.p[0]),
(void *)CompareSymbolName,
(const char *[2]){pkg->strings.p, pkg->strings.p});
}
bool FindSymbol(const char *name, struct Package *pkg,
struct Packages *directdeps, struct Package **out_pkg,
struct Symbol **out_sym) {
size_t i;
struct Package *dep;
struct Symbol key, *sym;
key.name = 0;
if ((sym = bisect(&key, &pkg->symbols.p[0], pkg->symbols.i,
sizeof(pkg->symbols.p[0]), (void *)CompareSymbolName,
(const char *[2]){name, pkg->strings.p}))) {
if (out_pkg) *out_pkg = pkg;
if (out_sym) *out_sym = sym;
return true;
}
for (i = 0; i < directdeps->i; ++i) {
dep = directdeps->p[i];
if ((sym = bisect(&key, &dep->symbols.p[0], dep->symbols.i,
sizeof(dep->symbols.p[0]), (void *)CompareSymbolName,
(const char *[2]){name, dep->strings.p}))) {
if (out_pkg) *out_pkg = dep;
if (out_sym) *out_sym = sym;
return true;
}
}
return false;
}
void CheckStrictDeps(struct Package *pkg, struct Packages *deps) {
size_t i, j;
struct Package *dep;
struct Symbol *undef;
for (i = 0; i < pkg->undefs.i; ++i) {
undef = &pkg->undefs.p[i];
if (undef->bind == STB_WEAK) continue;
if (!FindSymbol(&pkg->strings.p[undef->name], pkg, deps, NULL, NULL)) {
fprintf(stderr, "%s: %s (%s) %s %s\n", "error",
&pkg->strings.p[undef->name],
&pkg->strings.p[pkg->objects.p[undef->object].path],
"not defined by direct deps of", &pkg->strings.p[pkg->path]);
for (j = 0; j < deps->i; ++j) {
dep = deps->p[j];
fputc('\t', stderr);
fputs(&dep->strings.p[dep->path], stderr);
fputc('\n', stderr);
}
exit(1);
}
}
free(pkg->undefs.p);
memset(&pkg->undefs, 0, sizeof(pkg->undefs));
}
forceinline bool IsRipRelativeModrm(uint8_t modrm) {
return (modrm & 0b11000111) == 0b00000101;
}
forceinline uint8_t ChangeRipToRbx(uint8_t modrm) {
return (modrm & 0b00111000) | 0b10000011;
}
void OptimizeRelocations(struct Package *pkg, struct Packages *deps,
struct Object *obj) {
Elf64_Half i;
struct Op *op;
Elf64_Rela *rela;
struct Symbol *refsym;
struct Package *refpkg;
unsigned char *code, *p;
Elf64_Shdr *shdr, *shdrcode;
for (i = 0; i < obj->elf->e_shnum; ++i) {
shdr = getelfsectionheaderaddress(obj->elf, obj->size, i);
if (shdr->sh_type == SHT_RELA) {
CHECK_EQ(sizeof(struct Elf64_Rela), shdr->sh_entsize);
CHECK_NOTNULL((shdrcode = getelfsectionheaderaddress(obj->elf, obj->size,
shdr->sh_info)));
if (!(shdrcode->sh_flags & SHF_EXECINSTR)) continue;
CHECK_NOTNULL(
(code = getelfsectionaddress(obj->elf, obj->size, shdrcode)));
for (rela = getelfsectionaddress(obj->elf, obj->size, shdr);
((uintptr_t)rela + shdr->sh_entsize <=
min((uintptr_t)obj->elf + obj->size,
(uintptr_t)obj->elf + shdr->sh_offset + shdr->sh_size));
++rela) {
CHECK_LT(ELF64_R_SYM(rela->r_info), obj->symcount);
#if 0
/*
* Change (%rip) to (%rbx) on program instructions that
* reference memory, if and only if the memory location is a
* global variable that's mutable after initialization. The
* displacement is also updated to be relative to the image
* base, rather than relative to the program counter.
*/
if ((ELF64_R_TYPE(rela->r_info) == R_X86_64_PC32 ||
ELF64_R_TYPE(rela->r_info) == R_X86_64_GOTPCREL) &&
FindSymbol(
getelfstring(obj->elf, obj->size, obj->strs,
obj->syms[ELF64_R_SYM(rela->r_info)].st_name),
pkg, deps, &refpkg, &refsym) &&
(refsym->kind == kData || refsym->kind == kBss) &&
IsRipRelativeModrm(code[rela->r_offset - 1])) {
op = &obj->sections.p[shdr->sh_info].ops.p[bisectcarleft(
(const int32_t(*)[2])obj->sections.p[shdr->sh_info].ops.p,
obj->sections.p[shdr->sh_info].ops.i, rela->r_offset)];
CHECK_GT(op->decoded_length, 4);
CHECK_GT(op->pos_disp, 0);
rela->r_info = ELF64_R_INFO(ELF64_R_SYM(rela->r_info), R_X86_64_32S);
rela->r_addend = -IMAGE_BASE_VIRTUAL + rela->r_addend +
(op->decoded_length - op->pos_disp);
code[rela->r_offset - 1] = ChangeRipToRbx(code[rela->r_offset - 1]);
}
#endif
/*
* GCC isn't capable of -mnop-mcount when using -fpie.
* Let's fix that. It saves ~14 cycles per function call.
* Then libc/runtime/ftrace.greg.c morphs it back at runtime.
*/
if (ELF64_R_TYPE(rela->r_info) == R_X86_64_GOTPCRELX &&
strcmp(getelfstring(obj->elf, obj->size, obj->strs,
obj->syms[ELF64_R_SYM(rela->r_info)].st_name),
"mcount") == 0) {
rela->r_info = R_X86_64_NONE;
p = &code[rela->r_offset - 2];
p[0] = 0x66; /* nopw 0x00(%rax,%rax,1) */
p[1] = 0x0f;
p[2] = 0x1f;
p[3] = 0x44;
p[4] = 0x00;
p[5] = 0x00;
}
/*
* Let's just try to nop mcount calls in general due to the above.
*/
if ((ELF64_R_TYPE(rela->r_info) == R_X86_64_PC32 ||
ELF64_R_TYPE(rela->r_info) == R_X86_64_PLT32) &&
strcmp(getelfstring(obj->elf, obj->size, obj->strs,
obj->syms[ELF64_R_SYM(rela->r_info)].st_name),
"mcount") == 0) {
rela->r_info = R_X86_64_NONE;
p = &code[rela->r_offset - 1];
p[0] = 0x0f; /* nopl 0x00(%rax,%rax,1) */
p[1] = 0x1f;
p[2] = 0x44;
p[3] = 0x00;
p[4] = 0x00;
}
}
}
}
}
bool IsSymbolDirectlyReachable(struct Package *pkg, struct Packages *deps,
const char *symbol) {
return FindSymbol(symbol, pkg, deps, NULL, NULL);
}
struct RlEncoder {
size_t i, n;
struct RlDecode *p;
};
ssize_t rlencode_extend(struct RlEncoder *rle, size_t n) {
size_t n2;
struct RlDecode *p2;
n2 = rle->n;
if (!n2) n2 = 512;
while (n > n2) n2 += n2 >> 1;
if (!(p2 = realloc(rle->p, n2 * sizeof(rle->p[0])))) return -1;
rle->p = p2;
rle->n = n2;
return n2;
}
void rlencode_encode(struct RlEncoder *rle, const unsigned char *data,
size_t size) {
size_t i, j;
for (i = 0; i < size; i += j) {
for (j = 1; j < 255 && i + j < size; ++j) {
if (data[i] != data[i + j]) break;
}
rle->p[rle->i].repititions = j;
rle->p[rle->i].byte = data[i];
rle->i++;
}
rle->p[rle->i].repititions = 0;
rle->p[rle->i].byte = 0;
rle->i++;
}
ssize_t rlencode(struct RlEncoder *rle, const unsigned char *data,
size_t size) {
if (size + 1 > rle->n && rlencode_extend(rle, size + 1) == -1) return -1;
rlencode_encode(rle, data, size);
assert(rle->i <= rle->n);
return rle->i;
}
void CompressLowEntropyReadOnlyDataSections(struct Package *pkg,
struct Packages *deps,
struct Object *obj) {
Elf64_Half i;
const char *name;
unsigned char *p;
Elf64_Shdr *shdr;
struct RlEncoder rle;
bool haverldecode, isprofitable;
memset(&rle, 0, sizeof(rle));
haverldecode = IsSymbolDirectlyReachable(pkg, deps, "rldecode");
for (i = 0; i < obj->elf->e_shnum; ++i) {
if ((shdr = getelfsectionheaderaddress(obj->elf, obj->size, i)) &&
shdr->sh_size >= 256 &&
(shdr->sh_type == SHT_PROGBITS &&
!(shdr->sh_flags &
(SHF_WRITE | SHF_MERGE | SHF_STRINGS | SHF_COMPRESSED))) &&
(p = getelfsectionaddress(obj->elf, obj->size, shdr)) &&
startswith((name = getelfsectionname(obj->elf, obj->size, shdr)),
".rodata") &&
rlencode(&rle, p, shdr->sh_size) != -1) {
isprofitable = rle.i * sizeof(rle.p[0]) <= shdr->sh_size / 2;
LOGF("%s(%s): rlencode()%s on %s is%s profitable (%,zu → %,zu bytes)",
&pkg->strings.p[pkg->path], &pkg->strings.p[obj->path],
haverldecode ? "" : " [NOT LINKED]", name,
isprofitable ? "" : " NOT", shdr->sh_size, rle.i * sizeof(rle.p[0]));
}
}
free(rle.p);
}
void RewriteObjects(struct Package *pkg, struct Packages *deps) {
size_t i;
struct Object *obj;
#if 0
struct ElfWriter *elf;
elf = elfwriter_open(gc(xstrcat(&pkg->strings.p[pkg->path], ".o")), 0644);
elfwriter_cargoculting(elf);
#endif
for (i = 0; i < pkg->objects.i; ++i) {
obj = &pkg->objects.p[i];
OpenObject(pkg, obj, O_RDWR, PROT_READ | PROT_WRITE, MAP_SHARED);
OptimizeRelocations(pkg, deps, obj);
#if 0
CompressLowEntropyReadOnlyDataSections(pkg, deps, obj);
#endif
CloseObject(obj);
}
#if 0
elfwriter_close(elf);
#endif
}
void Package(int argc, char *argv[], struct Package *pkg,
struct Packages *deps) {
size_t i, j;
GetOpts(pkg, deps, argc, argv);
LoadObjects(pkg);
CheckStrictDeps(pkg, deps);
RewriteObjects(pkg, deps);
WritePackage(pkg);
for (i = 0; i < deps->i; ++i) {
CHECK_NE(-1, munmap(deps->p[i]->addr, deps->p[i]->size));
}
for (i = 0; i < pkg->objects.i; ++i) {
for (j = 0; j < pkg->objects.p[i].sections.i; ++j) {
free(pkg->objects.p[i].sections.p[j].ops.p);
}
free(pkg->objects.p[i].sections.p);
}
free_s(&pkg->strings.p);
free_s(&pkg->objects.p);
free_s(&pkg->symbols.p);
free_s(&deps->p);
}
int main(int argc, char *argv[]) {
struct Package pkg;
struct Packages deps;
memset(&pkg, 0, sizeof(pkg));
memset(&deps, 0, sizeof(deps));
Package(argc, argv, &pkg, &deps);
return 0;
}

126
tool/build/refactor.c Normal file
View File

@@ -0,0 +1,126 @@
/*-*- 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 2020 Justine Alexandra Roberts Tunney │
│ │
│ This program is free software; you can redistribute it and/or modify │
│ it under the terms of the GNU General Public License as published by │
│ the Free Software Foundation; version 2 of the License. │
│ │
│ This program is distributed in the hope that it will be useful, but │
│ WITHOUT ANY WARRANTY; without even the implied warranty of │
│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
│ General Public License for more details. │
│ │
│ You should have received a copy of the GNU General Public License │
│ along with this program; if not, write to the Free Software │
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/alg/alg.h"
#include "libc/assert.h"
#include "libc/bits/safemacros.h"
#include "libc/calls/calls.h"
#include "libc/calls/struct/dirent.h"
#include "libc/calls/struct/stat.h"
#include "libc/fmt/fmt.h"
#include "libc/log/check.h"
#include "libc/runtime/gc.h"
#include "libc/stdio/stdio.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/dt.h"
#include "libc/sysv/consts/map.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/prot.h"
#include "libc/x/x.h"
/**
* @fileoverview Pretty fast substring refactor tool.
*/
static const char kBefore[] = "\
│ Copyright 2019 Justine Alexandra Roberts Tunney │\n\
│ │\n\
│ Copying of this file is authorized only if (1) you are Justine Tunney, or │\n\
│ (2) you make absolutely no changes to your copy. │\n\
│ │\n\
│ THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES │\n\
│ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF │\n\
│ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR │\n\
│ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES │\n\
│ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN │\n\
│ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF │\n\
│ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. │\n\
";
const char kAfter[] = "\
│ Copyright 2020 Justine Alexandra Roberts Tunney │\n\
│ │\n\
│ This program is free software; you can redistribute it and/or modify │\n\
│ it under the terms of the GNU General Public License as published by │\n\
│ the Free Software Foundation; version 2 of the License. │\n\
│ │\n\
│ This program is distributed in the hope that it will be useful, but │\n\
│ WITHOUT ANY WARRANTY; without even the implied warranty of │\n\
│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │\n\
│ General Public License for more details. │\n\
│ │\n\
│ You should have received a copy of the GNU General Public License │\n\
│ along with this program; if not, write to the Free Software │\n\
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │\n\
│ 02110-1301 USA │\n\
";
void RefactorFile(const char *path) {
int fd;
struct stat st;
size_t len, partlen, len1, len2;
char *mem, *spot = NULL, *part1, *part2;
CHECK_NE(-1, (fd = open(path, O_RDONLY)));
CHECK_NE(-1, fstat(fd, &st));
if ((len = st.st_size)) {
CHECK_NE(MAP_FAILED,
(mem = mmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, 0)));
partlen = sizeof(kBefore) - 1;
if ((spot = memmem(mem, len, kBefore, partlen))) {
part1 = gc(xmalloc((len1 = spot - mem)));
part2 = gc(xmalloc((len2 = len - partlen - (spot - mem))));
memcpy(part1, mem, len1);
memcpy(part2, spot + partlen, len2);
}
CHECK_NE(-1, munmap(mem, len));
}
CHECK_NE(-1, close(fd));
if (spot) {
fprintf(stderr, "found! %s\n", path);
CHECK_NE(-1, (fd = open(path, O_RDWR | O_TRUNC)));
CHECK_EQ(len1, write(fd, part1, len1));
CHECK_EQ(sizeof(kAfter) - 1, write(fd, kAfter, sizeof(kAfter) - 1));
CHECK_EQ(len2, write(fd, part2, len2));
CHECK_NE(-1, close(fd));
}
}
void RefactorDir(const char *dpath) {
DIR *dir;
struct dirent *ent;
char *path = gc(xmalloc(PATH_MAX));
CHECK_NOTNULL(dir = opendir(firstnonnull(dpath, ".")));
while ((ent = readdir(dir))) {
if (startswith(ent->d_name, ".")) continue;
if (strcmp(ent->d_name, "o") == 0) continue;
snprintf(path, PATH_MAX, "%s%s%s", dpath ? dpath : "", dpath ? "/" : "",
ent->d_name);
if (isdirectory(path)) {
RefactorDir(path);
} else if (isregularfile(path)) {
RefactorFile(path);
}
}
CHECK_NE(-1, closedir(dir));
}
int main(int argc, char *argv[]) {
RefactorDir(NULL);
return 0;
}

243
tool/build/rle.c Normal file
View File

@@ -0,0 +1,243 @@
/*-*- 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 2020 Justine Alexandra Roberts Tunney │
│ │
│ This program is free software; you can redistribute it and/or modify │
│ it under the terms of the GNU General Public License as published by │
│ the Free Software Foundation; version 2 of the License. │
│ │
│ This program is distributed in the hope that it will be useful, but │
│ WITHOUT ANY WARRANTY; without even the implied warranty of │
│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
│ General Public License for more details. │
│ │
│ You should have received a copy of the GNU General Public License │
│ along with this program; if not, write to the Free Software │
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/calls/calls.h"
#include "libc/dce.h"
#include "libc/errno.h"
#include "libc/fmt/fmt.h"
#include "libc/log/check.h"
#include "libc/runtime/runtime.h"
#include "libc/stdio/stdio.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/ex.h"
#include "libc/sysv/consts/exit.h"
#include "libc/sysv/errfuns.h"
#include "third_party/getopt/getopt.h"
#define USAGE1 \
"NAME\n\
\n\
rle - Run Length Encoder\n\
\n\
SYNOPSIS\n\
\n\
"
#define USAGE2 \
" [FLAGS] [FILE...]\n\
\n\
DESCRIPTION\n\
\n\
This is a primitive compression algorithm. Its advantage is that\n\
the concomitant rldecode() library is seventeen bytes, and works\n\
on IA-16, IA-32, and NexGen32e without needing to be recompiled.\n\
\n\
This CLI is consistent with gzip, bzip2, lzma, etc.\n\
\n\
FLAGS\n\
\n\
-1 .. -9 ignored\n\
-a ignored\n\
-c send to stdout\n\
-d decompress\n\
-f ignored\n\
-t test integrity\n\
-S SUFFIX overrides .rle extension\n\
-h shows this information\n"
FILE *fin_, *fout_;
bool decompress_, test_;
const char *suffix_, *hint_;
void StartErrorMessage(void) {
fputs("error: ", stderr);
fputs(hint_, stderr);
fputs(": ", stderr);
}
void PrintIoErrorMessage(void) {
int err;
err = errno;
StartErrorMessage();
fputs(strerror(err), stderr);
fputc('\n', stderr);
}
void PrintUsage(int rc, FILE *f) {
fputs(USAGE1, f);
fputs(program_invocation_name, f);
fputs(USAGE2, f);
exit(rc);
}
void GetOpts(int argc, char *argv[]) {
int opt;
fin_ = stdin;
suffix_ = ".rle";
while ((opt = getopt(argc, argv, "123456789S:acdfho:t")) != -1) {
switch (opt) {
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case 'a':
case 'f':
break;
case 'c':
fout_ = stdout;
break;
case 'd':
decompress_ = true;
break;
case 't':
test_ = true;
break;
case 'o':
fclose_s(&fout_);
if (!(fout_ = fopen((hint_ = optarg), "w"))) {
PrintIoErrorMessage();
exit(1);
}
break;
case 'S':
suffix_ = optarg;
break;
case 'h':
case '?':
PrintUsage(EXIT_SUCCESS, stdout);
default:
PrintUsage(EX_USAGE, stderr);
}
}
}
int RunLengthEncode1(void) {
int byte1, byte2, runlength;
byte2 = -1;
runlength = 0;
if ((byte1 = fgetc(fin_)) == -1) return -1;
do {
while (++runlength < 255) {
if ((byte2 = fgetc(fin_)) != byte1) break;
}
if (fputc(runlength, fout_) == -1 || fputc(byte1, fout_) == -1) {
return -1;
}
runlength = 0;
} while ((byte1 = byte2) != -1);
return feof(fin_) ? 0 : -1;
}
int RunLengthEncode2(void) { return fputc(0, fout_) | fputc(0, fout_); }
int EmitRun(unsigned char count, unsigned char byte) {
do {
if (fputc(byte, fout_) == -1) return -1;
} while (--count);
return 0;
}
int RunLengthDecode(void) {
int byte1, byte2;
if ((byte1 = fgetc(fin_)) == -1) return einval();
if ((byte2 = fgetc(fin_)) == -1) return einval();
while (byte1) {
if (!test_ && EmitRun(byte1, byte2) == -1) return -1;
if ((byte1 = fgetc(fin_)) == -1) break;
if ((byte2 = fgetc(fin_)) == -1) return einval();
}
if (byte1 != 0 || byte2 != 0) return einval();
fgetc(fin_);
return feof(fin_) ? 0 : -1;
}
int RunLengthCode(void) {
if (test_ || decompress_) {
return RunLengthDecode();
} else {
return RunLengthEncode1();
}
}
int Run(char **paths, size_t count) {
int rc;
char *p;
size_t i, suffixlen;
const char pathbuf[PATH_MAX];
if (!count) {
hint_ = "/dev/stdin";
if (!fout_) fout_ = stdout;
rc = RunLengthCode();
rc |= fclose_s(&fin_);
} else {
rc = fclose_s(&fin_);
for (i = 0; i < count && rc != -1; ++i) {
rc = -1;
if ((fin_ = fopen((hint_ = paths[i]), "r"))) {
if (test_ || fout_) {
rc = RunLengthCode();
} else {
suffixlen = strlen(suffix_);
if (!IsTrustworthy() && strlen(paths[i]) + suffixlen + 1 > PATH_MAX) {
return eoverflow();
}
p = stpcpy(pathbuf, paths[i]);
if (!decompress_) {
strcpy(p, suffix_);
} else if (p - pathbuf > suffixlen &&
memcmp(p - suffixlen, suffix_, suffixlen) == 0) {
p[-suffixlen] = '\0';
} else {
return enotsup();
}
if ((fout_ = fopen((hint_ = pathbuf), "w"))) {
rc = RunLengthCode();
if (rc != -1 && !decompress_) {
rc = RunLengthEncode2();
}
if ((rc |= fclose_s(&fout_)) != -1) {
unlink(paths[i]);
}
}
}
rc |= fclose_s(&fin_);
}
}
}
if (rc != -1 && fout_) {
rc = RunLengthEncode2();
rc |= fclose_s(&fout_);
}
return rc;
}
int main(int argc, char *argv[]) {
GetOpts(argc, argv);
if (Run(argv + optind, argc - optind) != -1) {
return EXIT_SUCCESS;
} else {
PrintIoErrorMessage();
return EXIT_FAILURE;
}
}

400
tool/build/runit.c Normal file
View File

@@ -0,0 +1,400 @@
/*-*- 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 2020 Justine Alexandra Roberts Tunney │
│ │
│ This program is free software; you can redistribute it and/or modify │
│ it under the terms of the GNU General Public License as published by │
│ the Free Software Foundation; version 2 of the License. │
│ │
│ This program is distributed in the hope that it will be useful, but │
│ WITHOUT ANY WARRANTY; without even the implied warranty of │
│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
│ General Public License for more details. │
│ │
│ You should have received a copy of the GNU General Public License │
│ along with this program; if not, write to the Free Software │
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/alg/alg.h"
#include "libc/bits/safemacros.h"
#include "libc/calls/calls.h"
#include "libc/calls/hefty/spawn.h"
#include "libc/calls/struct/itimerval.h"
#include "libc/calls/struct/sigaction.h"
#include "libc/calls/struct/stat.h"
#include "libc/conv/conv.h"
#include "libc/dce.h"
#include "libc/dns/dns.h"
#include "libc/errno.h"
#include "libc/fmt/fmt.h"
#include "libc/limits.h"
#include "libc/log/check.h"
#include "libc/log/log.h"
#include "libc/mem/mem.h"
#include "libc/runtime/gc.h"
#include "libc/runtime/runtime.h"
#include "libc/sock/ipclassify.h"
#include "libc/sock/sock.h"
#include "libc/stdio/stdio.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/af.h"
#include "libc/sysv/consts/ai.h"
#include "libc/sysv/consts/ex.h"
#include "libc/sysv/consts/exit.h"
#include "libc/sysv/consts/fileno.h"
#include "libc/sysv/consts/ipproto.h"
#include "libc/sysv/consts/itimer.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/shut.h"
#include "libc/sysv/consts/sig.h"
#include "libc/sysv/consts/sock.h"
#include "libc/time/time.h"
#include "libc/x/x.h"
#include "tool/build/runit.h"
/**
* @fileoverview Remote test runner.
*
* This is able to upload and run test binaries on remote operating
* systems with about 30 milliseconds of latency. It requires zero ops
* work too, since it deploys the ephemeral runit daemon via SSH upon
* ECONNREFUSED. That takes 10x longer (300 milliseconds). Further note
* there's no make -j race conditions here, thanks to SO_REUSEPORT.
*
* o/default/tool/build/runit.com \
* o/default/tool/build/runitd.com \
* o/default/test/libc/alg/qsort_test.com \
* freebsd.test.:31337:22
*
* The only thing that needs to be configured is /etc/hosts or Bind, to
* assign numbers to the officially reserved canned names. For example:
*
* 192.168.0.10 windows.test. windows
* 192.168.0.11 freebsd.test. freebsd
* 192.168.0.12 openbsd.test. openbsd
*
* Life is easiest if SSH public key authentication is configured too.
* It can be tuned as follows in ~/.ssh/config:
*
* host windows.test.
* user testacct
* host freebsd.test.
* user testacct
* host openbsd.test.
* user testacct
*
* Firewalls may need to be configured as well, to allow port tcp:31337
* from the local subnet. For example:
*
* iptables -L -vn
* iptables -I INPUT 1 -s 10.0.0.0/8 -p tcp --dport 31337 -j ACCEPT
* iptables -I INPUT 1 -s 192.168.0.0/16 -p tcp --dport 31337 -j ACCEPT
*
* If your system administrator blocks all ICMP, you'll likely encounter
* difficulties. Consider offering feedback to his/her manager and grand
* manager.
*
* Finally note this tool isn't designed for untrustworthy environments.
* It also isn't designed to process untrustworthy inputs.
*/
static const struct addrinfo kResolvHints = {.ai_family = AF_INET,
.ai_socktype = SOCK_STREAM,
.ai_protocol = IPPROTO_TCP};
int g_sock;
jmp_buf g_jmpbuf;
uint16_t g_sshport, g_runitdport;
char *g_prog, *g_runitd, *g_ssh, g_hostname[128];
forceinline pureconst size_t GreatestTwoDivisor(size_t x) {
return x & (~x + 1);
}
noreturn void ShowUsage(FILE *f, int rc) {
fprintf(f, "Usage: %s RUNITD PROGRAM HOSTNAME[:RUNITDPORT[:SSHPORT]]...\n",
program_invocation_name);
exit(rc);
unreachable;
}
void CheckExists(const char *path) {
if (!isregularfile(path)) {
fprintf(stderr, "error: %s: not found or irregular\n", path);
ShowUsage(stderr, EX_USAGE);
unreachable;
}
}
nodiscard char *MakeDeployScript(struct addrinfo *remotenic, size_t combytes) {
const char *ip4 = (const char *)&remotenic->ai_addr4->sin_addr;
return xasprintf("mkdir -p o/ &&\n"
"dd bs=%zu count=%zu of=o/runitd.$$.com 2>/dev/null &&\n"
"exec <&- &&\n"
"chmod +x o/runitd.$$.com &&\n"
"o/runitd.$$.com -rdl%hhu.%hhu.%hhu.%hhu -p %hu &&\n"
"rm -f o/runitd.$$.com\n",
GreatestTwoDivisor(combytes),
combytes ? combytes / GreatestTwoDivisor(combytes) : 0,
ip4[0], ip4[1], ip4[2], ip4[3], g_runitdport);
}
void Upload(int pipe, int fd, struct stat *st) {
int64_t i;
for (i = 0; i < st->st_size;) {
CHECK_GT(splice(fd, &i, pipe, NULL, st->st_size - i, 0), 0);
}
CHECK_NE(-1, close(fd));
}
void DeployEphemeralRunItDaemonRemotelyViaSsh(struct addrinfo *ai) {
size_t got;
struct stat st;
char linebuf[32];
int sshpid, wstatus, binfd, sshfds[3];
DEBUGF("spawning %s on %s:%hu", g_runitd, g_hostname, g_runitdport);
CHECK_NE(-1, (binfd = open(g_runitd, O_RDONLY | O_CLOEXEC)));
CHECK_NE(-1, fstat(binfd, &st));
sshfds[0] = -1;
sshfds[1] = -1;
sshfds[2] = STDERR_FILENO;
CHECK_NE(-1, (sshpid = spawnve(
0, sshfds, g_ssh,
(char *const[]){"ssh", "-C", "-p",
gc(xasprintf("%hu", g_sshport)), g_hostname,
gc(MakeDeployScript(ai, st.st_size)), NULL},
environ)));
Upload(sshfds[0], binfd, &st);
CHECK_NE(-1, close(sshfds[0]));
CHECK_NE(-1, (got = read(sshfds[1], linebuf, sizeof(linebuf))));
CHECK_GT(got, 0);
linebuf[sizeof(linebuf) - 1] = '\0';
if (strncmp(linebuf, "ready ", 6) != 0) {
FATALF("expected ready response but got %`'.*s", got, linebuf);
}
g_runitdport = (uint16_t)atoi(&linebuf[6]);
CHECK_NE(-1, close(sshfds[1]));
CHECK_NE(-1, waitpid(sshpid, &wstatus, 0));
CHECK_EQ(0, WEXITSTATUS(wstatus));
}
void SetDeadline(int micros) {
setitimer(ITIMER_REAL, &(const struct itimerval){{0, 0}, {0, micros}}, NULL);
}
void Connect(int attempt) {
int rc, olderr;
const char *ip4;
struct addrinfo *ai;
if ((rc = getaddrinfo(g_hostname, gc(xasprintf("%hu", g_runitdport)),
&kResolvHints, &ai)) != 0) {
FATALF("%s:%hu: EAI_%s %m", g_hostname, g_runitdport, eai2str(rc));
unreachable;
}
if (ispublicip(ai->ai_family, &ai->ai_addr4->sin_addr)) {
ip4 = (const char *)&ai->ai_addr4->sin_addr;
FATALF("%s points to %hhu.%hhu.%hhu.%hhu"
" which isn't part of a local/private/testing subnet",
g_hostname, ip4[0], ip4[1], ip4[2], ip4[3]);
unreachable;
}
CHECK_NE(-1, (g_sock = socket(ai->ai_family, ai->ai_socktype | SOCK_CLOEXEC,
ai->ai_protocol)));
SetDeadline(50000);
olderr = errno;
rc = connect(g_sock, ai->ai_addr, ai->ai_addrlen);
SetDeadline(0);
if (rc == -1) {
if (!attempt &&
(errno == ECONNREFUSED || errno == EHOSTUNREACH || errno == EINTR)) {
errno = olderr;
DeployEphemeralRunItDaemonRemotelyViaSsh(ai);
Connect(1);
} else if (errno == EINTR) {
fprintf(stderr, "%s(%s:%hu): %s\n", "connect", g_hostname, g_runitdport,
"offline, icmp misconfigured, or too slow; tune make HOSTS=...");
exit(1);
} else {
FATALF("%s(%s:%hu): %m", "connect", g_hostname, g_runitdport);
unreachable;
}
}
freeaddrinfo(ai);
}
void SendRequest(void) {
int fd;
int64_t off;
struct stat st;
const char *name;
unsigned char *hdr;
size_t progsize, namesize, hdrsize;
CHECK_NE(-1, (fd = open(g_prog, O_RDONLY)));
CHECK_NE(-1, fstat(fd, &st));
CHECK_LE((namesize = strlen((name = basename(g_prog)))), PATH_MAX);
CHECK_LE((progsize = st.st_size), INT_MAX);
CHECK_NOTNULL((hdr = gc(calloc(1, (hdrsize = 4 + 1 + 4 + 4 + namesize)))));
hdr[0 + 0] = (unsigned char)((unsigned)RUNITD_MAGIC >> 030);
hdr[0 + 1] = (unsigned char)((unsigned)RUNITD_MAGIC >> 020);
hdr[0 + 2] = (unsigned char)((unsigned)RUNITD_MAGIC >> 010);
hdr[0 + 3] = (unsigned char)((unsigned)RUNITD_MAGIC >> 000);
hdr[4 + 0] = kRunitExecute;
hdr[5 + 0] = (unsigned char)((unsigned)namesize >> 030);
hdr[5 + 1] = (unsigned char)((unsigned)namesize >> 020);
hdr[5 + 2] = (unsigned char)((unsigned)namesize >> 010);
hdr[5 + 3] = (unsigned char)((unsigned)namesize >> 000);
hdr[9 + 0] = (unsigned char)((unsigned)progsize >> 030);
hdr[9 + 1] = (unsigned char)((unsigned)progsize >> 020);
hdr[9 + 2] = (unsigned char)((unsigned)progsize >> 010);
hdr[9 + 3] = (unsigned char)((unsigned)progsize >> 000);
memcpy(&hdr[4 + 1 + 4 + 4], name, namesize);
CHECK_EQ(hdrsize, write(g_sock, hdr, hdrsize));
for (off = 0; off < progsize;) {
CHECK_GT(sendfile(g_sock, fd, &off, progsize - off), 0);
}
CHECK_NE(-1, shutdown(g_sock, SHUT_WR));
}
int ReadResponse(void) {
int res;
uint32_t size;
ssize_t rc;
size_t n, m;
unsigned char *p;
enum RunitCommand cmd;
static unsigned char msg[512];
res = -1;
for (;;) {
CHECK_NE(-1, (rc = recv(g_sock, msg, sizeof(msg), 0)));
p = &msg[0];
n = (size_t)rc;
if (!n) break;
do {
CHECK_GE(n, 4 + 1);
CHECK_EQ(RUNITD_MAGIC, read32be(p));
p += 4, n -= 4;
cmd = *p++, n--;
switch (cmd) {
case kRunitExit:
CHECK_GE(n, 1);
res = *p;
goto drop;
case kRunitStderr:
CHECK_GE(n, 4);
size = read32be(p), p += 4, n -= 4;
while (size) {
if (n) {
CHECK_NE(-1, (rc = write(STDERR_FILENO, p, min(n, size))));
CHECK_NE(0, (m = (size_t)rc));
p += m, n -= m, size -= m;
} else {
CHECK_NE(-1, (rc = recv(g_sock, msg, sizeof(msg), 0)));
p = &msg[0];
n = (size_t)rc;
if (!n) goto drop;
}
}
break;
default:
die();
}
} while (n);
}
drop:
CHECK_NE(-1, close(g_sock));
return res;
}
int RunOnHost(char *spec) {
char *p;
for (p = spec; *p; ++p) {
if (*p == ':') *p = ' ';
}
CHECK_GE(sscanf(spec, "%100s %hu %hu", g_hostname, &g_runitdport, &g_sshport),
1);
if (!strchr(g_hostname, '.')) strcat(g_hostname, ".test.");
Connect(0);
SendRequest();
return ReadResponse();
}
bool IsParallelBuild(void) {
const char *makeflags;
return (makeflags = getenv("MAKEFLAGS")) && strstr(makeflags, "-j");
}
bool ShouldRunInParralel(void) {
return !IsWindows() && IsParallelBuild();
}
int RunRemoteTestsInSerial(char *hosts[], int count) {
int i, exitcode;
for (i = 0; i < count; ++i) {
if ((exitcode = RunOnHost(hosts[i]))) {
return exitcode;
}
}
return 0;
}
void OnInterrupt(int sig) {
static bool once;
if (!once) {
once = true;
gclongjmp(g_jmpbuf, 128 + sig);
} else {
abort();
}
}
int RunRemoteTestsInParallel(char *hosts[], int count) {
const struct sigaction onsigterm = {.sa_handler = (void *)OnInterrupt};
struct sigaction onsigint = {.sa_handler = (void *)OnInterrupt};
int i, rc, exitcode;
int64_t leader, *pids;
leader = getpid();
pids = gc(xcalloc(count, sizeof(char *)));
if (!(exitcode = setjmp(g_jmpbuf))) {
sigaction(SIGINT, &onsigint, NULL);
sigaction(SIGTERM, &onsigterm, NULL);
for (i = 0; i < count; ++i) {
CHECK_NE(-1, (pids[i] = fork()));
if (!pids[i]) {
return RunOnHost(hosts[i]);
}
}
for (i = 0; i < count; ++i) {
CHECK_NE(-1, waitpid(pids[i], &rc, 0));
exitcode |= WEXITSTATUS(rc);
}
} else if (getpid() == leader) {
onsigint.sa_handler = SIG_IGN;
sigaction(SIGINT, &onsigint, NULL);
kill(0, SIGINT);
while (waitpid(-1, NULL, 0) > 0) donothing;
}
return exitcode;
}
int main(int argc, char *argv[]) {
showcrashreports();
g_loglevel = kLogDebug;
const struct sigaction onsigalrm = {.sa_handler = (void *)missingno};
if (argc > 1 &&
(strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0)) {
ShowUsage(stdout, 0);
unreachable;
}
if (argc < 1 + 2) ShowUsage(stderr, EX_USAGE);
CHECK_NOTNULL((g_ssh = commandv(firstnonnull(getenv("SSH"), "ssh"))));
CheckExists((g_runitd = argv[1]));
CheckExists((g_prog = argv[2]));
if (argc == 1 + 2) return 0; /* hosts list empty */
sigaction(SIGALRM, &onsigalrm, NULL);
g_sshport = 22;
g_runitdport = RUNITD_PORT;
return (ShouldRunInParralel() ? RunRemoteTestsInParallel
: RunRemoteTestsInSerial)(&argv[3], argc - 3);
}

15
tool/build/runit.h Normal file
View File

@@ -0,0 +1,15 @@
#ifndef COSMOPOLITAN_TOOL_BUILD_RUNIT_H_
#define COSMOPOLITAN_TOOL_BUILD_RUNIT_H_
#define RUNITD_PORT 31337
#define RUNITD_MAGIC 0xFEEDABEEu
#define RUNITD_TIMEOUT_MS (1000 * 10)
enum RunitCommand {
kRunitExecute,
kRunitStdout,
kRunitStderr,
kRunitExit,
};
#endif /* COSMOPOLITAN_TOOL_BUILD_RUNIT_H_ */

389
tool/build/runitd.c Normal file
View File

@@ -0,0 +1,389 @@
/*-*- 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 2020 Justine Alexandra Roberts Tunney │
│ │
│ This program is free software; you can redistribute it and/or modify │
│ it under the terms of the GNU General Public License as published by │
│ the Free Software Foundation; version 2 of the License. │
│ │
│ This program is distributed in the hope that it will be useful, but │
│ WITHOUT ANY WARRANTY; without even the implied warranty of │
│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
│ General Public License for more details. │
│ │
│ You should have received a copy of the GNU General Public License │
│ along with this program; if not, write to the Free Software │
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/bits/bits.h"
#include "libc/bits/safemacros.h"
#include "libc/calls/calls.h"
#include "libc/calls/hefty/spawn.h"
#include "libc/calls/struct/sigaction.h"
#include "libc/calls/struct/stat.h"
#include "libc/conv/conv.h"
#include "libc/dce.h"
#include "libc/errno.h"
#include "libc/fmt/fmt.h"
#include "libc/log/check.h"
#include "libc/log/log.h"
#include "libc/macros.h"
#include "libc/nt/runtime.h"
#include "libc/paths.h"
#include "libc/runtime/gc.h"
#include "libc/runtime/runtime.h"
#include "libc/sock/sock.h"
#include "libc/stdio/stdio.h"
#include "libc/stdio/temp.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/af.h"
#include "libc/sysv/consts/auxv.h"
#include "libc/sysv/consts/ex.h"
#include "libc/sysv/consts/exit.h"
#include "libc/sysv/consts/f.h"
#include "libc/sysv/consts/fd.h"
#include "libc/sysv/consts/fileno.h"
#include "libc/sysv/consts/inaddr.h"
#include "libc/sysv/consts/ipproto.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/poll.h"
#include "libc/sysv/consts/sa.h"
#include "libc/sysv/consts/shut.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/testlib/testlib.h"
#include "libc/x/x.h"
#include "third_party/getopt/getopt.h"
#include "tool/build/runit.h"
/**
* @fileoverview Remote test runner daemon.
* Delivers 10x latency improvement over SSH (100x if Debian defaults)
*
* Here's how it handles connections:
*
* 1. Receives atomically-written request header, comprised of:
*
* - 4 byte nbo magic = 0xFEEDABEEu
* - 1 byte command = kRunitExecute
* - 4 byte nbo name length in bytes, e.g. "test1"
* - 4 byte nbo executable file length in bytes
* - <name bytes> (no NUL terminator)
* - <file bytes> (it's binary data)
*
* 2. Runs program, after verifying it came from the IP that spawned
* this program via SSH. Be sure to only run this over a trusted
* physically-wired network. To use this software on untrustworthy
* networks, wrap it with stunnel and use your own CA.
*
* 3. Sends stdout/stderr fragments, potentially multiple times:
*
* - 4 byte nbo magic = 0xFEEDABEEu
* - 1 byte command = kRunitStdout/Stderr
* - 4 byte nbo byte length
* - <chunk bytes>
*
* 4. Sends process exit code:
*
* - 4 byte nbo magic = 0xFEEDABEEu
* - 1 byte command = kRunitExit
* - 1 byte exit status
*/
#define kLogFile "o/runitd.log"
#define kLogMaxBytes (2 * 1000 * 1000)
jmp_buf g_jb;
char *g_exepath;
volatile bool g_childterm;
struct sockaddr_in g_servaddr;
unsigned char g_buf[PAGESIZE];
bool g_daemonize, g_sendready;
int g_timeout, g_devnullfd, g_servfd, g_clifd, g_exefd;
void OnInterrupt(int sig) {
static bool once;
if (once) abort();
once = true;
kill(0, sig);
for (;;) {
if (waitpid(-1, NULL, 0) == -1) break;
}
gclongjmp(g_jb, sig);
unreachable;
}
void OnChildTerminated(int sig) {
g_childterm = true;
}
noreturn void ShowUsage(FILE *f, int rc) {
fprintf(f, "%s: %s %s\n", "Usage", program_invocation_name,
"[-d] [-r] [-l LISTENIP] [-p PORT] [-t TIMEOUTMS]");
exit(rc);
}
/\
* hi
*/
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;
while ((opt = getopt(argc, argv, "hdrl:p:t:w:")) != -1) {
switch (opt) {
case 'd':
g_daemonize = true;
break;
case 'r':
g_sendready = true;
break;
case 't':
g_timeout = atoi(optarg);
break;
case 'p':
CHECK_NE(0xFFFF, (g_servaddr.sin_port = htons(parseport(optarg))));
break;
case 'l':
CHECK_EQ(1, inet_pton(AF_INET, optarg, &g_servaddr.sin_addr));
break;
case 'h':
ShowUsage(stdout, EXIT_SUCCESS);
unreachable;
default:
ShowUsage(stderr, EX_USAGE);
unreachable;
}
}
}
nodiscard 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;
CHECK_NE(-1, (g_servfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)));
setsockopt(g_servfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));
setsockopt(g_servfd, SOL_SOCKET, SO_REUSEPORT, &yes, sizeof(yes));
if (bind(g_servfd, &g_servaddr, sizeof(g_servaddr)) == -1) {
if (g_servaddr.sin_port != 0) {
g_servaddr.sin_port = 0;
StartTcpServer();
return;
} else {
FATALF("bind failed %m");
}
}
CHECK_NE(-1, listen(g_servfd, 10));
asize = sizeof(g_servaddr);
CHECK_NE(-1, getsockname(g_servfd, &g_servaddr, &asize));
if (g_sendready) {
printf("ready %hu\n", ntohs(g_servaddr.sin_port));
fflush(stdout);
stdout->fd = g_devnullfd;
}
CHECK_NE(-1, fcntl(g_servfd, F_SETFD, FD_CLOEXEC));
LOGF("%s:%s", "listening on tcp", gc(DescribeAddress(&g_servaddr)));
}
void SendExitMessage(int sock, int rc) {
unsigned char msg[4 + 1 + 1];
msg[0 + 0] = (unsigned char)((unsigned)RUNITD_MAGIC >> 030);
msg[0 + 1] = (unsigned char)((unsigned)RUNITD_MAGIC >> 020);
msg[0 + 2] = (unsigned char)((unsigned)RUNITD_MAGIC >> 010);
msg[0 + 3] = (unsigned char)((unsigned)RUNITD_MAGIC >> 000);
msg[4] = kRunitExit;
msg[5] = (unsigned char)rc;
CHECK_EQ(sizeof(msg), send(sock, msg, sizeof(msg), 0));
}
void SendOutputFragmentMessage(int sock, enum RunitCommand kind,
unsigned char *buf, size_t size) {
ssize_t rc;
size_t sent;
unsigned char msg[4 + 1 + 4];
msg[0 + 0] = (unsigned char)((unsigned)RUNITD_MAGIC >> 030);
msg[0 + 1] = (unsigned char)((unsigned)RUNITD_MAGIC >> 020);
msg[0 + 2] = (unsigned char)((unsigned)RUNITD_MAGIC >> 010);
msg[0 + 3] = (unsigned char)((unsigned)RUNITD_MAGIC >> 000);
msg[4 + 0] = kind;
msg[5 + 0] = (unsigned char)((unsigned)size >> 030);
msg[5 + 1] = (unsigned char)((unsigned)size >> 020);
msg[5 + 2] = (unsigned char)((unsigned)size >> 010);
msg[5 + 3] = (unsigned char)((unsigned)size >> 000);
CHECK_EQ(sizeof(msg), send(sock, msg, sizeof(msg), 0));
while (size) {
CHECK_NE(-1, (rc = send(sock, buf, size, 0)));
CHECK_LE((sent = (size_t)rc), size);
size -= sent;
buf += sent;
}
}
void HandleClient(void) {
const size_t kMinMsgSize = 4 + 1 + 4 + 4;
const size_t kMaxNameSize = 32;
const size_t kMaxFileSize = 10 * 1024 * 1024;
unsigned char *p;
ssize_t got, wrote;
struct sockaddr_in addr;
char *addrstr, *exename;
int wstatus, child, stdiofds[3];
uint32_t addrsize, namesize, filesize, remaining;
/* read request to run program */
addrsize = sizeof(addr);
CHECK_NE(-1, (g_clifd = accept4(g_servfd, &addr, &addrsize, SOCK_CLOEXEC)));
defer(close_s, &g_clifd);
addrstr = gc(DescribeAddress(&addr));
DEBUGF("%s %s %s", gc(DescribeAddress(&g_servaddr)), "accepted", addrstr);
got = recv(g_clifd, (p = &g_buf[0]), sizeof(g_buf), 0);
CHECK_GE(got, kMinMsgSize);
CHECK_LE(got, sizeof(g_buf));
CHECK_EQ(RUNITD_MAGIC, read32be(p));
p += 4, got -= 4;
CHECK_EQ(kRunitExecute, *p++);
got--;
namesize = read32be(p), p += 4, got -= 4;
filesize = read32be(p), p += 4, got -= 4;
CHECK_GE(got, namesize);
CHECK_LE(namesize, kMaxNameSize);
CHECK_LE(filesize, kMaxFileSize);
exename = gc(xasprintf("%.*s", namesize, p));
g_exepath = gc(xasprintf("o/%d.%s", getpid(), basename(exename)));
LOGF("%s asked we run %`'s (%,u bytes @ %`'s)", addrstr, exename, filesize,
g_exepath);
p += namesize, got -= namesize;
/* write the file to disk */
remaining = filesize;
CHECK_NE(-1, (g_exefd = creat(g_exepath, 0700)));
defer(unlink_s, &g_exepath);
defer(close_s, &g_exefd);
ftruncate(g_exefd, filesize);
if (got) {
CHECK_EQ(got, write(g_exefd, p, got));
CHECK_LE(got, remaining);
remaining -= got;
}
while (remaining) {
CHECK_NE(-1, (got = recv(g_clifd, g_buf, sizeof(g_buf), 0)));
CHECK_LE(got, remaining);
if (!got) {
LOGF("%s %s %,u/%,u %s", addrstr, "sent", remaining, filesize,
"bytes before hangup");
return;
}
remaining -= got;
p = &g_buf[0];
do {
CHECK_GT((wrote = write(g_exefd, g_buf, got)), 0);
CHECK_LE(wrote, got);
} while ((got -= wrote));
}
/* CHECK_NE(-1, shutdown(g_clifd, SHUT_RD)); */
CHECK_NE(-1, close_s(&g_exefd));
/* run program, tee'ing stderr to both log and client */
DEBUGF("spawning %s", exename);
g_childterm = false;
stdiofds[0] = g_devnullfd;
stdiofds[1] = g_devnullfd;
stdiofds[2] = -1;
CHECK_NE(-1, (child = spawnve(0, stdiofds, g_exepath,
(char *const[]){g_exepath, NULL}, environ)));
DEBUGF("communicating %s[%d]", exename, child);
for (;;) {
CHECK_NE(-1, (got = read(stdiofds[2], g_buf, sizeof(g_buf))));
if (!got) {
close_s(&stdiofds[2]);
break;
}
fwrite(g_buf, got, 1, stderr);
SendOutputFragmentMessage(g_clifd, kRunitStderr, g_buf, got);
}
if (!g_childterm) {
CHECK_NE(-1, waitpid(child, &wstatus, 0));
}
DEBUGF("exited %s[%d] → %d", exename, child, WEXITSTATUS(wstatus));
/* let client know how it went */
SendExitMessage(g_clifd, WEXITSTATUS(wstatus));
/* CHECK_NE(-1, shutdown(g_clifd, SHUT_RDWR)); */
CHECK_NE(-1, close(g_clifd));
}
int Poll(void) {
int i, evcount;
struct pollfd fds[] = {{g_servfd, POLLIN}};
TryAgain:
evcount = poll(fds, ARRAYLEN(fds), g_timeout);
if (evcount == -1 && errno == EINTR) goto TryAgain;
CHECK_NE(-1, evcount);
for (i = 0; i < evcount; ++i) {
CHECK(fds[i].revents & POLLIN);
HandleClient();
}
return evcount;
}
int Serve(void) {
int rc;
const struct sigaction onsigint = {.sa_handler = (void *)OnInterrupt,
.sa_flags = SA_RESETHAND};
const struct sigaction onsigterm = {.sa_handler = (void *)OnInterrupt,
.sa_flags = SA_RESETHAND};
const struct sigaction onsigchld = {.sa_handler = SIG_IGN,
.sa_flags = SA_RESETHAND | SA_RESTART};
StartTcpServer();
defer(close_s, &g_servfd);
if (!(rc = setjmp(g_jb))) {
sigaction(SIGINT, &onsigint, NULL);
sigaction(SIGTERM, &onsigterm, NULL);
sigaction(SIGCHLD, &onsigchld, NULL);
while (g_servfd != -1) {
if (!Poll()) break;
}
LOGF("timeout expired, shutting down");
} else {
if (isatty(fileno(stderr))) fputc('\r', stderr);
LOGF("got %s, shutting down", strsignal(rc));
rc += 128;
}
return rc;
}
void Daemonize(void) {
struct stat st;
if (fork() > 0) _exit(0);
setsid();
if (fork() > 0) _exit(0);
stdin->fd = g_devnullfd;
if (!g_sendready) stdout->fd = g_devnullfd;
if (stat(kLogFile, &st) != -1 && st.st_size > kLogMaxBytes) unlink(kLogFile);
freopen(kLogFile, "a", stderr);
}
int main(int argc, char *argv[]) {
showcrashreports();
g_loglevel = kLogDebug;
GetOpts(argc, argv);
CHECK_NE(-1, (g_devnullfd = open("/dev/null", O_RDWR)));
defer(close_s, &g_devnullfd);
if (!isdirectory("o")) CHECK_NE(-1, mkdir("o", 0700));
if (g_daemonize) Daemonize();
return Serve();
}

303
tool/build/zipobj.c Normal file
View File

@@ -0,0 +1,303 @@
/*-*- 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 2020 Justine Alexandra Roberts Tunney │
│ │
│ This program is free software; you can redistribute it and/or modify │
│ it under the terms of the GNU General Public License as published by │
│ the Free Software Foundation; version 2 of the License. │
│ │
│ This program is distributed in the hope that it will be useful, but │
│ WITHOUT ANY WARRANTY; without even the implied warranty of │
│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
│ General Public License for more details. │
│ │
│ You should have received a copy of the GNU General Public License │
│ along with this program; if not, write to the Free Software │
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/alg/arraylist.h"
#include "libc/bits/bits.h"
#include "libc/bits/safemacros.h"
#include "libc/calls/calls.h"
#include "libc/calls/struct/stat.h"
#include "libc/calls/struct/timespec.h"
#include "libc/dos.h"
#include "libc/elf/def.h"
#include "libc/limits.h"
#include "libc/log/check.h"
#include "libc/log/log.h"
#include "libc/macros.h"
#include "libc/mem/alloca.h"
#include "libc/nt/enum/fileflagandattributes.h"
#include "libc/runtime/gc.h"
#include "libc/runtime/runtime.h"
#include "libc/runtime/symbols.h"
#include "libc/stdio/stdio.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/ex.h"
#include "libc/sysv/consts/exit.h"
#include "libc/sysv/consts/fileno.h"
#include "libc/sysv/consts/map.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/ok.h"
#include "libc/sysv/consts/prot.h"
#include "libc/time/struct/tm.h"
#include "libc/time/time.h"
#include "libc/x/x.h"
#include "libc/zip.h"
#include "third_party/getopt/getopt.h"
#include "third_party/zlib/zlib.h"
#include "tool/build/lib/elfwriter.h"
#define ZIP_LOCALFILE_SECTION ".piro.data.sort.zip.2."
#define ZIP_DIRECTORY_SECTION ".piro.data.sort.zip.4."
#define PUT8(P, V) *P++ = V
#define PUT16(P, V) P[0] = V & 0xff, P[1] = V >> 010 & 0xff, P += 2
#define PUT32(P, V) \
P[0] = V & 0xff, P[1] = V >> 010 & 0xff, P[2] = V >> 020 & 0xff, \
P[3] = V >> 030 & 0xff, P += 4
char *symbol_;
char *outpath_;
const size_t kMinCompressSize = 32;
const char kNoCompressExts[][8] = {".gz", ".xz", ".jpg", ".png",
".gif", ".zip", ".bz2", ".mpg",
".mp4", ".lz4", ".webp", ".mpeg"};
noreturn void PrintUsage(int rc, FILE *f) {
fprintf(f, "%s%s%s\n", "Usage: ", program_invocation_name,
" [-o FILE] [-s SYMBOL] [FILE...]\n");
exit(rc);
}
void GetOpts(int *argc, char ***argv) {
int opt;
while ((opt = getopt(*argc, *argv, "?ho:s:")) != -1) {
switch (opt) {
case 'o':
outpath_ = optarg;
break;
case 's':
symbol_ = optarg;
break;
case '?':
case 'h':
PrintUsage(EXIT_SUCCESS, stdout);
default:
PrintUsage(EX_USAGE, stderr);
}
}
*argc -= optind;
*argv += optind;
CHECK_NOTNULL(outpath_);
}
bool IsPureAscii(const void *data, size_t size) {
const unsigned char *p, *pe;
for (p = data, pe = p + size; p < pe; ++p) {
if (!*p || *p >= 0x80) {
return false;
}
}
return true;
}
bool ShouldCompress(const char *name, size_t size) {
size_t i;
char key[8];
const char *p;
if (!(p = memrchr(name, '.', size))) return true;
strncpy(key, p, sizeof(key));
for (i = 0; i < ARRAYLEN(kNoCompressExts); ++i) {
if (memcmp(key, kNoCompressExts[i], sizeof(key)) == 0) return false;
}
return true;
}
void GetDosLocalTime(int64_t utcunixts, uint16_t *out_time,
uint16_t *out_date) {
struct tm tm;
CHECK_NOTNULL(localtime_r(&utcunixts, &tm));
*out_time = DOS_TIME(tm.tm_hour, tm.tm_min, tm.tm_sec);
*out_date = DOS_DATE(tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);
}
static unsigned char *EmitZipLfileHdr(unsigned char *op, const void *name,
size_t namesize, uint32_t crc,
uint8_t era, uint16_t gflags,
uint16_t method, uint16_t mtime,
uint16_t mdate, size_t compsize,
size_t uncompsize) {
PUT32(op, kZipLfileHdrMagic);
PUT8(op, era);
PUT8(op, kZipOsDos);
PUT16(op, gflags);
PUT16(op, method);
PUT16(op, mtime);
PUT16(op, mdate);
PUT32(op, crc);
PUT32(op, compsize);
PUT32(op, uncompsize);
PUT16(op, namesize);
PUT16(op, 0); /* extra */
return mempcpy(op, name, namesize);
}
static void EmitZipCdirHdr(unsigned char *op, const void *name, size_t namesize,
uint32_t crc, uint8_t era, uint16_t gflags,
uint16_t method, uint16_t mtime, uint16_t mdate,
uint16_t iattrs, uint16_t dosmode, uint16_t unixmode,
size_t compsize, size_t uncompsize,
size_t commentsize) {
PUT32(op, kZipCfileHdrMagic);
PUT8(op, kZipCosmopolitanVersion);
PUT8(op, kZipOsUnix);
PUT8(op, era);
PUT8(op, kZipOsDos);
PUT16(op, gflags);
PUT16(op, method);
PUT16(op, mtime);
PUT16(op, mdate);
PUT32(op, crc);
PUT32(op, compsize);
PUT32(op, uncompsize);
PUT16(op, namesize);
PUT16(op, 0); /* extra size */
PUT16(op, commentsize);
PUT16(op, 0); /* disk */
PUT16(op, iattrs);
PUT16(op, dosmode);
PUT16(op, unixmode);
PUT32(op, 0); /* RELOCATE ME (kZipCfileOffsetOffset) */
memcpy(op, name, namesize);
}
void EmitZip(struct ElfWriter *elf, const char *name, size_t namesize,
const unsigned char *data, struct stat *st) {
z_stream zs;
uint8_t era;
uint32_t crc;
unsigned char *lfile, *cfile;
struct ElfWriterSymRef lfilesym;
size_t lfilehdrsize, uncompsize, compsize, commentsize;
uint16_t method, gflags, mtime, mdate, iattrs, dosmode;
compsize = st->st_size;
uncompsize = st->st_size;
CHECK_LE(uncompsize, UINT32_MAX);
lfilehdrsize = kZipLfileHdrMinSize + namesize;
crc = crc32_z(0, data, uncompsize);
GetDosLocalTime(st->st_mtim.tv_sec, &mtime, &mdate);
gflags = IsPureAscii(name, namesize) ? 0 : kZipGflagUtf8;
commentsize = kZipCdirHdrLinkableSize - (kZipCfileHdrMinSize + namesize);
iattrs = IsPureAscii(data, st->st_size) ? kZipIattrAscii : kZipIattrBinary;
dosmode = !(st->st_mode & 0200) ? kNtFileAttributeReadonly : 0;
method = (st->st_size >= kMinCompressSize && ShouldCompress(name, namesize))
? kZipCompressionDeflate
: kZipCompressionNone;
/* emit embedded file content w/ pkzip local file header */
elfwriter_align(elf, kZipCdirAlign, 0);
elfwriter_startsection(elf,
gc(xasprintf("%s%s", ZIP_LOCALFILE_SECTION, name)),
SHT_PROGBITS, SHF_ALLOC | SHF_WRITE);
if (method == kZipCompressionDeflate) {
CHECK_EQ(Z_OK, deflateInit2(memset(&zs, 0, sizeof(zs)),
Z_DEFAULT_COMPRESSION, Z_DEFLATED, -MAX_WBITS,
MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY));
zs.next_in = data;
zs.avail_in = uncompsize;
zs.next_out = ((lfile = elfwriter_reserve(
elf, (lfilehdrsize +
(zs.avail_out = compressBound(uncompsize))))) +
lfilehdrsize);
CHECK_EQ(Z_STREAM_END, deflate(&zs, Z_FINISH));
CHECK_EQ(Z_OK, deflateEnd(&zs));
if (zs.total_out < uncompsize) {
compsize = zs.total_out;
} else {
method = kZipCompressionNone;
}
} else {
lfile = elfwriter_reserve(elf, lfilehdrsize + uncompsize);
}
if (method == kZipCompressionNone) {
memcpy(lfile + lfilehdrsize, data, uncompsize);
}
era = (gflags || method) ? kZipEra1993 : kZipEra1989;
EmitZipLfileHdr(lfile, name, namesize, crc, era, gflags, method, mtime, mdate,
compsize, uncompsize);
elfwriter_commit(elf, lfilehdrsize + compsize);
lfilesym = elfwriter_appendsym(elf, gc(xasprintf("%s%s", "zip+lfile:", name)),
ELF64_ST_INFO(STB_LOCAL, STT_OBJECT),
STV_DEFAULT, 0, lfilehdrsize);
elfwriter_appendsym(elf, name, ELF64_ST_INFO(STB_GLOBAL, STT_OBJECT),
STV_DEFAULT, lfilehdrsize, compsize);
elfwriter_finishsection(elf);
/* emit central directory record */
elfwriter_align(elf, kZipCdirAlign, 0);
elfwriter_startsection(elf,
gc(xasprintf("%s%s", ZIP_DIRECTORY_SECTION, name)),
SHT_PROGBITS, SHF_ALLOC | SHF_WRITE);
EmitZipCdirHdr((cfile = elfwriter_reserve(elf, kZipCdirHdrLinkableSize)),
name, namesize, crc, era, gflags, method, mtime, mdate, iattrs,
dosmode, st->st_mode, compsize, uncompsize, commentsize);
elfwriter_appendsym(elf, gc(xasprintf("%s%s", "zip+cdir:", name)),
ELF64_ST_INFO(STB_LOCAL, STT_OBJECT), STV_DEFAULT, 0,
kZipCdirHdrLinkableSize);
elfwriter_appendrela(elf, kZipCfileOffsetOffset, lfilesym, R_X86_64_32,
-IMAGE_BASE_VIRTUAL);
elfwriter_commit(elf, kZipCdirHdrLinkableSize);
elfwriter_finishsection(elf);
}
void ProcessFile(struct ElfWriter *elf, const char *path) {
int fd;
void *map;
struct stat st;
CHECK_NE(-1, (fd = open(path, O_RDONLY)));
CHECK_NE(-1, fstat(fd, &st));
CHECK_NE(MAP_FAILED,
(map = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0)));
EmitZip(elf, path, strlen(path), map, &st);
CHECK_NE(-1, munmap(map, st.st_size));
CHECK_NE(-1, close(fd));
}
void PullEndOfCentralDirectoryIntoLinkage(struct ElfWriter *elf) {
elfwriter_align(elf, 1, 0);
elfwriter_startsection(elf, ".yoink", SHT_PROGBITS,
SHF_ALLOC | SHF_EXECINSTR);
elfwriter_yoink(elf, "__zip_start");
elfwriter_yoink(elf, "__zip_end");
elfwriter_finishsection(elf);
}
void CheckFilenameKosher(const char *path) {
CHECK_LE(strlen(path), PATH_MAX);
CHECK(!startswith(path, "/"));
CHECK(!strstr(path, ".."));
}
void zipobj(int argc, char **argv) {
size_t i;
struct ElfWriter *elf;
CHECK_LT(argc, UINT16_MAX / 3 - 64); /* ELF 64k section limit */
GetOpts(&argc, &argv);
for (i = 0; i < argc; ++i) CheckFilenameKosher(argv[i]);
elf = elfwriter_open(outpath_, 0644);
elfwriter_cargoculting(elf);
for (i = 0; i < argc; ++i) ProcessFile(elf, argv[i]);
PullEndOfCentralDirectoryIntoLinkage(elf);
elfwriter_close(elf);
}
int main(int argc, char **argv) {
zipobj(argc, argv);
return 0;
}