cosmopolitan/tool/build/ar.c
Justine Tunney b420ed8248 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 14:07:40 -07:00

284 lines
9.3 KiB
C

/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
╞══════════════════════════════════════════════════════════════════════════════╡
│ Copyright 2020 Justine Alexandra Roberts Tunney │
│ │
│ Permission to use, copy, modify, and/or distribute this software for │
│ any purpose with or without fee is hereby granted, provided that the │
│ above copyright notice and this permission notice appear in all copies. │
│ │
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
│ PERFORMANCE OF THIS SOFTWARE. │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/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"
/**
* @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.
*/
struct Args {
size_t n;
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 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[]) {
FILE *f;
void *elf;
char *line;
char *strs;
ssize_t rc;
size_t wrote;
size_t remain;
struct stat *st;
uint32_t outpos;
Elf64_Sym *syms;
struct Args args;
uint64_t outsize;
uint8_t *tablebuf;
struct iovec iov[7];
const char *symname;
const char *outpath;
Elf64_Xword symcount;
struct Ints symnames;
struct String symbols;
struct String filenames;
struct Header *header1, *header2;
int *offsets, *modes, *sizes, *names;
int i, j, fd, err, name, outfd, tablebufsize;
if (argc == 2 && !strcmp(argv[1], "-n")) exit(0);
if (!(argc > 2 && strcmp(argv[1], "rcsD") == 0)) {
fprintf(stderr, "%s%s%s\n", "Usage: ", argv[0], " rcsD ARCHIVE FILE...");
return 1;
}
memset(&args, 0, sizeof(args));
for (i = 3; i < argc; ++i) {
if (argv[i][0] != '@') {
args.p = realloc(args.p, ++args.n * sizeof(*args.p));
args.p[args.n - 1] = strdup(argv[i]);
} else {
CHECK_NOTNULL((f = fopen(argv[i] + 1, "r")));
while ((line = chomp(xgetline(f)))) {
if (!isempty(line)) {
args.p = realloc(args.p, ++args.n * sizeof(*args.p));
args.p[args.n - 1] = line;
} else {
free(line);
}
}
CHECK_NE(-1, fclose(f));
}
}
st = xmalloc(sizeof(struct stat));
symbols.i = 0;
symbols.n = 4096;
symbols.p = xmalloc(symbols.n);
filenames.i = 0;
filenames.n = 1024;
filenames.p = xmalloc(filenames.n);
symnames.i = 0;
symnames.n = 1024;
symnames.p = xmalloc(symnames.n * sizeof(int));
outpath = argv[2];
modes = xmalloc(sizeof(int) * args.n);
names = xmalloc(sizeof(int) * args.n);
sizes = xmalloc(sizeof(int) * args.n);
// load global symbols and populate page cache
for (i = 0; i < args.n; ++i) {
TryAgain:
CHECK_NE(-1, (fd = open(args.p[i], O_RDONLY)), "%s", args.p[i]);
CHECK_NE(-1, fstat(fd, st));
CHECK_LT(st->st_size, 0x7ffff000);
if (!st->st_size || S_ISDIR(st->st_mode) || endswith(args.p[i], ".pkg")) {
close(fd);
for (j = i; j + 1 < args.n; ++j) {
args.p[j] = args.p[j + 1];
}
--args.n;
goto TryAgain;
}
names[i] = filenames.i;
sizes[i] = st->st_size;
modes[i] = st->st_mode;
CONCAT(&filenames.p, &filenames.i, &filenames.n, basename(args.p[i]),
strlen(basename(args.p[i])));
CONCAT(&filenames.p, &filenames.i, &filenames.n, "/\n", 2);
CHECK_NE(MAP_FAILED,
(elf = mmap(NULL, st->st_size, PROT_READ, MAP_PRIVATE, fd, 0)));
CHECK(IsElf64Binary(elf, st->st_size));
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);
APPEND(&symnames.p, &symnames.i, &symnames.n, &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.n * 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.n; ++i) {
outsize += outsize & 1;
offsets[i] = outsize;
outsize += 60;
outsize += sizes[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) goto fail;
for (i = 0; i < args.n; ++i) {
if ((fd = open(args.p[i], O_RDONLY)) == -1) 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[i], modes[i], sizes[i]);
if (writev(outfd, iov, 2) == -1) goto fail;
outsize += (remain = sizes[i]);
if (copy_file_range(fd, NULL, outfd, NULL, remain, 0) != remain) goto fail;
close(fd);
}
close(outfd);
for (i = 0; i < args.n; ++i) free(args.p[i]);
free(args.p);
free(header2);
free(header1);
free(offsets);
free(tablebuf);
free(sizes);
free(names);
free(modes);
free(symbols.p);
free(filenames.p);
free(symnames.p);
free(st);
return 0;
fail:
err = errno;
if (!err) err = 1;
unlink(outpath);
fputs("error: ar failed\n", stderr);
return err;
}