mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-06-27 23:08:31 +00:00
Initial import
This commit is contained in:
commit
c91b3c5006
14915 changed files with 590219 additions and 0 deletions
82
tool/build/build.mk
Normal file
82
tool/build/build.mk
Normal 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
230
tool/build/coefficients.c
Normal 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
267
tool/build/img2code.c
Normal 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);
|
||||
}
|
58
tool/build/lib/buildlib.mk
Normal file
58
tool/build/lib/buildlib.mk
Normal 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
259
tool/build/lib/elfwriter.c
Normal 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})));
|
||||
}
|
67
tool/build/lib/elfwriter.h
Normal file
67
tool/build/lib/elfwriter.h
Normal 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_ */
|
31
tool/build/lib/elfwriter_cargoculting.c
Normal file
31
tool/build/lib/elfwriter_cargoculting.c
Normal 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);
|
||||
}
|
33
tool/build/lib/elfwriter_yoink.c
Normal file
33
tool/build/lib/elfwriter_yoink.c
Normal 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
134
tool/build/lib/interner.c
Normal 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
20
tool/build/lib/interner.h
Normal 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
123
tool/build/lib/persist.c
Normal 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
22
tool/build/lib/persist.h
Normal 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
184
tool/build/lz4toasm.c
Normal 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
314
tool/build/mkdeps.c
Normal 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
665
tool/build/package.c
Normal 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(§, 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(§.ops, &op));
|
||||
}
|
||||
}
|
||||
CHECK_NE(-1, append(&obj->sections, §));
|
||||
}
|
||||
}
|
||||
|
||||
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
126
tool/build/refactor.c
Normal 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
243
tool/build/rle.c
Normal 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
400
tool/build/runit.c
Normal 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
15
tool/build/runit.h
Normal 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
389
tool/build/runitd.c
Normal 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
303
tool/build/zipobj.c
Normal 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;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue