#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stdarg.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <sys/ptrace.h>
#include <sys/user.h>
#include <asm/unistd_32.h>
#include <sys/types.h>
#include <sys/syscall.h>

#include "estructures.h"
#include "utils.h"



#include <sys/reg.h>


#ifdef __x86_64__
  #define SC_NUMBER  (8 * ORIG_RAX)
  #define SC_RETCODE (8 * RAX)
#else
  #define SC_NUMBER  (4 * ORIG_EAX)
  #define SC_RETCODE (4 * EAX)
#endif


//Macro per obtenir el numero de limit (resource) a partir del nom
#define REGISTER(x) {RLIMIT_ ## x, #x, 0, {0,0}}

//Arrays de syscalls i de limits
static struct one_call syscalls[400];
static struct one_limit limits[] = {
  REGISTER(AS),
  REGISTER(CORE),
  REGISTER(CPU),
  REGISTER(DATA),
  REGISTER(FSIZE),
  REGISTER(LOCKS),
  REGISTER(MEMLOCK),
  REGISTER(NOFILE),
  REGISTER(NPROC),
  REGISTER(RSS),
  REGISTER(STACK),
  {-1, "", 0, {0,0}}
};

//Numero de crides a la taula de syscalls
int syscalltable_size=0;

int permitir_llamadas = 1;

void init(){
  //Fitxers obligatoris
  if (!(file_exists(LIMIT_CONFIG_FILE) && file_exists(SYSCALL_CONFIG_FILE) && file_exists(SYSCALL_TABLE_CONFIG_FILE))){
    perror("Error: Missing configuraton file");
    exit(EXIT_FAILURE);
  }

  //Si no existeix, genera la taula de crides a sistema
  if(!file_exists(SYSCALL_TABLE_CONFIG_FILE)){
    generate_syscall_table();
  }

  //Carrega el contingut dels fitxers de configuracio al arrays en memoria
  load_limit_config(limits);
  load_syscall_table(syscalls, &syscalltable_size);
  load_syscall_limits(syscalls, &syscalltable_size);
}

void run_hostile(char *argv[]){
  printf("Run hostile\n");
  execve(argv[0], argv, NULL);
}

static void trace_hostile(pid_t pid)
{
  int status;
  long sc_number, sc_retcode;

  // Wait until child gets stopped
  wait(&status);
  ptrace(PTRACE_SYSCALL, pid, NULL, NULL);

  //2 waits per no contabilitzar en els limits la crida original a execve
  //  wait(&status);
  //  ptrace(PTRACE_SYSCALL, pid, NULL, NULL);
  //  wait(&status);
  //  ptrace(PTRACE_SYSCALL, pid, NULL, NULL);

	//printf("Voy a empezar a tracear al hijo con pid %d\n", pid);
	
  while (1)
  {
    wait(&status);

    if (WIFEXITED(status)) {
      exit(EXIT_SUCCESS);
    }
    if (WIFSIGNALED(status)) {
      perror("Child killed by signal");
      exit(EXIT_FAILURE);
    }
    if (!WIFSTOPPED(status)) {
      perror("Unknown error");
      exit(EXIT_FAILURE);
    }
    if (WSTOPSIG(status) == SIGTRAP) {
      //Obtenim els registres
      struct user_regs_struct r;
      if (ptrace(PTRACE_GETREGS, pid, 0, &r) != 0){
	       perror("Error: ptrace_getregs failed");
	       exit(EXIT_FAILURE);
      }

      if (permitir_llamadas == 0){
        int syscall = 0;
        //Comprobem que el identificador de la syscall sigui correcte
        #ifdef __x86_64__
          syscall = r.orig_rax;
        #else
          syscall = r.orig_eax;
        #endif

        if(syscall < 0 ){
        	perror("Error:process reached resource limit");
  	      exit(EXIT_FAILURE);
        }
      
        // PONER PARA C, QUITAR PARA JAVA Y PYTHON
        int i;
        //Busquem en el array de syscalls la que coincideixi amb el valor que hi havia a eax
        for(i=0; i < 400; i++){
        	if(syscalls[i].syscall == syscall){
        	  // Cal tenir en compte que el proces fill es para tant a la entrada
        	  // de la crida com a la sortida. Es per això que es fa servir la 
        	  // variable in_call.
        	  if(syscalls[i].in_call == 0){
        	    syscalls[i].in_call = 1;
              //printf("Realizando llamada con id %d nombre %s  numero veces %d con limite %d \n", i, syscalls[i].name, syscalls[i].numcalls, syscalls[i].maxcalls);
        	    if(syscalls[i].maxcalls != -1){
        	      syscalls[i].numcalls += 1;              
        	      if(syscalls[i].numcalls > syscalls[i].maxcalls){
              		ptrace(PTRACE_KILL, pid, 0, 0);
              		perror("Syscall limit reached");
              		exit(EXIT_FAILURE);
        	      }
  	          }
        	  }else{
        	    syscalls[i].in_call = 0;
        	  }
        	  break;
        	}
        }
      }
      
    } else {
      //printf("Child stopped due to signal %d \n", WSTOPSIG(status));
      perror("Child stopped\n");
      exit(EXIT_FAILURE);
    }
    fflush(stdout);
    if (ptrace(PTRACE_SYSCALL, pid, 0, 0) != 0){
      perror("Error: ptrace_syscall failed");
      exit(EXIT_FAILURE);
    }
  }
}


static void start_trace(char *argv[])
{
	//printf("Estoy iniciando el traceador\n");
  pid_t pid = fork();
	//printf("He creado un hijo\n");
  switch (pid) {
    case -1:
      perror("Error: fork failed when trying to run hostile program");
      exit(EXIT_FAILURE);
    case 0: //Proces fill, executara el programa hostil
		 // printf("Soy el proceso hijo");
      set_resource_limits(limits);

      ptrace(PTRACE_TRACEME, 0, 0, 0);
      kill(getpid(), SIGSTOP); //Aturem el proces per a que al pare li dongui temps de cridar ptrace abans del execve
      execve(argv[0], argv, NULL);
      run_hostile(argv);
      //Mai ha d'arribar aqui
      perror("Error: could start hostile program");
      exit(EXIT_FAILURE);
    default: //Proces pare, fara el trace
    
		  //printf("Soy el proceso padre (nurse), y voy a empezar a tracear al hijo %s\n", argv[0]);
      trace_hostile(pid);
      break;
  }
}

int main(int argc, char *argv[]) {
  if (argc < 2) {
    printf("Usage: %s <permit_syscall(0|1)> <executable> <arguments>\n", argv[0]);
    exit(1);
  }

  permitir_llamadas = atoi(argv[1]);
  //printf("permitir_llamadas %d\n", permitir_llamadas);
  init();
  start_trace(&argv[2]);

  return 1;
}

