// Copyright 2017 The Crashpad Authors
//
// 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 "dlfcn_internal.h"

#include <android/api-level.h>
#include <dlfcn.h>
#include <errno.h>
#include <setjmp.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <sys/syscall.h>
#include <sys/system_properties.h>
#include <unistd.h>

#include <mutex>

namespace crashpad {
namespace internal {

// KitKat supports API levels up to 20.
#if __ANDROID_API__ < 21

namespace {

class ScopedSigactionRestore {
 public:
  ScopedSigactionRestore() : old_action_(), signo_(-1), valid_(false) {}

  ~ScopedSigactionRestore() { Reset(); }

  bool Reset() {
    bool result = true;
    if (valid_) {
      result = sigaction(signo_, &old_action_, nullptr) == 0;
      if (!result) {
        PrintErrmsg(errno);
      }
    }
    valid_ = false;
    signo_ = -1;
    return result;
  }

  bool ResetAndInstallHandler(int signo,
                              void (*handler)(int, siginfo_t*, void*)) {
    Reset();

    struct sigaction act;
    act.sa_sigaction = handler;
    sigemptyset(&act.sa_mask);
    act.sa_flags = SA_SIGINFO;
    if (sigaction(signo, &act, &old_action_) != 0) {
      PrintErrmsg(errno);
      return false;
    }
    signo_ = signo;
    valid_ = true;
    return true;
  }

 private:
  void PrintErrmsg(int err) {
    char errmsg[256];

    if (strerror_r(err, errmsg, sizeof(errmsg)) != 0) {
      snprintf(errmsg,
               sizeof(errmsg),
               "%s:%d: Couldn't set errmsg for %d: %d",
               __FILE__,
               __LINE__,
               err,
               errno);
      return;
    }

    fprintf(stderr, "%s:%d: sigaction: %s", __FILE__, __LINE__, errmsg);
  }

  struct sigaction old_action_;
  int signo_;
  bool valid_;
};

bool IsKitKat() {
  char prop_buf[PROP_VALUE_MAX];
  int length = __system_property_get("ro.build.version.sdk", prop_buf);
  if (length <= 0) {
    fprintf(stderr, "%s:%d: Couldn't get version", __FILE__, __LINE__);
    // It's safer to assume this is KitKat and execute dlsym with a signal
    // handler installed.
    return true;
  }
  if (strcmp(prop_buf, "19") == 0 || strcmp(prop_buf, "20") == 0) {
    return true;
  }
  return false;
}

class ScopedSetTID {
 public:
  explicit ScopedSetTID(pid_t* tid) : tid_(tid) { *tid_ = syscall(SYS_gettid); }

  ~ScopedSetTID() { *tid_ = -1; }

 private:
  pid_t* tid_;
};

sigjmp_buf dlsym_sigjmp_env;

pid_t dlsym_tid = -1;

void HandleSIGFPE(int signo, siginfo_t* siginfo, void* context) {
  if (siginfo->si_code != FPE_INTDIV || syscall(SYS_gettid) != dlsym_tid) {
    return;
  }
  siglongjmp(dlsym_sigjmp_env, 1);
}

}  // namespace

void* Dlsym(void* handle, const char* symbol) {
  if (!IsKitKat()) {
    return dlsym(handle, symbol);
  }

  static std::mutex* signal_handler_mutex = new std::mutex();
  std::lock_guard<std::mutex> lock(*signal_handler_mutex);

  ScopedSetTID set_tid(&dlsym_tid);

  ScopedSigactionRestore sig_restore;
  if (!sig_restore.ResetAndInstallHandler(SIGFPE, HandleSIGFPE)) {
    return nullptr;
  }

  if (sigsetjmp(dlsym_sigjmp_env, 1) != 0) {
    return nullptr;
  }

  return dlsym(handle, symbol);
}

#else

void* Dlsym(void* handle, const char* symbol) {
  return dlsym(handle, symbol);
}

#endif  // __ANDROID_API__ < 21

}  // namespace internal
}  // namespace crashpad
