/* -*- 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-lib/eztrace.h>
#include <eztrace-lib/eztrace_module.h>
#include <sys/types.h>
#include <signal.h>
#include <sys/wait.h>

#include "iotracer_to_otf2.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 _iotracer_initialized = 0;

/* PID of the iotracer process */
static pid_t iotracer_pid = 0;

#define CURRENT_MODULE iotracer
DECLARE_CURRENT_MODULE;


#define DATA_SIZE 512


static char * getTracedTaskName(){
  /* trace the current process */

  char * exec_path = malloc(DATA_SIZE);
  memset(exec_path, 0, DATA_SIZE);
  ssize_t s = readlink("/proc/self/exe", exec_path, DATA_SIZE);
  if(s < 0)
    return NULL;

  char* traced_task = basename(exec_path);
  strncpy(exec_path, traced_task, DATA_SIZE);

  return exec_path;
}



static char * getTracedFileName() {
  char * traced_file = getenv("IOTRACER_TRACED_FILE"); 

  if(traced_file == NULL){
    eztrace_error( "IOtracer: please set the IOTRACER_TRACED_FILE environment variable!\nIf you are using sudo, please the -E option to get environment variables!\n");
  }

  FILE *f = fopen(traced_file,"r");
  if(f == NULL){
    eztrace_error( "IOtracer: IOTRACER_TRACED_FILE: %s doesn't exist!\n", traced_file);
  }

  fclose(f);
  return traced_file;
}

static char * getTraceFilter() {
  char * traced_filter = getenv("IOTRACER_TRACED_FILTER"); 

  if(traced_filter == NULL){
    eztrace_error( "IOtracer: please set the IOTRACER_TRACED_FILTER environment variable : f/file or d/dir!\nIf you are using sudo, please the -E option to get environment variables!\n");
  }

  return traced_filter;
}

static char * getTracedInode(){
  char * traced_inode = getenv("IOTRACER_TRACED_INODE");

  if(traced_inode == NULL){
    eztrace_error( "IOtracer: please set the IOTRACER_TRACED_INODE environment variable!\nIf you are using sudo, please the -E option to get environment variables!\n");
  }

  /*
   * To do: check the existence of the related file/directory
   */

  
  return traced_inode;
}

void run_iotracer() {

  char * traced_task  = getTracedTaskName();
  char * trace_filter = getTraceFilter();
  char * trace_filter_option = malloc(strlen(trace_filter)+3);
  strcpy(trace_filter_option, "--");
  strcat(trace_filter_option, trace_filter);
  char * traced_inode = getTracedInode();

  printf("traced_task:%s\n", traced_task);
  printf("trace_filter:%s\n", trace_filter_option);
  printf("traced_inode:%s\n", traced_inode);

  /* create a child process to run iotracer */
  if((iotracer_pid=fork()) == 0) {
    /* the child process */

#define STR_SIZE 1024

    int iotracer_trace_file = -1;
    char trace_path[STR_SIZE];
    char* res = getenv("EZTRACE_TRACE_DIR");

    if (!res) {
      strncpy(trace_path, IOTRACER_TRACE_PATH, STR_SIZE);
    } else {
      strncpy(trace_path, res, STR_SIZE);
      strncat(trace_path, "/", STR_SIZE-1);
      strncat(trace_path, IOTRACER_TRACE_PATH, STR_SIZE-1);
    }

    close(STDERR_FILENO);    

    // make sure both 'sudo' and 'python' processes belong to the same
    // process group so that we can kill them
    setpgid(getpid(), getpid());
    execl("/usr/bin/sudo", "sudo", "python", IOTRACER_PATH, "-o", trace_path, "-t", traced_task, 
	  trace_filter_option, "-i", traced_inode,  "-l", "vpfb", NULL);

    eztrace_error("IOtracer: could not start IOTracer: %s\n", strerror(errno));
    exit(EXIT_FAILURE);
  }

  /* crappy synchronization: we need to make sure that iotracer started before continuing */
  sleep(1);
}

static void iotracer_start(){

  /* we're about to spawn a process. remove LD_PRELOAD from the environment so that the spawn
   * process is not instrumented with eztrace
   */
  unset_ld_preload();

  run_iotracer();
  eztrace_log(dbg_lvl_normal, "Starting IOtracer (PID: %d)\n", iotracer_pid);

  reset_ld_preload();
}


static void iotracer_stop() {
  unset_ld_preload();

  // stop the iotracer process
  eztrace_log(dbg_lvl_normal,"[IOTracer] killing iotracer process %d\n",iotracer_pid);

  assert(iotracer_pid > 0);

  if(killpg(iotracer_pid, SIGKILL) < 0) {
    eztrace_error("IOtracer: could not kill iotracer process (PID: %d).\n", iotracer_pid);
  }

  /* wait until iotracer stops */
  int status;
  pid_t pid = waitpid(iotracer_pid, &status, 0);
  if( pid != iotracer_pid) {
    eztrace_error("IOTrace: failed to wait for the IOTracer process\n");
  }

  EZTRACE_PROTECT {
    EZTRACE_PROTECT_ON();                                                                    
    convert_iotracer_log_to_otf2();
    EZTRACE_PROTECT_OFF();                                                                   
  }
  
  reset_ld_preload();

  eztrace_log(dbg_lvl_normal, "IOtracer: stop\n");
}

PPTRACE_START_INTERCEPT_FUNCTIONS(iotracer)
PPTRACE_END_INTERCEPT_FUNCTIONS(iotracer);

static void init_iotracer() {
  INSTRUMENT_FUNCTIONS(iotracer);

  if (eztrace_autostart_enabled())
    eztrace_start();

  /*
   * get activate or not the utilisation of iotracer by checking an env variable
   */
  iotracer_start();

  _iotracer_initialized = 1;
}

static void finalize_iotracer() {
  _iotracer_initialized = 0;

  iotracer_stop();

  eztrace_stop();
}

static void _iotracer_init(void) __attribute__((constructor));
static void _iotracer_init(void) {
  eztrace_log(dbg_lvl_debug, "eztrace_iotracer constructor starts\n");
  EZT_REGISTER_MODULE(iotracer, "Module for tracing kernel I/O events",
		      init_iotracer, finalize_iotracer);
  eztrace_log(dbg_lvl_debug, "eztrace_ends constructor starts\n");
}
