/* -*- c-file-style: "GNU" -*- */
/*
 * Copyright (C) CNRS, INRIA, Universite Bordeaux 1, Telecom SudParis
 * See COPYING in top-level directory.
 */

#ifndef _REENTRANT
#define _REENTRANT
#endif

#include <eztrace-core/eztrace_config.h>
#include <eztrace-instrumentation/pptrace.h>
#include <eztrace-lib/eztrace.h>
#include <eztrace-lib/eztrace_module.h>
#include <pthread.h>
#include <semaphore.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <otf2/OTF2_AttributeList.h>

/* set to 1 when all the hooks are set.
 * This is usefull in order to avoid recursive calls to mutex_lock for example
 */
static volatile int _pthread_initialized = 0;

#define CURRENT_MODULE pthread
DECLARE_CURRENT_MODULE;


/* pointers to actual pthread functions */
int (*libpthread_mutex_lock)(pthread_mutex_t* mutex) = NULL;
int (*libpthread_mutex_trylock)(pthread_mutex_t* mutex);
int (*libpthread_mutex_unlock)(pthread_mutex_t* mutex);

int (*libpthread_spin_lock)(pthread_spinlock_t* lock);
int (*libpthread_spin_unlock)(pthread_spinlock_t* lock);
int (*libpthread_spin_trylock)(pthread_spinlock_t* lock);

int (*libpthread_barrier_wait)(pthread_barrier_t* barrier);

int (*libpthread_sem_wait)(sem_t* sem);
int (*libpthread_sem_post)(sem_t* sem);

int (*libpthread_cond_init)(pthread_cond_t* __restrict _cond,
                            __const pthread_condattr_t* __restrict _cond_attr);

int (*libpthread_cond_wait)(pthread_cond_t* __restrict cond,
                            pthread_mutex_t* __restrict mutex);
int (*libpthread_cond_broadcast)(pthread_cond_t* cond);
int (*libpthread_cond_signal)(pthread_cond_t* cond);
int (*libpthread_cond_destroy)(pthread_cond_t* _cond);

int (*libpthread_cond_timedwait)(pthread_cond_t* __restrict _cond,
                                 pthread_mutex_t* __restrict _mutex,
                                 __const struct timespec* __restrict _abstime);

int (*libpthread_rwlock_rdlock)(pthread_rwlock_t* rwlock);
int (*libpthread_rwlock_wrlock)(pthread_rwlock_t* rwlock);
int (*libpthread_rwlock_unlock)(pthread_rwlock_t* rwlock);

/* Mutex-related callbacks */
int pthread_mutex_lock(pthread_mutex_t* mutex) {
  FUNCTION_ENTRY_WITH_ARGS(mutex);
  
  /* If the current file's constructor has not been called yet,
   this means that the application is being initialized and that
   there's only one thread. Thus, we do not to call mutex_lock for
   real.
   If you try to call mutex_lock in that case, you'll get a SIGSEVG
   since dlsym was not called yet for mutex_lock
   */
  int ret = 0;
  if (_pthread_initialized) {
    ret = libpthread_mutex_lock(mutex);
  }
  /* _pthread_initialized is set to 0. The application is initializing,
   so don't do anything
   */
  FUNCTION_EXIT;
  return ret;
}

int pthread_mutex_trylock(pthread_mutex_t* mutex) {
  FUNCTION_ENTRY_WITH_ARGS(mutex);
  int ret =0;
  if (_pthread_initialized) {
    ret = libpthread_mutex_trylock(mutex);
  }
  FUNCTION_EXIT_WITH_ARGS(ret);
  return ret;
}

int pthread_mutex_unlock(pthread_mutex_t* mutex) {
  int ret = 0;
  if (_pthread_initialized) {
    FUNCTION_ENTRY_WITH_ARGS(mutex);
    ret = libpthread_mutex_unlock(mutex);
    FUNCTION_EXIT;
  }
  return ret;
}

/* Spinlock-related callbacks */
int pthread_spin_lock(pthread_spinlock_t* lock) {
  FUNCTION_ENTRY_WITH_ARGS(lock);
  INTERCEPT_FUNCTION("pthread_spin_lock", libpthread_spin_lock);
  int ret = libpthread_spin_lock(lock);
  FUNCTION_EXIT;
  return ret;
}

int pthread_spin_unlock(pthread_spinlock_t* lock) {
  FUNCTION_ENTRY_WITH_ARGS(lock);
  INTERCEPT_FUNCTION("pthread_spin_unlock", libpthread_spin_unlock);  
  int ret = libpthread_spin_unlock(lock);
  FUNCTION_EXIT;
  return ret;
}

int pthread_spin_trylock(pthread_spinlock_t* lock) {
  FUNCTION_ENTRY_WITH_ARGS(lock);
  INTERCEPT_FUNCTION("pthread_spin_trylock", libpthread_spin_trylock);
  int ret = libpthread_spin_trylock(lock);
  FUNCTION_EXIT_WITH_ARGS(ret);
  return ret;
}

/* Barrier-related callbacks */
int pthread_barrier_wait(pthread_barrier_t* barrier) {
  FUNCTION_ENTRY_WITH_ARGS(barrier);
  INTERCEPT_FUNCTION("pthread_barrier_wait", libpthread_barrier_wait);
  int ret = libpthread_barrier_wait(barrier);
  FUNCTION_EXIT;
  return ret;
}

/* Semaphore-related callbacks */
int sem_wait(sem_t* sem) {
  FUNCTION_ENTRY_WITH_ARGS(sem);
  INTERCEPT_FUNCTION("sem_wait", libpthread_sem_wait);
  int ret = libpthread_sem_wait(sem);
  FUNCTION_EXIT;
  return ret;
}

int sem_post(sem_t* sem) {
  FUNCTION_ENTRY_WITH_ARGS(sem);
  INTERCEPT_FUNCTION("sem_post", libpthread_sem_post);
  int ret = libpthread_sem_post(sem);
  FUNCTION_EXIT;
  return ret;
}

/* Condition-related callbacks */

/* We don't actually need these (init, destroy) functions, but if we don't
 * catch these functions, it will lead to using somehow different implementations
 * (ie. cond_init from one implementation and cond_wait from another) that are
 * not compatible, resulting in data corruption :(
 */
int pthread_cond_init(pthread_cond_t* __restrict cond,
                      __const pthread_condattr_t* __restrict _cond_attr) {
  FUNCTION_ENTRY_WITH_ARGS(cond);
  INTERCEPT_FUNCTION("pthread_cond_init", libpthread_cond_init);
  int ret = libpthread_cond_init(cond, _cond_attr);
  FUNCTION_EXIT;
  return ret;
}

int pthread_cond_destroy(pthread_cond_t* cond) {
  FUNCTION_ENTRY_WITH_ARGS(cond);
  INTERCEPT_FUNCTION("pthread_cond_destroy", libpthread_cond_destroy);
  int ret = libpthread_cond_destroy(cond);
  FUNCTION_EXIT;
  return ret;
}

int pthread_cond_wait(pthread_cond_t* __restrict cond,
                      pthread_mutex_t* __restrict mutex) {
  FUNCTION_ENTRY_WITH_ARGS(cond, mutex);
  INTERCEPT_FUNCTION("pthread_cond_wait", libpthread_cond_wait);
  int ret = libpthread_cond_wait(cond, mutex);
  FUNCTION_EXIT;
  return ret;
}

int pthread_cond_timedwait(pthread_cond_t* __restrict cond,
                           pthread_mutex_t* __restrict mutex,
                           __const struct timespec* __restrict _abstime) {
  FUNCTION_ENTRY_WITH_ARGS(cond, mutex);
  INTERCEPT_FUNCTION("pthread_cond_timedwait", libpthread_cond_timedwait);
  int ret = libpthread_cond_timedwait(cond, mutex, _abstime);
  /* todo: what if the timer expires ? */
  FUNCTION_EXIT;
  return ret;
}

int pthread_cond_broadcast(pthread_cond_t* cond) {
  FUNCTION_ENTRY_WITH_ARGS(cond);
  INTERCEPT_FUNCTION("pthread_cond_broadcast", libpthread_cond_broadcast);
  int ret = libpthread_cond_broadcast(cond);
  FUNCTION_EXIT;
  return ret;
}

int pthread_cond_signal(pthread_cond_t* cond) {
  FUNCTION_ENTRY_WITH_ARGS(cond);
  INTERCEPT_FUNCTION("pthread_cond_signal", libpthread_cond_signal);
  int ret = libpthread_cond_signal(cond);
  FUNCTION_EXIT;
  return ret;
}

/* RWLock-related callbacks */
int pthread_rwlock_rdlock(pthread_rwlock_t* rwlock) {
  FUNCTION_ENTRY_WITH_ARGS(rwlock);
  INTERCEPT_FUNCTION("pthread_rwlock_rdlock", libpthread_rwlock_rdlock);
  int ret = libpthread_rwlock_rdlock(rwlock);
  FUNCTION_EXIT;
  return ret;
}

int pthread_rwlock_wrlock(pthread_rwlock_t* rwlock) {
  FUNCTION_ENTRY_WITH_ARGS(rwlock);
  INTERCEPT_FUNCTION("pthread_rwlock_wrlock", libpthread_rwlock_wrlock);
  int ret = libpthread_rwlock_wrlock(rwlock);
  FUNCTION_EXIT;
  return ret;
}

int pthread_rwlock_unlock(pthread_rwlock_t* rwlock) {
  FUNCTION_ENTRY_WITH_ARGS(rwlock);
  INTERCEPT_FUNCTION("pthread_rwlock_unlock", libpthread_rwlock_unlock);
  int ret = libpthread_rwlock_unlock(rwlock);
  FUNCTION_EXIT;
  return ret;
}

PPTRACE_START_INTERCEPT_FUNCTIONS(pthread)
INTERCEPT3("pthread_mutex_lock", libpthread_mutex_lock)
INTERCEPT3("pthread_mutex_trylock", libpthread_mutex_trylock)
INTERCEPT3("pthread_mutex_unlock", libpthread_mutex_unlock)
INTERCEPT3("pthread_spin_lock", libpthread_spin_lock)
INTERCEPT3("pthread_spin_unlock", libpthread_spin_unlock)
INTERCEPT3("pthread_spin_trylock", libpthread_spin_trylock)
INTERCEPT3("pthread_barrier_wait", libpthread_barrier_wait)
INTERCEPT3("sem_wait", libpthread_sem_wait)
INTERCEPT3("sem_post", libpthread_sem_post)
INTERCEPT3("pthread_cond_wait", libpthread_cond_wait)
INTERCEPT3("pthread_cond_timedwait", libpthread_cond_timedwait)
INTERCEPT3("pthread_cond_broadcast", libpthread_cond_broadcast)
INTERCEPT3("pthread_cond_signal", libpthread_cond_signal)
INTERCEPT3("pthread_cond_init", libpthread_cond_init)
INTERCEPT3("pthread_cond_destroy", libpthread_cond_destroy)
INTERCEPT3("pthread_rwlock_rdlock", libpthread_rwlock_rdlock)
INTERCEPT3("pthread_rwlock_wrlock", libpthread_rwlock_wrlock)
INTERCEPT3("pthread_rwlock_unlock", libpthread_rwlock_unlock)
PPTRACE_END_INTERCEPT_FUNCTIONS(pthread)

static void init_pthread() {
  INSTRUMENT_FUNCTIONS(pthread);

  if (eztrace_autostart_enabled())
    eztrace_start();

  _pthread_initialized = 1;
}

static void finalize_pthread() {
  _pthread_initialized = 0;

  eztrace_stop();
}

static void _pthread_init(void) __attribute__((constructor));
static void _pthread_init(void) {
  eztrace_log(dbg_lvl_debug, "eztrace_pthread constructor starts\n");
  EZT_REGISTER_MODULE(pthread, "Module for pthread synchronization functions (mutex, spinlock, etc.)",
		      init_pthread, finalize_pthread);
  eztrace_log(dbg_lvl_debug, "eztrace_pthread constructor ends\n");
}
