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

#ifndef _REENTRANT
#define _REENTRANT
#endif

#include "posix_io_otf2.h"
#include <eztrace-lib/eztrace.h>
#include <eztrace-lib/eztrace_module.h>

/* crappy hack here:
 * we need to include fcntl.h so that O_* flags are defined
 *
 * but if we do so, the compiler complains about the prototype of open* functions
 * that should be variadic.
 * Since we can't wrap a variadic function, we need to include only the part of fcntl.h
 * that defines the O_* flags.
 * This part is in bits/fcntl.h but it is protected by #ifndef _FCNTL_H
 * So, let's define _FCNTL_H and hope everything is fine.
 */
#define _FCNTL_H
#include <bits/fcntl.h>

#define CURRENT_MODULE posixio
DECLARE_CURRENT_MODULE;


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



FILE *(*libfopen)(const char *pathname, const char *mode);
FILE *fopen(const char *pathname, const char *mode) {
  INTERCEPT_FUNCTION("fopen", libfopen);

  FUNCTION_ENTRY;
  FILE* ret = libfopen(pathname, mode);
  if(ret)
    otf2_fopen_file(pathname, mode, ret);

  FUNCTION_EXIT;
  return ret;
}

FILE *(*libfdopen)(int fd, const char *mode);
FILE *fdopen(int fd, const char *mode) {
  FUNCTION_ENTRY;
  INTERCEPT_FUNCTION("fdopen", libfdopen);
  FILE* ret = libfdopen(fd, mode);
  /* TODO */
  //  struct ezt_file_handle*handle = get_file_handle_fd(fd);
  //  if(handle)
  //    handle->stream = ret;
  FUNCTION_EXIT;
  return ret;
}

FILE *(*libfreopen)(const char *pathname, const char *mode, FILE *stream);
FILE *freopen(const char *pathname, const char *mode, FILE *stream) {
  FUNCTION_ENTRY;
  INTERCEPT_FUNCTION("freopen", libfreopen);
  FILE* ret = libfreopen(pathname, mode, stream);
  if(ret)
    otf2_fopen_file(pathname, mode, ret);

  FUNCTION_EXIT;
  return ret;
}

int (*libfclose)(FILE *stream);
int fclose(FILE *stream) {
  FUNCTION_ENTRY;
  INTERCEPT_FUNCTION("fclose", libfclose);

  otf2_fclose_file(stream);
    
  int ret = libfclose(stream);
  FUNCTION_EXIT;
  return ret;
}


int (*libdup)(int oldfd);
int dup(int oldfd) {
  FUNCTION_ENTRY;
  INTERCEPT_FUNCTION("dup", libdup);
  int ret = libdup(oldfd);
  if(ret >=0)
    otf2_dup_fd(oldfd, ret);

  FUNCTION_EXIT;
  return ret;
}

int (*libdup2)(int oldfd, int newfd);
int dup2(int oldfd, int newfd) {
  FUNCTION_ENTRY;
  INTERCEPT_FUNCTION("dup2", libdup2);
  int ret = libdup2(oldfd, newfd);
  if(ret >=0)
    otf2_dup_fd(oldfd, newfd);

  FUNCTION_EXIT;
  return ret;
}

int (*libdup3)(int oldfd, int newfd, int flags);
int dup3(int oldfd, int newfd, int flags) {
  FUNCTION_ENTRY;
  INTERCEPT_FUNCTION("dup3", libdup3);
  int ret = libdup3(oldfd, newfd, flags);
  if(ret >=0)
    otf2_dup_fd(oldfd, newfd);

  FUNCTION_EXIT;
  return ret;
}

int (*libopen)(const char *pathname, int flags, mode_t mode);
int open(const char *pathname, int flags, mode_t mode){
  FUNCTION_ENTRY;
  INTERCEPT_FUNCTION("open", libopen);
  int ret = libopen(pathname, flags, mode);
  if(ret >=0)
    otf2_open_file(pathname, flags, ret);

  FUNCTION_EXIT;
  return ret;
}

int (*libopen64)(const char *pathname, int flags, mode_t mode);
int open64(const char *pathname, int flags, mode_t mode){
  FUNCTION_ENTRY;
  INTERCEPT_FUNCTION("open64", libopen64);
  int ret = libopen64(pathname, flags, mode);
  if(ret >=0)
    otf2_open_file(pathname, flags, ret);

  FUNCTION_EXIT;
  return ret;
}

int (*libcreat)(const char *pathname, mode_t mode);
int creat(const char *pathname, mode_t mode) {
  FUNCTION_ENTRY;
  INTERCEPT_FUNCTION("creat", libcreat);
  int ret = libcreat(pathname, mode);

  if(ret >=0)
    otf2_open_file(pathname, O_CREAT|O_WRONLY|O_TRUNC, ret);

  FUNCTION_EXIT;
  return ret;
}

int (*libopenat)(int dirfd, const char *pathname, int flags, mode_t mode);
int openat(int dirfd, const char *pathname, int flags, mode_t mode) {
  FUNCTION_ENTRY;
  INTERCEPT_FUNCTION("openat", libopenat);
  int ret = libopenat(dirfd, pathname, flags, mode);

  if(ret >=0)
    otf2_open_file(pathname, flags, ret);

  FUNCTION_EXIT;
  return ret;
}

int __openat64_(int dirfd, const char *pathname, int flags, mode_t mode) __attribute__((alias ("openat64")));
int (*libopenat64)(int dirfd, const char *pathname, int flags, mode_t mode);
int openat64(int dirfd, const char *pathname, int flags, mode_t mode) {
  FUNCTION_ENTRY;
  INTERCEPT_FUNCTION("openat64", libopenat64);
  int ret = libopenat64(dirfd, pathname, flags, mode);

  if(ret >=0)
    otf2_open_file(pathname, flags, ret);

  FUNCTION_EXIT;
  return ret;
}

int (*libclose)(int fd);
int close(int fd) {
  FUNCTION_ENTRY;
  INTERCEPT_FUNCTION("close", libclose);
  int ret =0;

  if(fd>2) {
    otf2_close_file(fd);
    ret = libclose(fd);
  }
  
  FUNCTION_EXIT;
  return ret;
}

ssize_t (*libread)(int fd, void* buf, size_t count);
ssize_t read(int fd, void* buf, size_t count) {
  FUNCTION_ENTRY;
  INTERCEPT_FUNCTION("read", libread);

  struct context context;
  otf2_begin_fd_operation(fd, OTF2_IO_OPERATION_MODE_READ, count, &context);
  ssize_t ret = libread(fd, buf, count);
  otf2_end_fd_operation(&context, ret);

  FUNCTION_EXIT;
  return ret;
}

ssize_t (*libwrite)(int fd, const void* buf, size_t count);
ssize_t write(int fd, const void* buf, size_t count) {
  FUNCTION_ENTRY;
  INTERCEPT_FUNCTION("write", libwrite);

  struct context context;
  otf2_begin_fd_operation(fd, OTF2_IO_OPERATION_MODE_WRITE, count, &context);
  ssize_t ret = libwrite(fd, buf, count);
  otf2_end_fd_operation(&context, ret);

  FUNCTION_EXIT;
  return ret;
}

/* pointers to actual posixio functions */
ssize_t (*libpread)(int fd, void* buf, size_t count, off_t offset);
ssize_t (*libpwrite)(int fd, const void* buf, size_t count, off_t offset);
ssize_t (*libreadv)(int fd, const struct iovec* iov, int iovcnt);
ssize_t (*libwritev)(int fd, const struct iovec* iov, int iovcnt);
size_t (*libfread)(void* ptr, size_t size, size_t nmemb, FILE* stream);
size_t (*libfwrite)(const void* ptr, size_t size, size_t nmemb, FILE* stream);
off_t (*liblseek)(int fd, off_t offset, int whence);

ssize_t pread(int fd, void* buf, size_t count, off_t offset) {
  FUNCTION_ENTRY;
  INTERCEPT_FUNCTION("pread", libpread);

  struct context context;
  otf2_fd_seek_operation(fd, offset, SEEK_SET, offset);
  otf2_begin_fd_operation(fd, OTF2_IO_OPERATION_MODE_READ, count, &context);
  ssize_t ret = libpread(fd, buf, count, offset);
  otf2_end_fd_operation(&context, ret);

  FUNCTION_EXIT;
  return ret;
}

ssize_t pwrite(int fd, const void* buf, size_t count, off_t offset) {
  FUNCTION_ENTRY;
  INTERCEPT_FUNCTION("pwrite", libpwrite);

  struct context context;
  otf2_fd_seek_operation(fd, offset, SEEK_SET, offset);
  otf2_begin_fd_operation(fd, OTF2_IO_OPERATION_MODE_WRITE, count, &context);
  ssize_t ret = libpwrite(fd, buf, count, offset);
  otf2_end_fd_operation(&context, ret);

  FUNCTION_EXIT;
  return ret;
}

ssize_t readv(int fd, const struct iovec* iov, int iovcnt) {
  FUNCTION_ENTRY;
  INTERCEPT_FUNCTION("readv", libreadv);

  size_t count =0;
  for(int i=0; i<iovcnt; i++){
    count += iov[i].iov_len;
  }

  struct context context;
  otf2_begin_fd_operation(fd, OTF2_IO_OPERATION_MODE_READ, count, &context);
  ssize_t ret = libreadv(fd, iov, iovcnt);
  otf2_end_fd_operation(&context, ret);

  FUNCTION_EXIT;
  return ret;
}

ssize_t writev(int fd, const struct iovec* iov, int iovcnt) {
  FUNCTION_ENTRY;
  INTERCEPT_FUNCTION("writev", libwritev);

  size_t count =0;
  for(int i=0; i<iovcnt; i++){
    count += iov[i].iov_len;
  }

  struct context context;
  otf2_begin_fd_operation(fd, OTF2_IO_OPERATION_MODE_WRITE, count, &context);
  ssize_t ret = libwritev(fd, iov, iovcnt);
  otf2_end_fd_operation(&context, ret);

  FUNCTION_EXIT;
  return ret;
}


size_t fread(void* ptr, size_t size, size_t nmemb, FILE* stream) {
  FUNCTION_ENTRY;
  INTERCEPT_FUNCTION("fread", libfread);
  
  struct context context;
  otf2_begin_stream_operation(stream, OTF2_IO_OPERATION_MODE_READ, size*nmemb, &context);
  size_t ret = libfread(ptr, size, nmemb, stream);
  otf2_end_fd_operation(&context, ret);

  FUNCTION_EXIT;
  return ret;
}

size_t fwrite(const void* ptr, size_t size, size_t nmemb, FILE* stream) {
  FUNCTION_ENTRY;
  INTERCEPT_FUNCTION("fwrite", libfwrite);

  struct context context;
  otf2_begin_stream_operation(stream, OTF2_IO_OPERATION_MODE_WRITE, size*nmemb, &context);
  size_t ret = libfwrite(ptr, size, nmemb, stream);
  otf2_end_fd_operation(&context, ret);

  FUNCTION_EXIT;
  return ret;
}

off_t lseek(int fd, off_t offset, int whence) {
  FUNCTION_ENTRY;
  INTERCEPT_FUNCTION("lseek", liblseek);

  off_t ret = liblseek(fd, offset, whence);
  otf2_fd_seek_operation(fd, offset, whence, ret);

  FUNCTION_EXIT;
  return ret;
}

int (*libfseek)(FILE *stream, long offset, int whence);
int fseek(FILE *stream, long offset, int whence) {
  FUNCTION_ENTRY;
  INTERCEPT_FUNCTION("fseek", libfseek);

  int ret = libfseek(stream, offset, whence);
  otf2_stream_seek_operation(stream, offset, whence, ftell(stream));

  FUNCTION_EXIT;
  return ret;
}


PPTRACE_START_INTERCEPT_FUNCTIONS(posixio)
INTERCEPT3("fopen", libfopen)
INTERCEPT3("fdopen", libfdopen)
INTERCEPT3("freopen", libfreopen)
INTERCEPT3("fclose", libfclose)
INTERCEPT3("dup", libdup)
INTERCEPT3("dup2", libdup2)
INTERCEPT3("dup3", libdup3)
INTERCEPT3("open", libopen)
INTERCEPT3("open64", libopen64)
INTERCEPT3("openat", libopenat)
INTERCEPT3("openat64", libopenat64)
INTERCEPT3("creat", libcreat)
INTERCEPT3("close", libclose)
INTERCEPT3("read", libread)
INTERCEPT3("write", libwrite)
INTERCEPT3("pread", libpread)
INTERCEPT3("pwrite", libpwrite)
INTERCEPT3("readv", libreadv)
INTERCEPT3("writev", libwritev)
INTERCEPT3("fread", libfread)
INTERCEPT3("fwrite", libfwrite)
INTERCEPT3("lseek", liblseek)
INTERCEPT3("fseek", libfseek)
PPTRACE_END_INTERCEPT_FUNCTIONS(posixio)
  
struct ezt_file_handle* new_file_fd(const char* filename, int fd);

void post_otf2_init_posixio(){
  init_otf2_posixio();

  _posixio_initialized = 1;
}

static void init_posixio() {
  INSTRUMENT_FUNCTIONS(posixio);


  if (eztrace_autostart_enabled())
    eztrace_start();

  enqueue_todo("init_posixio", post_otf2_init_posixio, "ezt_otf2", init_complete);
}

static void finalize_posixio() {
  _posixio_initialized = 0;


  eztrace_stop();
}

static void _posixio_init(void) __attribute__((constructor));
static void _posixio_init(void) {
  eztrace_log(dbg_lvl_debug, "eztrace_posixio constructor starts\n");
  EZT_REGISTER_MODULE(posixio, "Module for posix IO functions (fread, fwrite, read, write, etc.)",
		      init_posixio, finalize_posixio);
  eztrace_log(dbg_lvl_debug, "eztrace_posixio constructor ends\n");
}
