cosmopolitan/tool/build/ar.c

304 lines
9.5 KiB
C
Raw Normal View History

/*-*- 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
2020-12-28 01:18:44 +00:00
Permission to use, copy, modify, and/or distribute this software for
any purpose with or without fee is hereby granted, provided that the
above copyright notice and this permission notice appear in all copies.
2020-12-28 01:18:44 +00:00
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/alg/arraylist2.internal.h"
#include "libc/bits/bits.h"
#include "libc/bits/safemacros.internal.h"
#include "libc/calls/calls.h"
#include "libc/calls/struct/iovec.h"
#include "libc/calls/struct/stat.h"
#include "libc/elf/elf.h"
#include "libc/errno.h"
#include "libc/fmt/conv.h"
#include "libc/fmt/itoa.h"
#include "libc/log/check.h"
#include "libc/macros.internal.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/madv.h"
#include "libc/sysv/consts/map.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/prot.h"
#include "libc/x/x.h"
#include "tool/build/lib/getargs.h"
/**
* @fileoverview System Five Static Archive Builder.
*
* GNU ar has a bug which causes it to take hundreds of milliseconds to
* build archives like ntdll.a and several minutes for cosmopolitan.a.
* This goes quadratically faster taking 1ms to do ntdll w/ hot cache.
*
* Compared to LLVM ar this tool goes 10x faster because it uses madvise
* and copy_file_range which give us the optimal page cached file system
* beahvior that a build environment needs.
*
* This tool also adds a feature: it ignores directory parameters. This
* is important because good Makefiles on Linux will generally have the
* directory be a .a prerequisite so archives rebuild on file deletion.
*/
2021-02-07 14:11:44 +00:00
struct Args {
size_t i, n;
2021-02-07 14:11:44 +00:00
char **p;
};
struct String {
size_t i, n;
char *p;
};
struct Ints {
size_t i, n;
int *p;
};
struct Header {
char name[16];
char date[12];
char uid[6];
char gid[6];
char mode[8];
char size[10];
char fmag[2];
};
static void NewInts(struct Ints *l, size_t n) {
l->i = 0;
l->n = n;
l->p = xmalloc(n * sizeof(int));
}
static void NewString(struct String *s, size_t n) {
s->i = 0;
s->n = n;
s->p = xmalloc(n);
}
static void AppendInt(struct Ints *l, int i) {
APPEND(&l->p, &l->i, &l->n, &i);
}
static void AppendArg(struct Args *l, char *s) {
APPEND(&l->p, &l->i, &l->n, &s);
}
static void MakeHeader(struct Header *h, const char *name, int ref, int mode,
int size) {
size_t n;
char buf[24];
memset(h, ' ', sizeof(*h));
n = strlen(name);
memcpy(h->name, name, n);
if (ref != -1) {
memcpy(h->name + n, buf, uint64toarray_radix10(ref, buf));
}
if (strcmp(name, "//") != 0) {
h->date[0] = '0';
h->uid[0] = '0';
h->gid[0] = '0';
memcpy(h->mode, buf, uint64toarray_radix8(mode & 0777, buf));
}
h->fmag[0] = '`';
h->fmag[1] = '\n';
memcpy(h->size, buf, uint64toarray_radix10(size, buf));
}
int main(int argc, char *argv[]) {
2021-02-07 14:11:44 +00:00
FILE *f;
void *elf;
2021-02-07 14:11:44 +00:00
char *line;
char *strs;
ssize_t rc;
int *offsets;
2021-02-07 14:11:44 +00:00
size_t wrote;
size_t remain;
struct stat *st;
uint32_t outpos;
Elf64_Sym *syms;
const char *arg;
2021-02-07 14:11:44 +00:00
struct Args args;
uint64_t outsize;
uint8_t *tablebuf;
struct GetArgs ga;
struct Ints modes;
struct Ints names;
struct Ints sizes;
const char *reason;
struct iovec iov[7];
const char *symname;
2020-11-18 16:26:03 +00:00
const char *outpath;
Elf64_Xword symcount;
struct Ints symnames;
struct String symbols;
struct String filenames;
struct Header *header1, *header2;
int i, j, fd, err, name, outfd, tablebufsize;
Undiamond Python headers This change gets the Python codebase into a state where it conforms to the conventions of this codebase. It's now possible to include headers from Python, without worrying about ordering. Python has traditionally solved that problem by "diamonding" everything in Python.h, but that's problematic since it means any change to any Python header invalidates all the build artifacts. Lastly it makes tooling not work. Since it is hard to explain to Emacs when I press C-c C-h to add an import line it shouldn't add the header that actually defines the symbol, and instead do follow the nonstandard Python convention. Progress has been made on letting Python load source code from the zip executable structure via the standard C library APIs. System calss now recognizes zip!FILENAME alternative URIs as equivalent to zip:FILENAME since Python uses colon as its delimiter. Some progress has been made on embedding the notice license terms into the Python object code. This is easier said than done since Python has an extremely complicated ownership story. - Some termios APIs have been added - Implement rewinddir() dirstream API - GetCpuCount() API added to Cosmopolitan Libc - More bugs in Cosmopolitan Libc have been fixed - zipobj.com now has flags for mangling the path - Fixed bug a priori with sendfile() on certain BSDs - Polyfill F_DUPFD and F_DUPFD_CLOEXEC across platforms - FIOCLEX / FIONCLEX now polyfilled for fast O_CLOEXEC changes - APE now supports a hybrid solution to no-self-modify for builds - Many BSD-only magnums added, e.g. O_SEARCH, O_SHLOCK, SF_NODISKIO
2021-08-12 07:42:14 +00:00
if (argc == 2 && !strcmp(argv[1], "-n")) exit(0);
2020-11-18 16:26:03 +00:00
if (!(argc > 2 && strcmp(argv[1], "rcsD") == 0)) {
fprintf(stderr, "%s%s%s\n", "Usage: ", argv[0], " rcsD ARCHIVE FILE...");
return 1;
}
outpath = argv[2];
Make numerous improvements - Python static hello world now 1.8mb - Python static fully loaded now 10mb - Python HTTPS client now uses MbedTLS - Python REPL now completes import stmts - Increase stack size for Python for now - Begin synthesizing posixpath and ntpath - Restore Python \N{UNICODE NAME} support - Restore Python NFKD symbol normalization - Add optimized code path for Intel SHA-NI - Get more Python unit tests passing faster - Get Python help() pagination working on NT - Python hashlib now supports MbedTLS PBKDF2 - Make memcpy/memmove/memcmp/bcmp/etc. faster - Add Mersenne Twister and Vigna to LIBC_RAND - Provide privileged __printf() for error code - Fix zipos opendir() so that it reports ENOTDIR - Add basic chmod() implementation for Windows NT - Add Cosmo's best functions to Python cosmo module - Pin function trace indent depth to that of caller - Show memory diagram on invalid access in MODE=dbg - Differentiate stack overflow on crash in MODE=dbg - Add stb_truetype and tools for analyzing font files - Upgrade to UNICODE 13 and reduce its binary footprint - COMPILE.COM now logs resource usage of build commands - Start implementing basic poll() support on bare metal - Set getauxval(AT_EXECFN) to GetModuleFileName() on NT - Add descriptions to strerror() in non-TINY build modes - Add COUNTBRANCH() macro to help with micro-optimizations - Make error / backtrace / asan / memory code more unbreakable - Add fast perfect C implementation of μ-Law and a-Law audio codecs - Make strtol() functions consistent with other libc implementations - Improve Linenoise implementation (see also github.com/jart/bestline) - COMPILE.COM now suppresses stdout/stderr of successful build commands
2021-09-28 05:58:51 +00:00
bzero(&args, sizeof(args));
st = xmalloc(sizeof(struct stat));
NewInts(&modes, 128);
NewInts(&names, 128);
NewInts(&sizes, 128);
NewInts(&symnames, 1024);
NewString(&symbols, 4096);
NewString(&filenames, 1024);
getargs_init(&ga, argv + 3);
// load global symbols and populate page cache
for (i = 0;; ++i) {
TryAgain:
if (!(arg = getargs_next(&ga))) break;
CHECK_NE(-1, (fd = open(arg, O_RDONLY)), "%s", arg);
CHECK_NE(-1, fstat(fd, st));
CHECK_LT(st->st_size, 0x7ffff000);
if (!st->st_size || S_ISDIR(st->st_mode) || endswith(arg, ".pkg")) {
goto TryAgain;
}
AppendArg(&args, xstrdup(arg));
AppendInt(&names, filenames.i);
AppendInt(&sizes, st->st_size);
AppendInt(&modes, st->st_mode);
CONCAT(&filenames.p, &filenames.i, &filenames.n, basename(arg),
strlen(basename(arg)));
CONCAT(&filenames.p, &filenames.i, &filenames.n, "/\n", 2);
CHECK_NE(MAP_FAILED,
(elf = mmap(0, st->st_size, PROT_READ, MAP_PRIVATE, fd, 0)));
CHECK(IsElf64Binary(elf, st->st_size), "%s", arg);
CHECK_NOTNULL((strs = GetElfStringTable(elf, st->st_size)));
CHECK_NOTNULL((syms = GetElfSymbolTable(elf, st->st_size, &symcount)));
for (j = 0; j < symcount; ++j) {
if (syms[j].st_shndx == SHN_UNDEF) continue;
if (syms[j].st_other == STV_INTERNAL) continue;
if (ELF64_ST_BIND(syms[j].st_info) == STB_LOCAL) continue;
symname = GetElfString(elf, st->st_size, strs, syms[j].st_name);
CONCAT(&symbols.p, &symbols.i, &symbols.n, symname, strlen(symname) + 1);
AppendInt(&symnames, i);
}
CHECK_NE(-1, munmap(elf, st->st_size));
close(fd);
}
APPEND(&filenames.p, &filenames.i, &filenames.n, "\n");
// compute length of output archive
outsize = 0;
tablebufsize = 4 + symnames.i * 4;
tablebuf = xmalloc(tablebufsize);
offsets = xmalloc(args.i * 4);
header1 = xmalloc(sizeof(struct Header));
header2 = xmalloc(sizeof(struct Header));
iov[0].iov_base = "!<arch>\n";
outsize += (iov[0].iov_len = 8);
iov[1].iov_base = header1;
outsize += (iov[1].iov_len = 60);
iov[2].iov_base = tablebuf;
outsize += (iov[2].iov_len = tablebufsize);
iov[3].iov_base = symbols.p;
outsize += (iov[3].iov_len = symbols.i);
iov[4].iov_base = "\n";
outsize += (iov[4].iov_len = outsize & 1);
iov[5].iov_base = header2;
outsize += (iov[5].iov_len = 60);
iov[6].iov_base = filenames.p;
outsize += (iov[6].iov_len = filenames.i);
for (i = 0; i < args.i; ++i) {
outsize += outsize & 1;
offsets[i] = outsize;
outsize += 60;
outsize += sizes.p[i];
}
CHECK_LE(outsize, 0x7ffff000);
// serialize metadata
MakeHeader(header1, "/", -1, 0, tablebufsize + symbols.i);
MakeHeader(header2, "//", -1, 0, filenames.i);
WRITE32BE(tablebuf, symnames.i);
for (i = 0; i < symnames.i; ++i) {
WRITE32BE(tablebuf + 4 + i * 4, offsets[symnames.p[i]]);
}
// write output archive
CHECK_NE(-1, (outfd = open(outpath, O_WRONLY | O_TRUNC | O_CREAT, 0644)));
ftruncate(outfd, outsize);
if ((outsize = writev(outfd, iov, ARRAYLEN(iov))) == -1) {
reason = "writev1 failed";
goto fail;
}
for (i = 0; i < args.i; ++i) {
if ((fd = open(args.p[i], O_RDONLY)) == -1) {
reason = "open failed";
goto fail;
}
iov[0].iov_base = "\n";
outsize += (iov[0].iov_len = outsize & 1);
iov[1].iov_base = header1;
outsize += (iov[1].iov_len = 60);
MakeHeader(header1, "/", names.p[i], modes.p[i], sizes.p[i]);
if (writev(outfd, iov, 2) == -1) {
reason = "writev2 failed";
goto fail;
}
outsize += (remain = sizes.p[i]);
while ((rc = copy_file_range(fd, 0, outfd, 0, remain, 0)) < remain) {
if (rc <= 0) {
reason = "copy_file_range failed";
goto fail;
} else {
remain -= rc;
}
}
close(fd);
}
close(outfd);
for (i = 0; i < args.i; ++i) free(args.p[i]);
getargs_destroy(&ga);
free(filenames.p);
free(symnames.p);
free(symbols.p);
free(tablebuf);
free(modes.p);
free(names.p);
free(sizes.p);
free(offsets);
free(header1);
free(header2);
free(args.p);
free(st);
return 0;
fail:
err = errno;
unlink(outpath);
fputs("error: ar failed: ", stderr);
fputs(reason, stderr);
fputs(": ", stderr);
fputs(strerror(err), stderr);
fputs("\n", stderr);
return 1;
}