/*
 * Copyright (C) 2019 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 "palette/palette.h"

#include <dlfcn.h>
#include <stdlib.h>

#include <android/log.h>
#include <android-base/macros.h>

namespace {

// Logging tag.
static constexpr const char* kLogTag = "libartpalette";

// Name of the palette library present in the /system partition.
static constexpr const char* kPaletteSystemLibrary = "libartpalette-system.so";

// Generic method used when a dynamically loaded palette instance does not
// support a method.
palette_status_t PaletteMethodNotSupported() {
  return PALETTE_STATUS_NOT_SUPPORTED;
}

// Declare type aliases for pointers to each function in the interface.
#define PALETTE_METHOD_TYPE_ALIAS(Name, ...) \
  using Name ## Method = palette_status_t(*)(__VA_ARGS__);
PALETTE_METHOD_LIST(PALETTE_METHOD_TYPE_ALIAS)
#undef PALETTE_METHOD_TYPE_ALIAS

// Singleton class responsible for dynamically loading the palette library and
// binding functions there to method pointers.
class PaletteLoader {
 public:
  static PaletteLoader& Instance() {
    static PaletteLoader instance;
    return instance;
  }

  // Accessor methods to get instances of palette methods.
#define PALETTE_LOADER_METHOD_ACCESSOR(Name, ...)                       \
  Name ## Method Get ## Name ## Method() const { return Name ## Method ## _; }
PALETTE_METHOD_LIST(PALETTE_LOADER_METHOD_ACCESSOR)
#undef PALETTE_LOADER_METHOD_ACCESSOR

 private:
  PaletteLoader();

  static void* OpenLibrary();
  static void* GetMethod(void* palette_lib, const char* name);

  // Handle to the palette library from dlopen().
  void* palette_lib_;

  // Fields to store pointers to palette methods.
#define PALETTE_LOADER_METHOD_FIELD(Name, ...) \
  const Name ## Method Name ## Method ## _;
  PALETTE_METHOD_LIST(PALETTE_LOADER_METHOD_FIELD)
#undef PALETTE_LOADER_METHOD_FIELD

  DISALLOW_COPY_AND_ASSIGN(PaletteLoader);
};

void* PaletteLoader::OpenLibrary() {
  void* handle = dlopen(kPaletteSystemLibrary, RTLD_NOW | RTLD_GLOBAL | RTLD_NODELETE);
  if (handle == nullptr) {
    // dlerror message includes details of error and file being opened.
    __android_log_assert(nullptr, kLogTag, "%s", dlerror());
  }
  return handle;
}

void* PaletteLoader::GetMethod(void* palette_lib, const char* name) {
  void* method = nullptr;
  if (palette_lib != nullptr) {
    method = dlsym(palette_lib, name);
  }
  if (method == nullptr) {
    return reinterpret_cast<void*>(PaletteMethodNotSupported);
  }
  // TODO(oth): consider new GetMethodSignature() in the Palette API which
  // would allow checking the validity of the type signatures.
  return method;
}

PaletteLoader::PaletteLoader() :
    palette_lib_(OpenLibrary())
#define PALETTE_LOADER_BIND_METHOD(Name, ...)                           \
    , Name ## Method ## _(reinterpret_cast<Name ## Method>(GetMethod(palette_lib_, #Name)))
    PALETTE_METHOD_LIST(PALETTE_LOADER_BIND_METHOD)
#undef PALETTE_LOADER_BIND_METHOD
{
}

}  // namespace

extern "C" {

palette_status_t PaletteSchedSetPriority(int32_t tid, int32_t java_priority) {
  PaletteSchedSetPriorityMethod m = PaletteLoader::Instance().GetPaletteSchedSetPriorityMethod();
  return m(tid, java_priority);
}

palette_status_t PaletteSchedGetPriority(int32_t tid, /*out*/int32_t* java_priority) {
  PaletteSchedGetPriorityMethod m = PaletteLoader::Instance().GetPaletteSchedGetPriorityMethod();
  return m(tid, java_priority);
}

palette_status_t PaletteWriteCrashThreadStacks(/*in*/const char* stack, size_t stack_len) {
  PaletteWriteCrashThreadStacksMethod m =
      PaletteLoader::Instance().GetPaletteWriteCrashThreadStacksMethod();
  return m(stack, stack_len);
}

palette_status_t PaletteTraceEnabled(/*out*/int32_t* enabled) {
  PaletteTraceEnabledMethod m = PaletteLoader::Instance().GetPaletteTraceEnabledMethod();
  return m(enabled);
}

palette_status_t PaletteTraceBegin(/*in*/const char* name) {
  PaletteTraceBeginMethod m = PaletteLoader::Instance().GetPaletteTraceBeginMethod();
  return m(name);
}

palette_status_t PaletteTraceEnd() {
  PaletteTraceEndMethod m = PaletteLoader::Instance().GetPaletteTraceEndMethod();
  return m();
}

palette_status_t PaletteTraceIntegerValue(/*in*/const char* name, int32_t value) {
  PaletteTraceIntegerValueMethod m = PaletteLoader::Instance().GetPaletteTraceIntegerValueMethod();
  return m(name, value);
}

palette_status_t PaletteAshmemCreateRegion(const char* name, size_t size, int* fd) {
  PaletteAshmemCreateRegionMethod m =
      PaletteLoader::Instance().GetPaletteAshmemCreateRegionMethod();
  return m(name, size, fd);
}

palette_status_t PaletteAshmemSetProtRegion(int fd, int prot) {
  PaletteAshmemSetProtRegionMethod m =
      PaletteLoader::Instance().GetPaletteAshmemSetProtRegionMethod();
  return m(fd, prot);
}

palette_status_t PaletteGetHooks(PaletteHooks** hooks) {
  PaletteGetHooksMethod m =
      PaletteLoader::Instance().GetPaletteGetHooksMethod();
  return m(hooks);
}

palette_status_t PaletteCreateOdrefreshStagingDirectory(const char** staging_dir) {
  PaletteCreateOdrefreshStagingDirectoryMethod m =
      PaletteLoader::Instance().GetPaletteCreateOdrefreshStagingDirectoryMethod();
  return m(staging_dir);
}

}  // extern "C"
