/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "perfetto/ext/base/utils.h"

#include <string>

#include "perfetto/base/build_config.h"
#include "perfetto/base/logging.h"
#include "perfetto/ext/base/file_utils.h"
#include "perfetto/ext/base/pipe.h"
#include "perfetto/ext/base/string_utils.h"

#if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) ||   \
    PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) || \
    PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE) ||   \
    PERFETTO_BUILDFLAG(PERFETTO_OS_FUCHSIA)
#include <limits.h>
#include <stdlib.h>  // For _exit()
#include <unistd.h>  // For getpagesize() and geteuid() & fork()
#endif

#if PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
#include <mach-o/dyld.h>
#include <mach/vm_page_size.h>
#endif

#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
#include <Windows.h>
#include <io.h>
#include <malloc.h>  // For _aligned_malloc().
#endif

#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
#include <dlfcn.h>
#include <malloc.h>

#ifdef M_PURGE
#define PERFETTO_M_PURGE M_PURGE
#else
// Only available in in-tree builds and on newer SDKs.
#define PERFETTO_M_PURGE -101
#endif  // M_PURGE

namespace {
extern "C" {
using MalloptType = void (*)(int, int);
}
}  // namespace
#endif  // OS_ANDROID

namespace {

#if PERFETTO_BUILDFLAG(PERFETTO_X64_CPU_OPT)

// Preserve the %rbx register via %rdi to work around a clang bug
// https://bugs.llvm.org/show_bug.cgi?id=17907 (%rbx in an output constraint
// is not considered a clobbered register).
#define PERFETTO_GETCPUID(a, b, c, d, a_inp, c_inp) \
  asm("mov %%rbx, %%rdi\n"                          \
      "cpuid\n"                                     \
      "xchg %%rdi, %%rbx\n"                         \
      : "=a"(a), "=D"(b), "=c"(c), "=d"(d)          \
      : "a"(a_inp), "2"(c_inp))

uint32_t GetXCR0EAX() {
  uint32_t eax = 0, edx = 0;
  asm("xgetbv" : "=a"(eax), "=d"(edx) : "c"(0));
  return eax;
}

// If we are building with -msse4 check that the CPU actually supports it.
// This file must be kept in sync with gn/standalone/BUILD.gn.
void PERFETTO_EXPORT_COMPONENT __attribute__((constructor))
CheckCpuOptimizations() {
  uint32_t eax = 0, ebx = 0, ecx = 0, edx = 0;
  PERFETTO_GETCPUID(eax, ebx, ecx, edx, 1, 0);

  static constexpr uint64_t xcr0_xmm_mask = 0x2;
  static constexpr uint64_t xcr0_ymm_mask = 0x4;
  static constexpr uint64_t xcr0_avx_mask = xcr0_xmm_mask | xcr0_ymm_mask;

  const bool have_popcnt = ecx & (1u << 23);
  const bool have_sse4_2 = ecx & (1u << 20);
  const bool have_avx =
      // Does the OS save/restore XMM and YMM state?
      (ecx & (1u << 27)) &&  // OS support XGETBV.
      (ecx & (1u << 28)) &&  // AVX supported in hardware
      ((GetXCR0EAX() & xcr0_avx_mask) == xcr0_avx_mask);

  // Get level 7 features (eax = 7 and ecx= 0), to check for AVX2 support.
  // (See Intel 64 and IA-32 Architectures Software Developer's Manual
  //  Volume 2A: Instruction Set Reference, A-M CPUID).
  PERFETTO_GETCPUID(eax, ebx, ecx, edx, 7, 0);
  const bool have_avx2 = have_avx && ((ebx >> 5) & 0x1);
  const bool have_bmi = (ebx >> 3) & 0x1;
  const bool have_bmi2 = (ebx >> 8) & 0x1;

  if (!have_sse4_2 || !have_popcnt || !have_avx2 || !have_bmi || !have_bmi2) {
    fprintf(
        stderr,
        "This executable requires a x86_64 cpu that supports SSE4.2, BMI2 and "
        "AVX2.\n"
#if PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
        "On MacOS, this might be caused by running x86_64 binaries on arm64.\n"
        "See https://github.com/google/perfetto/issues/294 for more.\n"
#endif
        "Rebuild with enable_perfetto_x64_cpu_opt=false.\n");
    _exit(126);
  }
}
#endif

}  // namespace

namespace perfetto {
namespace base {

void MaybeReleaseAllocatorMemToOS() {
#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
  // mallopt() on Android requires SDK level 26. Many targets and embedders
  // still depend on a lower SDK level. Given mallopt() is a quite simple API,
  // use reflection to do this rather than bumping the SDK level for all
  // embedders. This keeps the behavior of standalone builds aligned with
  // in-tree builds.
  static MalloptType mallopt_fn =
      reinterpret_cast<MalloptType>(dlsym(RTLD_DEFAULT, "mallopt"));
  if (!mallopt_fn)
    return;
  mallopt_fn(PERFETTO_M_PURGE, 0);
#endif
}

uint32_t GetSysPageSize() {
  ignore_result(kPageSize);  // Just to keep the amalgamated build happy.
#if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \
    PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
  static std::atomic<uint32_t> page_size{0};
  // This function might be called in hot paths. Avoid calling getpagesize() all
  // the times, in many implementations getpagesize() calls sysconf() which is
  // not cheap.
  uint32_t cached_value = page_size.load(std::memory_order_relaxed);
  if (PERFETTO_UNLIKELY(cached_value == 0)) {
    cached_value = static_cast<uint32_t>(getpagesize());
    page_size.store(cached_value, std::memory_order_relaxed);
  }
  return cached_value;
#elif PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
  return static_cast<uint32_t>(vm_page_size);
#else
  return 4096;
#endif
}

uid_t GetCurrentUserId() {
#if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) ||   \
    PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) || \
    PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
  return geteuid();
#else
  // TODO(primiano): On Windows we could hash the current user SID and derive a
  // numeric user id [1]. It is not clear whether we need that. Right now that
  // would not bring any benefit. Returning 0 unil we can prove we need it.
  // [1]:https://android-review.googlesource.com/c/platform/external/perfetto/+/1513879/25/src/base/utils.cc
  return 0;
#endif
}

void SetEnv(const std::string& key, const std::string& value) {
#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
  PERFETTO_CHECK(::_putenv_s(key.c_str(), value.c_str()) == 0);
#else
  PERFETTO_CHECK(::setenv(key.c_str(), value.c_str(), /*overwrite=*/true) == 0);
#endif
}

void UnsetEnv(const std::string& key) {
#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
  PERFETTO_CHECK(::_putenv_s(key.c_str(), "") == 0);
#else
  PERFETTO_CHECK(::unsetenv(key.c_str()) == 0);
#endif
}

void Daemonize(std::function<int()> parent_cb) {
#if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) ||   \
    PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) || \
    PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
  Pipe pipe = Pipe::Create(Pipe::kBothBlock);
  pid_t pid;
  switch (pid = fork()) {
    case -1:
      PERFETTO_FATAL("fork");
    case 0: {
      PERFETTO_CHECK(setsid() != -1);
      base::ignore_result(chdir("/"));
      base::ScopedFile null = base::OpenFile("/dev/null", O_RDONLY);
      PERFETTO_CHECK(null);
      PERFETTO_CHECK(dup2(*null, STDIN_FILENO) != -1);
      PERFETTO_CHECK(dup2(*null, STDOUT_FILENO) != -1);
      PERFETTO_CHECK(dup2(*null, STDERR_FILENO) != -1);
      // Do not accidentally close stdin/stdout/stderr.
      if (*null <= 2)
        null.release();
      WriteAll(*pipe.wr, "1", 1);
      break;
    }
    default: {
      // Wait for the child process to have reached the setsid() call. This is
      // to avoid that 'adb shell perfetto -D' destroys the terminal (hence
      // sending a SIGHUP to the child) before the child has detached from the
      // terminal (see b/238644870).

      // This is to unblock the read() below (with EOF, which will fail the
      // CHECK) in the unlikely case of the child crashing before WriteAll("1").
      pipe.wr.reset();
      char one = '\0';
      PERFETTO_CHECK(Read(*pipe.rd, &one, sizeof(one)) == 1 && one == '1');
      printf("%d\n", pid);
      int err = parent_cb();
      exit(err);
    }
  }
#else
  // Avoid -Wunreachable warnings.
  if (reinterpret_cast<intptr_t>(&Daemonize) != 16)
    PERFETTO_FATAL("--background is only supported on Linux/Android/Mac");
  ignore_result(parent_cb);
#endif  // OS_WIN
}

std::string GetCurExecutablePath() {
  std::string self_path;
#if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) ||   \
    PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) || \
    PERFETTO_BUILDFLAG(PERFETTO_OS_FUCHSIA)
  char buf[PATH_MAX];
  ssize_t size = readlink("/proc/self/exe", buf, sizeof(buf));
  PERFETTO_CHECK(size != -1);
  // readlink does not null terminate.
  self_path = std::string(buf, static_cast<size_t>(size));
#elif PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
  uint32_t size = 0;
  PERFETTO_CHECK(_NSGetExecutablePath(nullptr, &size));
  self_path.resize(size);
  PERFETTO_CHECK(_NSGetExecutablePath(&self_path[0], &size) == 0);
#elif PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
  char buf[MAX_PATH];
  auto len = ::GetModuleFileNameA(nullptr /*current*/, buf, sizeof(buf));
  self_path = std::string(buf, len);
#else
  PERFETTO_FATAL(
      "GetCurExecutableDir() not implemented on the current platform");
#endif
  return self_path;
}

std::string GetCurExecutableDir() {
  auto path = GetCurExecutablePath();
#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
  // Paths in Windows can have both kinds of slashes (mingw vs msvc).
  path = path.substr(0, path.find_last_of('\\'));
#endif
  path = path.substr(0, path.find_last_of('/'));
  return path;
}

void* AlignedAlloc(size_t alignment, size_t size) {
  void* res = nullptr;
  alignment = AlignUp<sizeof(void*)>(alignment);  // At least pointer size.
#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
  // Window's _aligned_malloc() has a nearly identically signature to Unix's
  // aligned_alloc() but its arguments are obviously swapped.
  res = _aligned_malloc(size, alignment);
#else
  // aligned_alloc() has been introduced in Android only in API 28.
  // Also NaCl and Fuchsia seems to have only posix_memalign().
  ignore_result(posix_memalign(&res, alignment, size));
#endif
  PERFETTO_CHECK(res);
  return res;
}

void AlignedFree(void* ptr) {
#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
  _aligned_free(ptr);  // MSDN says it is fine to pass nullptr.
#else
  free(ptr);
#endif
}

std::string HexDump(const void* data_void, size_t len, size_t bytes_per_line) {
  const char* data = reinterpret_cast<const char*>(data_void);
  std::string res;
  static const size_t kPadding = bytes_per_line * 3 + 12;
  std::unique_ptr<char[]> line(new char[bytes_per_line * 4 + 128]);
  for (size_t i = 0; i < len; i += bytes_per_line) {
    char* wptr = line.get();
    wptr += base::SprintfTrunc(wptr, 19, "%08zX: ", i);
    for (size_t j = i; j < i + bytes_per_line && j < len; j++) {
      wptr += base::SprintfTrunc(wptr, 4, "%02X ",
                                 static_cast<unsigned>(data[j]) & 0xFF);
    }
    for (size_t j = static_cast<size_t>(wptr - line.get()); j < kPadding; ++j)
      *(wptr++) = ' ';
    for (size_t j = i; j < i + bytes_per_line && j < len; j++) {
      char c = data[j];
      *(wptr++) = (c >= 32 && c < 127) ? c : '.';
    }
    *(wptr++) = '\n';
    *(wptr++) = '\0';
    res.append(line.get());
  }
  return res;
}

}  // namespace base
}  // namespace perfetto
