/*-*- 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/memtrack.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)kFixedmapStart,
                                        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})));
}