//===-- scudo_utils.cpp -----------------------------------------*- C++ -*-===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
///
/// Platform specific utility functions.
///
//===----------------------------------------------------------------------===//

#include "scudo_utils.h"

#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
#include <unistd.h>

#include <cstring>

// TODO(kostyak): remove __sanitizer *Printf uses in favor for our own less
//                complicated string formatting code. The following is a
//                temporary workaround to be able to use __sanitizer::VSNPrintf.
namespace __sanitizer {

extern int VSNPrintf(char *buff, int buff_length, const char *format,
                     va_list args);

} // namespace __sanitizer

namespace __scudo {

FORMAT(1, 2)
void NORETURN dieWithMessage(const char *Format, ...) {
  // Our messages are tiny, 128 characters is more than enough.
  char Message[128];
  va_list Args;
  va_start(Args, Format);
  __sanitizer::VSNPrintf(Message, sizeof(Message), Format, Args);
  va_end(Args);
  RawWrite(Message);
  Die();
}

typedef struct {
  u32 Eax;
  u32 Ebx;
  u32 Ecx;
  u32 Edx;
} CPUIDInfo;

static void getCPUID(CPUIDInfo *info, u32 leaf, u32 subleaf)
{
  asm volatile("cpuid"
      : "=a" (info->Eax), "=b" (info->Ebx), "=c" (info->Ecx), "=d" (info->Edx)
      : "a" (leaf), "c" (subleaf)
  );
}

// Returns true is the CPU is a "GenuineIntel" or "AuthenticAMD"
static bool isSupportedCPU()
{
  CPUIDInfo Info;

  getCPUID(&Info, 0, 0);
  if (memcmp(reinterpret_cast<char *>(&Info.Ebx), "Genu", 4) == 0 &&
      memcmp(reinterpret_cast<char *>(&Info.Edx), "ineI", 4) == 0 &&
      memcmp(reinterpret_cast<char *>(&Info.Ecx), "ntel", 4) == 0) {
      return true;
  }
  if (memcmp(reinterpret_cast<char *>(&Info.Ebx), "Auth", 4) == 0 &&
      memcmp(reinterpret_cast<char *>(&Info.Edx), "enti", 4) == 0 &&
      memcmp(reinterpret_cast<char *>(&Info.Ecx), "cAMD", 4) == 0) {
      return true;
  }
  return false;
}

bool testCPUFeature(CPUFeature feature)
{
  static bool InfoInitialized = false;
  static CPUIDInfo CPUInfo = {};

  if (InfoInitialized == false) {
    if (isSupportedCPU() == true)
      getCPUID(&CPUInfo, 1, 0);
    else
      UNIMPLEMENTED();
    InfoInitialized = true;
  }
  switch (feature) {
    case SSE4_2:
      return ((CPUInfo.Ecx >> 20) & 0x1) != 0;
    default:
      break;
  }
  return false;
}

// readRetry will attempt to read Count bytes from the Fd specified, and if
// interrupted will retry to read additional bytes to reach Count.
static ssize_t readRetry(int Fd, u8 *Buffer, size_t Count) {
  ssize_t AmountRead = 0;
  while (static_cast<size_t>(AmountRead) < Count) {
    ssize_t Result = read(Fd, Buffer + AmountRead, Count - AmountRead);
    if (Result > 0)
      AmountRead += Result;
    else if (!Result)
      break;
    else if (errno != EINTR) {
      AmountRead = -1;
      break;
    }
  }
  return AmountRead;
}

// Default constructor for Xorshift128Plus seeds the state with /dev/urandom
Xorshift128Plus::Xorshift128Plus() {
  int Fd = open("/dev/urandom", O_RDONLY);
  bool Success = readRetry(Fd, reinterpret_cast<u8 *>(&State_0_),
                           sizeof(State_0_)) == sizeof(State_0_);
  Success &= readRetry(Fd, reinterpret_cast<u8 *>(&State_1_),
                           sizeof(State_1_)) == sizeof(State_1_);
  close(Fd);
  if (!Success) {
    dieWithMessage("ERROR: failed to read enough data from /dev/urandom.\n");
  }
}

} // namespace __scudo
