/*-*- 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 2022 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/assert.h"
#include "libc/calls/blockcancel.internal.h"
#include "libc/calls/blocksigs.internal.h"
#include "libc/calls/calls.h"
#include "libc/calls/cp.internal.h"
#include "libc/calls/execve-sysv.internal.h"
#include "libc/calls/internal.h"
#include "libc/calls/struct/stat.internal.h"
#include "libc/calls/syscall-sysv.internal.h"
#include "libc/dce.h"
#include "libc/errno.h"
#include "libc/fmt/itoa.h"
#include "libc/intrin/asan.internal.h"
#include "libc/intrin/describeflags.internal.h"
#include "libc/intrin/safemacros.internal.h"
#include "libc/intrin/strace.internal.h"
#include "libc/intrin/weaken.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/f.h"
#include "libc/sysv/consts/fd.h"
#include "libc/sysv/consts/map.h"
#include "libc/sysv/consts/mfd.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/prot.h"
#include "libc/sysv/consts/shm.h"
#include "libc/sysv/errfuns.h"

static bool IsAPEFd(const int fd) {
  char buf[8];
  bool res;
  return (sys_pread(fd, buf, 8, 0, 0) == 8) && IsAPEMagic(buf);
}

static int fexecve_impl(const int fd, char *const argv[], char *const envp[]) {
  int rc;
  if (IsLinux()) {
    char path[14 + 12];
    FormatInt32(stpcpy(path, "/proc/self/fd/"), fd);
    rc = __sys_execve(path, argv, envp);
  } else if (IsFreebsd()) {
    rc = sys_fexecve(fd, argv, envp);
  } else {
    rc = enosys();
  }
  return rc;
}

typedef enum {
  PTF_NUM = 1 << 0,
  PTF_NUM2 = 1 << 1,
  PTF_NUM3 = 1 << 2,
  PTF_ANY = 1 << 3
} PTF_PARSE;

static bool ape_to_elf(void *ape, const size_t apesize) {
  static const char printftok[] = "printf '";
  static const size_t printftoklen = sizeof(printftok) - 1;
  const char *tok = memmem(ape, apesize, printftok, printftoklen);
  if (tok) {
    tok += printftoklen;
    uint8_t *dest = ape;
    PTF_PARSE state = PTF_ANY;
    uint8_t value = 0;
    for (; tok < (const char *)(dest + apesize); tok++) {
      if ((state & (PTF_NUM | PTF_NUM2 | PTF_NUM3)) &&
          (*tok >= '0' && *tok <= '7')) {
        value = (value << 3) | (*tok - '0');
        state <<= 1;
        if (state & PTF_ANY) {
          *dest++ = value;
        }
      } else if (state & PTF_NUM) {
        break;
      } else {
        if (state & (PTF_NUM2 | PTF_NUM3)) {
          *dest++ = value;
        }
        if (*tok == '\\') {
          state = PTF_NUM;
          value = 0;
        } else if (*tok == '\'') {
          return true;
        } else {
          *dest++ = *tok;
          state = PTF_ANY;
        }
      }
    }
  }
  errno = ENOEXEC;
  return false;
}

/**
 * Creates a memfd and copies fd to it.
 *
 * This does an inplace conversion of APE to ELF when detected!!!!
 */
static int fd_to_mem_fd(const int infd, char *path) {
  if (!IsLinux() && !IsFreebsd() || !_weaken(mmap) || !_weaken(munmap)) {
    return enosys();
  }

  struct stat st;
  if (fstat(infd, &st) == -1) {
    return -1;
  }
  int fd;
  if (IsLinux()) {
    fd = sys_memfd_create(__func__, MFD_CLOEXEC);
  } else if (IsFreebsd()) {
    fd = sys_shm_open(SHM_ANON, O_CREAT | O_RDWR, 0);
  } else {
    return enosys();
  }
  if (fd == -1) {
    return -1;
  }
  void *space;
  if ((sys_ftruncate(fd, st.st_size, st.st_size) != -1) &&
      ((space = _weaken(mmap)(0, st.st_size, PROT_READ | PROT_WRITE, MAP_SHARED,
                              fd, 0)) != MAP_FAILED)) {
    ssize_t readRc;
    readRc = pread(infd, space, st.st_size, 0);
    bool success = readRc != -1;
    if (success && (st.st_size > 8) && IsAPEMagic(space)) {
      int flags = fcntl(fd, F_GETFD);
      if (success = (flags != -1) &&
                    (fcntl(fd, F_SETFD, flags & (~FD_CLOEXEC)) != -1) &&
                    ape_to_elf(space, st.st_size)) {
        const int newfd = fcntl(fd, F_DUPFD, 9001);
        if (newfd != -1) {
          close(fd);
          fd = newfd;
        }
      }
    }
    const int e = errno;
    if ((_weaken(munmap)(space, st.st_size) != -1) && success) {
      if (path) {
        FormatInt32(stpcpy(path, "COSMOPOLITAN_INIT_ZIPOS="), fd);
      }
      _unassert(readRc == st.st_size);
      return fd;
    } else if (!success) {
      errno = e;
    }
  }
  const int e = errno;
  close(fd);
  errno = e;
  return -1;
}

/**
 * Executes binary executable at file descriptor.
 *
 * This is only supported on Linux and FreeBSD. APE binaries are
 * supported. Zipos is supported. Zipos fds or FD_CLOEXEC APE fds or
 * fds that fail fexecve with ENOEXEC are copied to a new memfd (with
 * in-memory APE to ELF conversion) and fexecve is (re)attempted.
 *
 * @param fd is opened executable and current file position is ignored
 * @return doesn't return on success, otherwise -1 w/ errno
 * @raise ENOEXEC if file at `fd` isn't an assimilated ELF executable
 * @raise ENOSYS on Windows, XNU, OpenBSD, NetBSD, and Metal
 */
int fexecve(int fd, char *const argv[], char *const envp[]) {
  int rc = 0;
  if (!argv || !envp ||
      (IsAsan() &&
       (!__asan_is_valid_strlist(argv) || !__asan_is_valid_strlist(envp)))) {
    rc = efault();
  } else {
    STRACE("fexecve(%d, %s, %s) â ...", fd, DescribeStringList(argv),
           DescribeStringList(envp));
    int savedErr = 0;
    do {
      if (!IsLinux() && !IsFreebsd()) {
        rc = enosys();
        break;
      }
      if (!__isfdkind(fd, kFdZip)) {
        bool memfdReq;
        BEGIN_CANCELLATION_POINT;
        BLOCK_SIGNALS;
        strace_enabled(-1);
        memfdReq = ((rc = fcntl(fd, F_GETFD)) != -1) && (rc & FD_CLOEXEC) &&
                   IsAPEFd(fd);
        strace_enabled(+1);
        ALLOW_SIGNALS;
        END_CANCELLATION_POINT;
        if (rc == -1) {
          break;
        } else if (!memfdReq) {
          fexecve_impl(fd, argv, envp);
          if (errno != ENOEXEC) {
            break;
          }
          savedErr = ENOEXEC;
        }
      }
      int newfd;
      char *path = alloca(PATH_MAX);
      BEGIN_CANCELLATION_POINT;
      BLOCK_SIGNALS;
      strace_enabled(-1);
      newfd = fd_to_mem_fd(fd, path);
      strace_enabled(+1);
      ALLOW_SIGNALS;
      END_CANCELLATION_POINT;
      if (newfd == -1) {
        break;
      }
      size_t numenvs;
      for (numenvs = 0; envp[numenvs];) ++numenvs;
      const size_t desenvs = min(500, max(numenvs + 1, 2));
      static _Thread_local char *envs[500];
      memcpy(envs, envp, numenvs * sizeof(char *));
      envs[numenvs] = path;
      envs[numenvs + 1] = NULL;
      fexecve_impl(newfd, argv, envs);
      if (!savedErr) {
        savedErr = errno;
      }
      BEGIN_CANCELLATION_POINT;
      BLOCK_SIGNALS;
      strace_enabled(-1);
      close(newfd);
      strace_enabled(+1);
      ALLOW_SIGNALS;
      END_CANCELLATION_POINT;
    } while (0);
    if (savedErr) {
      errno = savedErr;
    }
    rc = -1;
  }
  STRACE("fexecve(%d) failed %d% m", fd, rc);
  return rc;
}
