cosmopolitan/tool/build/ar.c
Justine Tunney e75ffde09e Get codebase completely working with LLVM
You can now build Cosmopolitan with Clang:

    make -j8 MODE=llvm
    o/llvm/examples/hello.com

The assembler and linker code is now friendly to LLVM too.
So it's not needed to configure Clang to use binutils under
the hood. If you love LLVM then you can now use pure LLVM.
2021-02-09 02:57:32 -08:00

282 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.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.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], "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;
}