//===-- guarded_pool_allocator_posix.cpp ------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "gwp_asan/guarded_pool_allocator.h"

#include <stdlib.h>
#include <errno.h>
#include <signal.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>

namespace gwp_asan {

void *GuardedPoolAllocator::mapMemory(size_t Size) const {
  void *Ptr =
      mmap(nullptr, Size, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);

  if (Ptr == MAP_FAILED) {
    Printf("Failed to map guarded pool allocator memory, errno: %d\n", errno);
    Printf("  mmap(nullptr, %zu, ...) failed.\n", Size);
    exit(EXIT_FAILURE);
  }
  return Ptr;
}

void GuardedPoolAllocator::markReadWrite(void *Ptr, size_t Size) const {
  if (mprotect(Ptr, Size, PROT_READ | PROT_WRITE) != 0) {
    Printf("Failed to set guarded pool allocator memory at as RW, errno: %d\n",
           errno);
    Printf("  mprotect(%p, %zu, RW) failed.\n", Ptr, Size);
    exit(EXIT_FAILURE);
  }
}

void GuardedPoolAllocator::markInaccessible(void *Ptr, size_t Size) const {
  // mmap() a PROT_NONE page over the address to release it to the system, if
  // we used mprotect() here the system would count pages in the quarantine
  // against the RSS.
  if (mmap(Ptr, Size, PROT_NONE, MAP_FIXED | MAP_ANONYMOUS | MAP_PRIVATE, -1,
           0) == MAP_FAILED) {
    Printf("Failed to set guarded pool allocator memory as inaccessible, "
           "errno: %d\n",
           errno);
    Printf("  mmap(%p, %zu, NONE, ...) failed.\n", Ptr, Size);
    exit(EXIT_FAILURE);
  }
}

size_t GuardedPoolAllocator::getPlatformPageSize() {
  return sysconf(_SC_PAGESIZE);
}

struct sigaction PreviousHandler;

static void sigSegvHandler(int sig, siginfo_t *info, void *ucontext) {
  gwp_asan::GuardedPoolAllocator::reportError(
      reinterpret_cast<uintptr_t>(info->si_addr));

  // Process any previous handlers.
  if (PreviousHandler.sa_flags & SA_SIGINFO) {
    PreviousHandler.sa_sigaction(sig, info, ucontext);
  } else if (PreviousHandler.sa_handler == SIG_IGN ||
             PreviousHandler.sa_handler == SIG_DFL) {
    // If the previous handler was the default handler, or was ignoring this
    // signal, install the default handler and re-raise the signal in order to
    // get a core dump and terminate this process.
    signal(SIGSEGV, SIG_DFL);
    raise(SIGSEGV);
  } else {
    PreviousHandler.sa_handler(sig);
  }
}

void GuardedPoolAllocator::installSignalHandlers() {
  struct sigaction Action;
  Action.sa_sigaction = sigSegvHandler;
  Action.sa_flags = SA_SIGINFO;
  sigaction(SIGSEGV, &Action, &PreviousHandler);
}

uint64_t GuardedPoolAllocator::getThreadID() {
#ifdef SYS_gettid
  return syscall(SYS_gettid);
#else
  return kInvalidThreadID;
#endif
}

} // namespace gwp_asan
