// Copyright(c) 2017, Intel Corporation
//
// Redistribution  and  use  in source  and  binary  forms,  with  or  without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of  source code  must retain the  above copyright notice,
//   this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
//   this list of conditions and the following disclaimer in the documentation
//   and/or other materials provided with the distribution.
// * Neither the name  of Intel Corporation  nor the names of its contributors
//   may be used to  endorse or promote  products derived  from this  software
//   without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,  BUT NOT LIMITED TO,  THE
// IMPLIED WARRANTIES OF  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED.  IN NO EVENT  SHALL THE COPYRIGHT OWNER  OR CONTRIBUTORS BE
// LIABLE  FOR  ANY  DIRECT,  INDIRECT,  INCIDENTAL,  SPECIAL,  EXEMPLARY,  OR
// CONSEQUENTIAL  DAMAGES  (INCLUDING,  BUT  NOT LIMITED  TO,  PROCUREMENT  OF
// SUBSTITUTE GOODS OR SERVICES;  LOSS OF USE,  DATA, OR PROFITS;  OR BUSINESS
// INTERRUPTION)  HOWEVER CAUSED  AND ON ANY THEORY  OF LIABILITY,  WHETHER IN
// CONTRACT,  STRICT LIABILITY,  OR TORT  (INCLUDING NEGLIGENCE  OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,  EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
/*
 * test-system.cpp
 */

#include "test_system.h"
#include <glob.h>
#include <stdarg.h>
#include <unistd.h>
#include <algorithm>
#include <cstring>
#include <fstream>
#include <iostream>
#include "c_test_system.h"
#include "test_utils.h"
#ifndef _GNU_SOURCE
#define _GNU_SOURCE 1
#endif
#include <dlfcn.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <uuid/uuid.h>
#include <ftw.h>

void *__builtin_return_address(unsigned level);

// hijack malloc
static bool _invalidate_malloc = false;
static uint32_t _invalidate_malloc_after = 0;
static const char *_invalidate_malloc_when_called_from = nullptr;
void *malloc(size_t size) {
  if (_invalidate_malloc) {
    if (!_invalidate_malloc_when_called_from) {
      if (!_invalidate_malloc_after) {
        _invalidate_malloc = false;
        return nullptr;
      }

      --_invalidate_malloc_after;

    } else {
      void *caller = __builtin_return_address(0);
      int res;
      Dl_info info;

      dladdr(caller, &info);
      if (!info.dli_sname)
        res = 1;
      else
        res = strcmp(info.dli_sname, _invalidate_malloc_when_called_from);

      if (!_invalidate_malloc_after && !res) {
        _invalidate_malloc = false;
        _invalidate_malloc_when_called_from = nullptr;
        return nullptr;
      } else if (!res)
        --_invalidate_malloc_after;
    }
  }
  return __libc_malloc(size);
}

// hijack calloc
static bool _invalidate_calloc = false;
static uint32_t _invalidate_calloc_after = 0;
static const char *_invalidate_calloc_when_called_from = nullptr;
void *calloc(size_t nmemb, size_t size) {
  if (_invalidate_calloc) {
    if (!_invalidate_calloc_when_called_from) {
      if (!_invalidate_calloc_after) {
        _invalidate_calloc = false;
        return nullptr;
      }

      --_invalidate_calloc_after;

    } else {
      void *caller = __builtin_return_address(0);
      int res;
      Dl_info info;

      dladdr(caller, &info);
      if (!info.dli_sname)
        res = 1;
      else
        res = strcmp(info.dli_sname, _invalidate_calloc_when_called_from);

      if (!_invalidate_calloc_after && !res) {
        _invalidate_calloc = false;
        _invalidate_calloc_when_called_from = nullptr;
        return nullptr;
      } else if (!res)
        --_invalidate_calloc_after;
    }
  }
  return __libc_calloc(nmemb, size);
}

namespace opae {
namespace testing {

static const char *dev_pattern =
    R"regex(/dev/(intel-fpga|dfl)-(fme|port)\.([0-9]+))regex";
static const char *sysclass_pattern =
    R"regex(/sys/class/fpga((?:_region)?/(region|intel-fpga-dev\.)([0-9]+))regex";

static std::map<std::string, std::string> fpga_sysfs_path_map = {
    {"/dev/intel", "/sys/class/fpga/intel-fpga-dev."},
    {"/dev/dfl", "/sys/class/fpga_region/region"}};

mock_object::mock_object(const std::string &devpath,
                         const std::string &sysclass, uint32_t device_id,
                         type_t type)
    : devpath_(devpath),
      sysclass_(sysclass),
      device_id_(device_id),
      type_(type) {}

int mock_fme::ioctl(int request, va_list argp) {
  (void)request;
  (void)argp;
  return 0;
}

int mock_port::ioctl(int request, va_list argp) {
  (void)request;
  (void)argp;
  return 0;
}

#define ASSERT_FN(fn)                              \
  do {                                             \
    if (fn == nullptr) {                           \
      throw std::runtime_error(#fn " not loaded"); \
    }                                              \
  } while (false);

test_device test_device::unknown() {
  return test_device{.fme_guid = "C544CE5C-F630-44E1-8551-59BD87AF432E",
                     .afu_guid = "C544CE5C-F630-44E1-8551-59BD87AF432E",
                     .segment = 0x1919,
                     .bus = 0x0A,
                     .device = 9,
                     .function = 5,
                     .num_vfs = 8,
                     .socket_id = 9,
                     .num_slots = 9,
                     .bbs_id = 9,
                     .bbs_version = {0xFF, 0xFF, 0xFF},
                     .state = FPGA_ACCELERATOR_ASSIGNED,
                     .num_mmio = 0,
                     .num_interrupts = 0xf,
                     .fme_object_id = 9,
                     .port_object_id = 9,
                     .vendor_id = 0x1234,
                     .device_id = 0x1234,
                     .fme_num_errors = 0x1234,
                     .port_num_errors = 0x1234,
                     .gbs_guid = "C544CE5C-F630-44E1-8551-59BD87AF432E",
                     .mdata = ""};
}

test_system *test_system::instance_ = nullptr;

test_system::test_system() : initialized_(false), root_("") {
  open_ = (open_func)dlsym(RTLD_NEXT, "open");
  open_create_ = open_;
  read_ = (read_func)dlsym(RTLD_NEXT, "read");
  fopen_ = (fopen_func)dlsym(RTLD_NEXT, "fopen");
  popen_ = (popen_func)dlsym(RTLD_NEXT, "popen");
  pclose_ = (pclose_func)dlsym(RTLD_NEXT, "pclose");
  close_ = (close_func)dlsym(RTLD_NEXT, "close");
  ioctl_ = (ioctl_func)dlsym(RTLD_NEXT, "ioctl");
  opendir_ = (opendir_func)dlsym(RTLD_NEXT, "opendir");
  readlink_ = (readlink_func)dlsym(RTLD_NEXT, "readlink");
  xstat_ = (__xstat_func)dlsym(RTLD_NEXT, "__xstat");
  lstat_ = (__xstat_func)dlsym(RTLD_NEXT, "__lxstat");
  scandir_ = (scandir_func)dlsym(RTLD_NEXT, "scandir");
  sched_setaffinity_ =
      (sched_setaffinity_func)dlsym(RTLD_NEXT, "sched_setaffinity");

  glob_ = (glob_func)dlsym(RTLD_NEXT, "glob");
  realpath_ = (realpath_func)dlsym(RTLD_NEXT, "realpath");

  hijack_sched_setaffinity_ = false;
  hijack_sched_setaffinity_return_val_ = 0;
  hijack_sched_setaffinity_after_ = 0;
  hijack_sched_setaffinity_caller_ = nullptr;
}

test_system *test_system::instance() {
  if (test_system::instance_ == nullptr) {
    test_system::instance_ = new test_system();
  }
  return test_system::instance_;
}

void test_system::prepare_syfs(const test_platform &platform) {
  int result = 0;
  char tmpsysfs[]{"tmpsysfs-XXXXXX"};

  if (platform.mock_sysfs != nullptr) {
    char *tmp = mkdtemp(tmpsysfs);
    if (tmp == nullptr) {
      throw std::runtime_error("error making tmpsysfs");
    }
    root_ = std::string(tmp);
    std::string cmd = "tar xzf " + std::string(platform.mock_sysfs) + " -C " +
                      root_ + " --strip 1";
    result = std::system(cmd.c_str());
  }
  return (void)result;
}


extern "C" {
int process_fpath(const char *fpath, const struct stat *sb, int typeflag, struct FTW *ftw) {
  (void)sb;
  (void)ftw;
  if (typeflag & FTW_DP) {
    if (rmdir(fpath)) {
      if (errno == ENOTDIR) {
        goto do_unlink;
      }
      std::cerr << "error removing directory: " << fpath << " - " << strerror(errno) << "\n";
      return -1;
    }
  }
do_unlink:
  if (unlink(fpath) && errno != ENOENT) {
      std::cerr << "error removing node: " << fpath << " - " << strerror(errno) << "\n";
      return -1;
  }
  return 0;
}
}

int test_system::remove_sysfs_dir(const char *path) {
  if (root_.find("tmpsysfs") != std::string::npos) {
    auto real_path = path == nullptr ? root_ : get_sysfs_path(path);
    return nftw(real_path.c_str(), process_fpath, 100, FTW_DEPTH | FTW_PHYS);
  }
  return 0;
}

int test_system::remove_sysfs() {
  return remove_sysfs_dir();
}


void test_system::set_root(const char *root) { root_ = root; }
std::string test_system::get_root() { return root_; }

std::string test_system::get_sysfs_path(const std::string &src) {
  auto it = registered_files_.find(src);
  if (it != registered_files_.end()) {
    return it->second;
  }
  if (src.find("/sys") == 0 || src.find("/dev/intel-fpga") == 0 ||
      src.find("/dev/dfl-") == 0) {
    if (!root_.empty() && root_.size() > 1) {
      return root_ + src;
    }
  }
  return src;
}

std::vector<uint8_t> test_system::assemble_gbs_header(const test_device &td) {
  std::vector<uint8_t> gbs_header(20, 0);
  if (uuid_parse(td.gbs_guid, gbs_header.data())) {
    std::string msg = "unable to parse UUID: ";
    msg.append(td.gbs_guid);
    throw std::runtime_error(msg);
  }
  uint32_t len = strlen(td.mdata);
  *reinterpret_cast<uint32_t *>(gbs_header.data() + 16) = len;
  std::copy(&td.mdata[0], &td.mdata[len], std::back_inserter(gbs_header));
  return gbs_header;
}

std::vector<uint8_t> test_system::assemble_gbs_header(const test_device &td,
                                                      const char *mdata) {
  if (mdata) {
    test_device copy = td;
    copy.mdata = mdata;
    return assemble_gbs_header(copy);
  }
  return std::vector<uint8_t>(0);
}

void test_system::initialize() {
  ASSERT_FN(open_);
  ASSERT_FN(open_create_);
  ASSERT_FN(read_);
  ASSERT_FN(fopen_);
  ASSERT_FN(popen_);
  ASSERT_FN(pclose_);
  ASSERT_FN(close_);
  ASSERT_FN(ioctl_);
  ASSERT_FN(readlink_);
  ASSERT_FN(xstat_);
  ASSERT_FN(lstat_);
  ASSERT_FN(scandir_);
  ASSERT_FN(sched_setaffinity_);
  ASSERT_FN(glob_);
  ASSERT_FN(realpath_);
  for (const auto &kv : default_ioctl_handlers_) {
    register_ioctl_handler(kv.first, kv.second);
  }
  initialized_ = true;
}

void test_system::finalize() {
  if (!initialized_) {
    return;
  }
  initialized_ = false;
  std::lock_guard<std::mutex> guard(fds_mutex_);
  for (auto kv : fds_) {
    if (kv.second) {
      delete kv.second;
      kv.second = nullptr;
    }
  }
  remove_sysfs();
  root_ = "";
  fds_.clear();
  for (auto kv : registered_files_) {
    unlink(kv.second.c_str());
  }
  registered_files_.clear();
  ioctl_handlers_.clear();
}

bool test_system::default_ioctl_handler(int request, ioctl_handler_t h) {
  bool already_registered =
      default_ioctl_handlers_.find(request) != default_ioctl_handlers_.end();
  default_ioctl_handlers_[request] = h;
  return already_registered;
}

bool test_system::register_ioctl_handler(int request, ioctl_handler_t h) {
  bool already_registered =
      ioctl_handlers_.find(request) != ioctl_handlers_.end();
  ioctl_handlers_[request] = h;
  return already_registered;
}

FILE *test_system::register_file(const std::string &path) {
  auto it = registered_files_.find(path);
  if (it == registered_files_.end()) {
    registered_files_[path] =
        "/tmp/testfile" + std::to_string(registered_files_.size());
  }

  auto fptr = fopen(path.c_str(), "w+");
  return fptr;
}

void test_system::normalize_guid(std::string &guid_str, bool with_hyphens) {
  // normalizing a guid string can make it easier to compare guid strings
  // and can also put the string in a format that can be parsed into actual
  // guid bytes (uuid_parse expects the string to include hyphens).
  const size_t std_guid_str_size = 36;
  const size_t char_guid_str_size = 32;
  if (guid_str.back() == '\n') {
    guid_str.erase(guid_str.end() - 1);
  }
  std::locale lc;
  auto c_idx = guid_str.find('-');
  if (with_hyphens && c_idx == std::string::npos) {
    // if we want the standard UUID format with hyphens (8-4-4-4-12)
    if (guid_str.size() == char_guid_str_size) {
      int idx = 20;
      while (c_idx != 8) {
        guid_str.insert(idx, 1, '-');
        idx -= 4;
        c_idx = guid_str.find('-');
      }
    } else {
      throw std::invalid_argument("invalid guid string");
    }
  } else if (!with_hyphens && c_idx == 8) {
    // we want the hex characters only, no other extra chars
    if (guid_str.size() == std_guid_str_size) {
      while (c_idx != std::string::npos) {
        guid_str.erase(c_idx, 1);
        c_idx = guid_str.find('-');
      }
    } else {
      throw std::invalid_argument("invalid guid string");
    }
  }

  for (auto &c : guid_str) {
    c = std::tolower(c, lc);
  }
}

uint32_t get_device_id(const std::string &sysclass) {
  uint32_t res(0);
  std::ifstream fs;
  fs.open(sysclass + "/device/device");
  if (fs.is_open()) {
    std::string line;
    std::getline(fs, line);
    fs.close();
    return std::stoul(line, 0, 16);
  }
  return res;
}

std::string test_system::get_sysfs_claass_path(const std::string &path) {
  for (auto it : fpga_sysfs_path_map) {
    if (path.find(it.first) == 0) {
      return it.second;
    }
  }
  return "";
}

int test_system::open(const std::string &path, int flags) {
  if (!initialized_) {
    return open_(path.c_str(), flags);
  }
  std::string syspath = get_sysfs_path(path);
  int fd;
  auto r1 = regex<>::create(sysclass_pattern);
  auto r2 = regex<>::create(dev_pattern);
  match_t::ptr_t m;

  // check if we are opening a driver attribute file
  // or a device file to save the fd in an internal map
  // this can be used later, (especially in ioctl)
  if (r1 && (m = r1->match(path))) {
    // path matches /sys/class/fpga/intel-fpga-dev\..*
    // we are opening a driver attribute file

    if (flags == O_WRONLY) {
      // truncate the file to zero to emulate the sysfs behavior.
      flags |= O_TRUNC;
    }

    fd = open_(syspath.c_str(), flags);
    auto sysclass_path = m->group(0);
    auto device_id = get_device_id(get_sysfs_path(sysclass_path));
    std::lock_guard<std::mutex> guard(fds_mutex_);
    fds_[fd] = new mock_object(path, sysclass_path, device_id);
  } else if (r2 && (m = r2->match(path))) {
    // path matches /dev/intel-fpga-(fme|port)\..*
    // we are opening a device
    fd = open_(syspath.c_str(), flags);
    auto sysclass_path = get_sysfs_claass_path(path) + m->group(3);
    auto device_id = get_device_id(get_sysfs_path(sysclass_path));
    if (m->group(2) == "fme") {
      std::lock_guard<std::mutex> guard(fds_mutex_);
      fds_[fd] = new mock_fme(path, sysclass_path, device_id);
    } else if (m->group(2) == "port") {
      std::lock_guard<std::mutex> guard(fds_mutex_);
      fds_[fd] = new mock_port(path, sysclass_path, device_id);
    }
  } else {
    fd = open_(syspath.c_str(), flags);
  }
  return fd;
}

int test_system::open(const std::string &path, int flags, mode_t mode) {
  if (!initialized_) {
    return open_create_(path.c_str(), flags, mode);
  }

  std::string syspath = get_sysfs_path(path);
  int fd = open_create_(syspath.c_str(), flags, mode);
  if (syspath.find(root_) == 0) {
    std::lock_guard<std::mutex> guard(fds_mutex_);
    std::map<int, mock_object *>::iterator it = fds_.find(fd);
    if (it != fds_.end()) {
      delete it->second;
    }
    fds_[fd] = new mock_object(path, "", 0);
  }
  return fd;
}

static bool _invalidate_read = false;
static uint32_t _invalidate_read_after = 0;
static const char *_invalidate_read_when_called_from = nullptr;
void test_system::invalidate_read(uint32_t after,
                                  const char *when_called_from) {
  _invalidate_read = true;
  _invalidate_read_after = after;
  _invalidate_read_when_called_from = when_called_from;
}

ssize_t test_system::read(int fd, void *buf, size_t count) {
  if (_invalidate_read) {
    if (!_invalidate_read_when_called_from) {
      if (!_invalidate_read_after) {
        _invalidate_read = false;
        return -1;
      }

      --_invalidate_read_after;

    } else {
      // 2 here, because we were called through..
      // 0 test_system.cpp:opae_test_read()
      // 1 mock.c:read()
      // 2 <caller>
      void *caller = __builtin_return_address(2);
      int res;
      Dl_info info;

      dladdr(caller, &info);
      if (!info.dli_sname)
        res = 1;
      else
        res = strcmp(info.dli_sname, _invalidate_read_when_called_from);

      if (!_invalidate_read_after && !res) {
        _invalidate_read = false;
        _invalidate_read_when_called_from = nullptr;
        return -1;
      } else if (!res)
        --_invalidate_read_after;
    }
  }
  return read_(fd, buf, count);
}

FILE *test_system::fopen(const std::string &path, const std::string &mode) {
  std::string syspath = get_sysfs_path(path);
  return fopen_(syspath.c_str(), mode.c_str());
}

FILE *test_system::popen(const std::string &cmd, const std::string &type) {
  // Is this something we're interested in?
  if (0 == cmd.compare(0, 5, "rdmsr")) {
    char tmpfile[20];
    strcpy(tmpfile, "popen-XXXXXX.tmp");
    close(mkstemps(tmpfile, 4));

    FILE *fp = fopen(tmpfile, "w+");

    size_t last_spc = cmd.find_last_of(' ');
    std::string msr(cmd.substr(last_spc + 1));

    if (0 == msr.compare("0x35")) {
      fprintf(fp, "0x0000000000180030");
    } else if (0 == msr.compare("0x610")) {
      fprintf(fp, "0x000388d000148758");
    } else if (0 == msr.compare("0x606")) {
      fprintf(fp, "0x00000000000a0e03");
    }

    fseek(fp, 0, SEEK_SET);
    popen_requests_.insert(std::make_pair(fp, tmpfile));

    return fp;
  } else {
    return popen_(cmd.c_str(), type.c_str());
  }
}

int test_system::pclose(FILE *stream) {
  // Is this something we intercepted?
  std::map<FILE *, std::string>::iterator it = popen_requests_.find(stream);
  if (it != popen_requests_.end()) {
    unlink(it->second.c_str());
    popen_requests_.erase(it);
    fclose(stream);
    return 0;  // process exit status
  }
  return pclose_(stream);
}

int test_system::close(int fd) {
  if (initialized_) {
    std::lock_guard<std::mutex> guard(fds_mutex_);
    std::map<int, mock_object *>::iterator it = fds_.find(fd);
    if (it != fds_.end()) {
      delete it->second;
      fds_.erase(it);
    }
  }
  return close_(fd);
}

int test_system::ioctl(int fd, unsigned long request, va_list argp) {
  mock_object *mo = nullptr;
  {
    std::lock_guard<std::mutex> guard(fds_mutex_);
    auto mi = fds_.find(fd);
    if (mi != fds_.end()) {
      mo = mi->second;
    }
  }

  if (mo == nullptr) {
    char *arg = va_arg(argp, char *);
    return ioctl_(fd, request, arg);
  }

  // replace mock_it->second with mo
  auto handler_it = ioctl_handlers_.find(request);
  if (handler_it != ioctl_handlers_.end()) {
    return handler_it->second(mo, request, argp);
  }
  return mo->ioctl(request, argp);
}

DIR *test_system::opendir(const char *path) {
  std::string syspath = get_sysfs_path(path);
  return opendir_(syspath.c_str());
}

ssize_t test_system::readlink(const char *path, char *buf, size_t bufsize) {
  std::string syspath = get_sysfs_path(path);
  return readlink_(syspath.c_str(), buf, bufsize);
}

int test_system::xstat(int ver, const char *path, struct stat *buf) {
  std::string syspath = get_sysfs_path(path);
  int res = xstat_(ver, syspath.c_str(), buf);

  if (!res && strlen(path) > 5) {
    // If path is rooted at /dev, assume it is a char device.
    std::string p(path, 5);
    if (p == std::string("/dev/")) {
            buf->st_mode &= ~S_IFMT;
            buf->st_mode |= S_IFCHR;
    }
  }

  return res;
}

int test_system::lstat(int ver, const char *path, struct stat *buf) {
  std::string syspath = get_sysfs_path(path);
  int res = lstat_(ver, syspath.c_str(), buf);

  if (!res && strlen(path) > 5) {
    // If path is rooted at /dev, assume it is a char device.
    std::string p(path, 5);
    if (p == std::string("/dev/")) {
            buf->st_mode &= ~S_IFMT;
            buf->st_mode |= S_IFCHR;
    }
  }

  return res;
}

int test_system::scandir(const char *dirp, struct dirent ***namelist,
                         filter_func filter, compare_func cmp) {
  std::string syspath = get_sysfs_path(dirp);
  return scandir_(syspath.c_str(), namelist, filter, cmp);
}

int test_system::sched_setaffinity(pid_t pid, size_t cpusetsize,
                                   const cpu_set_t *mask) {
  UNUSED_PARAM(pid);
  UNUSED_PARAM(cpusetsize);
  UNUSED_PARAM(mask);
  if (hijack_sched_setaffinity_) {
    if (!hijack_sched_setaffinity_caller_) {
      if (!hijack_sched_setaffinity_after_) {
        hijack_sched_setaffinity_ = false;
        int res = hijack_sched_setaffinity_return_val_;
        hijack_sched_setaffinity_return_val_ = 0;
        return res;
      }

      --hijack_sched_setaffinity_after_;

    } else {
      // 2 here, because we were called through..
      // 0 test_system.cpp:opae_test_sched_setaffinity()
      // 1 mock.c:sched_setaffinity()
      // 2 <caller>
      void *caller = __builtin_return_address(2);
      int res;
      Dl_info info;

      dladdr(caller, &info);
      if (!info.dli_sname)
        res = 1;
      else
        res = strcmp(info.dli_sname, hijack_sched_setaffinity_caller_);

      if (!hijack_sched_setaffinity_after_ && !res) {
        hijack_sched_setaffinity_ = false;
        hijack_sched_setaffinity_caller_ = nullptr;
        res = hijack_sched_setaffinity_return_val_;
        hijack_sched_setaffinity_return_val_ = 0;
        return res;
      } else if (!res)
        --hijack_sched_setaffinity_after_;
    }
  }
  return 0;  // return success - we don't actually
             // want to change the affinity.
}

void test_system::hijack_sched_setaffinity(int return_val, uint32_t after,
                                           const char *when_called_from) {
  hijack_sched_setaffinity_ = true;
  hijack_sched_setaffinity_return_val_ = return_val;
  hijack_sched_setaffinity_after_ = after;
  hijack_sched_setaffinity_caller_ = when_called_from;
}

int test_system::glob(const char *pattern, int flags,
                      int (*errfunc)(const char *epath, int eerrno),
                      glob_t *pglob) {
  if (pattern == nullptr) {
    return glob_(pattern, flags, errfunc, pglob);
  }

  auto path = get_sysfs_path(pattern);

  auto res = glob_(path.c_str(), flags, errfunc, pglob);
  if (!res) {
    for (unsigned int i = 0; i < pglob->gl_pathc; ++i) {
      std::string tmppath(pglob->gl_pathv[i]);
      if (tmppath.find(get_root()) == 0) {
        auto p = pglob->gl_pathv[i];
        auto root_len = get_root().size();
        auto new_len = tmppath.size() - root_len;
        std::copy(tmppath.begin() + root_len, tmppath.end(), p);
        p[new_len] = '\0';
      }
    }
  }

  return res;
}

char *test_system::realpath(const char *inp, char *dst)
{
  if (!initialized_ || root_.empty()) {
    return realpath_(inp, dst);
  }
  bool current_inv_state = _invalidate_malloc;
  _invalidate_malloc = false;
  char *retvalue = realpath_(get_sysfs_path(inp).c_str(), dst);
  if (retvalue) {
    std::string dst_str(dst);
    char prefix[PATH_MAX] = {0};
    char *prefix_ptr = realpath_(root_.c_str(), prefix);
    std::string prefix_str(prefix_ptr ? prefix_ptr : "");
    if (prefix_str.size() && dst_str.find(prefix_str) == 0) {
      auto cleaned_str = dst_str.substr(prefix_str.size());
      std::copy(cleaned_str.begin(), cleaned_str.end(), &dst[0]);
      dst[cleaned_str.size()] = '\0';
      retvalue = &dst[0];
    }
  }
  _invalidate_malloc = current_inv_state;
  return retvalue;
}

void test_system::invalidate_malloc(uint32_t after,
                                    const char *when_called_from) {
  _invalidate_malloc = true;
  _invalidate_malloc_after = after;
  _invalidate_malloc_when_called_from = when_called_from;
}

void test_system::invalidate_calloc(uint32_t after,
                                    const char *when_called_from) {
  _invalidate_calloc = true;
  _invalidate_calloc_after = after;
  _invalidate_calloc_when_called_from = when_called_from;
}

}  // end of namespace testing
}  // end of namespace opae

// C functions

int opae_test_open(const char *path, int flags) {
  return opae::testing::test_system::instance()->open(path, flags);
}

int opae_test_open_create(const char *path, int flags, mode_t mode) {
  return opae::testing::test_system::instance()->open(path, flags, mode);
}

ssize_t opae_test_read(int fd, void *buf, size_t count) {
  return opae::testing::test_system::instance()->read(fd, buf, count);
}

FILE *opae_test_fopen(const char *path, const char *mode) {
  return opae::testing::test_system::instance()->fopen(path, mode);
}

FILE *opae_test_popen(const char *cmd, const char *type) {
  return opae::testing::test_system::instance()->popen(cmd, type);
}

int opae_test_pclose(FILE *stream) {
  return opae::testing::test_system::instance()->pclose(stream);
}

int opae_test_close(int fd) {
  return opae::testing::test_system::instance()->close(fd);
}

int opae_test_ioctl(int fd, unsigned long request, va_list argp) {
  return opae::testing::test_system::instance()->ioctl(fd, request, argp);
}

DIR *opae_test_opendir(const char *name) {
  return opae::testing::test_system::instance()->opendir(name);
}

ssize_t opae_test_readlink(const char *path, char *buf, size_t bufsize) {
  return opae::testing::test_system::instance()->readlink(path, buf, bufsize);
}

int opae_test_xstat(int ver, const char *path, struct stat *buf) {
  return opae::testing::test_system::instance()->xstat(ver, path, buf);
}

int opae_test_lstat(int ver, const char *path, struct stat *buf) {
  return opae::testing::test_system::instance()->lstat(ver, path, buf);
}

int opae_test_scandir(const char *dirp, struct dirent ***namelist,
                      filter_func filter, compare_func cmp) {
  return opae::testing::test_system::instance()->scandir(dirp, namelist, filter,
                                                         cmp);
}

int opae_test_sched_setaffinity(pid_t pid, size_t cpusetsize,
                                const cpu_set_t *mask) {
  return opae::testing::test_system::instance()->sched_setaffinity(
      pid, cpusetsize, mask);
}

int opae_test_glob(const char *pattern, int flags,
                   int (*errfunc)(const char *epath, int eerrno),
                   glob_t *pglob) {
  return opae::testing::test_system::instance()->glob(pattern, flags, errfunc,
                                                      pglob);
}

char *opae_test_realpath(const char *inp, char *dst) {
  return opae::testing::test_system::instance()->realpath(inp, dst);
}
