/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8                               :vi │
╞══════════════════════════════════════════════════════════════════════════════╡
│ Copyright 2023 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/calls/calls.h"
#include "libc/calls/metalfile.internal.h"
#include "libc/calls/syscall-nt.internal.h"
#include "libc/calls/syscall-sysv.internal.h"
#include "libc/calls/syscall_support-sysv.internal.h"
#include "libc/dce.h"
#include "libc/limits.h"
#include "libc/runtime/runtime.h"
#include "libc/serialize.h"
#include "libc/stdio/stdio.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/auxv.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/ok.h"
#include "libc/testlib/ezbench.h"
#include "libc/testlib/subprocess.h"
#include "libc/testlib/testlib.h"

static char *self;
static bool loaded, skiptests, skiparg0;

void SetUpOnce(void) {
  self = GetProgramExecutableName();
  testlib_enable_tmp_setup_teardown();
  if (IsMetal()) {
    skiptests = true;
  } else if (IsWindows()) {
    /* do all tests */
  } else if (!loaded) {
    ASSERT_STRNE(self, "");
    ASSERT_SYS(0, 3, open(self, O_RDONLY));
    char buf[8];
    ASSERT_SYS(0, 8, pread(3, buf, 8, 0));
    ASSERT_SYS(0, 0, close(3));
    if (READ64LE(buf) != READ64LE("MZqFpD='") &&
        READ64LE(buf) != READ64LE("jartsr='") &&
        READ64LE(buf) != READ64LE("APEDBG='")) {
      // GetProgramExecutableName does not work reliably for assimilated
      // OpenBSD or XNU binaries.
      skiptests = IsOpenbsd() || (IsXnu() && !IsXnuSilicon());
    }
  } else {
    skiparg0 = !(getauxval(AT_FLAGS) & AT_FLAGS_PRESERVE_ARGV0);
  }
  fprintf(stderr, loaded ? "loaded\n" : "not loaded\n");
  if (skiptests) {
    fprintf(stderr, "skipping most GetProgramExecutableName tests\n");
  } else if (skiparg0) {
    fprintf(stderr, "skipping argv[0] tests\n");
  }
}

__attribute__((__constructor__)) static void Child(int argc, char *argv[]) {
  loaded = !!__program_executable_name;
  if (argc >= 2 && !strcmp(argv[1], "Child")) {
    int rc;
    if (!IsWindows()) {
      rc = sys_chdir("/");
    } else {
      rc = sys_chdir_nt("/");
    }
    if (rc) {
      exit(122);
    }
    if (strcmp(argv[2], GetProgramExecutableName())) {
      exit(123);
    }
    if (argc >= 4) {
      if (strcmp(argv[3], argv[0])) {
        exit(124);
      }
    }
    exit(0);
  }
}

TEST(GetProgramExecutableName, ofThisFile) {
  if (IsMetal()) {
    EXPECT_STREQ(self, APE_COM_NAME);
  } else {
    EXPECT_EQ('/', *self);
    EXPECT_TRUE(!!strstr(self, "getprogramexecutablename_test"));
    EXPECT_SYS(0, 0, access(self, X_OK));
  }
}

TEST(GetProgramExecutableName, nullEnv) {
  if (skiptests)
    return;
  SPAWN(fork);
  execve(self, (char *[]){self, "Child", self, skiparg0 ? 0 : self, 0},
         (char *[]){0});
  abort();
  EXITS(0);
}

TEST(GetProramExecutableName, weirdArgv0NullEnv) {
  if (skiptests)
    return;
  SPAWN(fork);
  execve(self, (char *[]){"hello", "Child", self, skiparg0 ? 0 : "hello", 0},
         (char *[]){0});
  abort();
  EXITS(0);
}

TEST(GetProgramExecutableName, movedSelf) {
  if (skiptests)
    return;
  if (IsAarch64() && IsQemuUser()) {
    // clang-format off
    // TODO(mrdomino): fix: make -j8 m=aarch64 o/aarch64/test/libc/calls/getprogramexecutablename_test.ok
    //                 possibly related to the intersection of binfmt_misc and qemu-aarch64
    // clang-format on
    return;
  }
  char buf[BUFSIZ];
  ASSERT_SYS(0, 3, open(GetProgramExecutableName(), O_RDONLY));
  ASSERT_SYS(0, 4, creat("test", 0755));
  ssize_t rc;
  while ((rc = read(3, buf, BUFSIZ)) > 0) {
    ASSERT_SYS(0, rc, write(4, buf, rc));
  }
  ASSERT_EQ(0, rc);
  ASSERT_SYS(0, 0, close(4));
  ASSERT_SYS(0, 0, close(3));
  ASSERT_NE(NULL, getcwd(buf, BUFSIZ - 5));
  stpcpy(buf + strlen(buf), "/test");
  SPAWN(fork);
  execve(buf, (char *[]){"hello", "Child", buf, skiparg0 ? 0 : "hello", 0},
         (char *[]){0});
  abort();
  EXITS(0);
  SPAWN(fork);
  execve("./test", (char *[]){"hello", "Child", buf, skiparg0 ? 0 : "hello", 0},
         (char *[]){0});
  abort();
  EXITS(0);
}
