#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <stdarg.h>
#include <time.h>

/* Sugar macro for send message by send command */
#define SOCK_MSG(Str) Str, strlen (Str)

/* Factorise the error code return */
enum
  {
    exit_noerror	= 0,
    exit_nousage	= 1,
    exit_cantloadfile	= 2,
    exit_cantfork	= 3,
    exit_cantsocket	= 4,
    exit_cantreuse	= 5,
    exit_cantbind	= 6,
    exit_cantlisten	= 7
  } exit_stat;

/* A service */
typedef struct _service
{
  pid_t		pid;

  int		log;
  char		log_file[2048];

  char		exec[1024];
  int		sz_exec;

  char**	tab_exec;
  int		n_tab;
} s_service;

/* Use errno for the errors */
extern int errno;

/* Each rl maintening a service know globaly (for the signal catching) the pid of the maintened service */
pid_t service_pid;


/* Signal catcher to quit the service managed when the rl manager is exited */
void sig_handle (int sig);

/* Return the last item in path */
char* get_name (char* path);

/* Log an event */
void log_event (s_service* s, const char* info, ...);

/* Serveur to work with the managed services */
void run_serveur (s_service *services, int nservices);


/* Main function */
int main (int argc, char *argv[])
{
  s_service	service;
  s_service	*services = NULL;
  int		nservices = 0;
  FILE*		f = NULL;
  int		i;
  int		j = 0;

  /* Check if the arguments are okay */
  if (argc != 2 && argc != 3)
  {
    fprintf(stderr, "Usage : rl services [log_directory]\n");
    return exit_nousage;
  }

  /* Open the services file */
  f = fopen (argv[1], "r");
  if (!f)
  {
    fprintf (stderr, "Can't open the services file conf\n");
    return exit_cantloadfile;
  }

  /* Fork a new rl for each service in the services file */
  while (!feof (f))
  {
    /* Init the temporary s_service item */
    memset ((void *)(&service), 0, sizeof (s_service));

    /* Load a line in the service file */
    if (!fgets (service.exec, sizeof (service.exec), f))
      break;

    /* Manage commented lines */
    if (service.exec[0] == '#')
      continue;

    /* Cut the line in array, with ' ' separator. This is needed by exec* */
    service.sz_exec = strlen (service.exec);
    if (service.sz_exec > 0 && service.exec[service.sz_exec - 1] == '\n')
    {
      service.exec[service.sz_exec - 1] = '\0';
      service.sz_exec--;
    }
    for (i = 0; service.exec[i];)
    {
      while (service.exec[i] == ' ')
	i++;

      if (service.exec[i])
      {
	service.tab_exec = (char **) realloc (service.tab_exec, (service.n_tab + 1) * sizeof (char *));
	service.tab_exec[service.n_tab++] = service.exec + i;
      }

      while (service.exec[i] && service.exec[i] != ' ')
	i++;
      if (!service.exec[i])
	break;
      service.exec[i] = '\0';
      i++;
    }
    service.tab_exec = (char **) realloc (service.tab_exec, (service.n_tab + 1) * sizeof (char *));
    service.tab_exec[service.n_tab++] = NULL;

    /* Manage log file */
    if (argc == 3)
    {
      service.log = 1;
      sprintf (service.log_file, "%s/%s_%i.log", argv[2], get_name(service.tab_exec[0]), j++);
    }

    /* Fork a new rl */
    service.pid = fork ();

    /* If there is an error, just exit : we can't work if we can't fork */
    if (service.pid < 0)
      return exit_cantfork;

    /* I'm the parent, I'm just to fork on the next service */
    else if (service.pid > 0)
    {
      services = (s_service*) realloc (services, (nservices + 1) * sizeof (s_service));
      services[nservices++] = service;
      continue;
    }

    /* I'm the children, I install the rl system */
    else
    {
      /* Some Daemon Style configurations */
      setsid ();
      chdir("/");
      close(STDERR_FILENO);
      close(STDIN_FILENO);
      close(STDOUT_FILENO);


      /* Install sig-trap : if the service manager is quited (by the main rl manager or by an other way) */
      {
	struct sigaction act;

	act.sa_handler = sig_handle;
	sigfillset (&act.sa_mask);
	service_pid = -1;

	sigaction (SIGINT, &act, 0);
	sigaction (SIGQUIT, &act, 0);
	sigaction (SIGTERM, &act, 0);
      }

      /* Relaunch the service indefinively */
      while (1)
      {
	/* Make a fork to launch the service via exec* */
	service_pid = fork ();

	/* If we can't fork, this service is just dead */
	if (service_pid == -1)
	  return exit_cantfork;

	/* I'm the parent (the rl charged to manage the service) */
	if (service_pid)
	{
	  int		stat;

	  waitpid(service_pid, &stat, WUNTRACED);

	  if (WIFEXITED (stat))
	    log_event (&service, "Service %s exited with value %i", service.tab_exec[0], WEXITSTATUS (stat));

	  if (WIFSIGNALED (stat))
	    log_event (&service, "Service %s terminate with signal %i", service.tab_exec[0], WTERMSIG (stat));

	  if (WIFSTOPPED (stat))
	    log_event (&service, "Service %s stop with signal %i", service.tab_exec[0], WSTOPSIG (stat));

	  /* Here the service was exited. We wait a little bit and we relaunch it */
	  sleep (1);
	}
	/* I'm the child : I launch the service */
	else /* Child */
	{
	  if (execv (service.tab_exec[0], service.tab_exec) == -1)
	    log_event (&service, "Can't load the service [%s] %s", strerror (errno), service.tab_exec[0]);
	  return exit_noerror;
	}
      }
    }
  }

  /* Run the server to pilot the maintened services */
  run_serveur (services, nservices);

  /* Clean and exit */
  free (services);
  return exit_noerror;
}

/* Quit managed service */
void sig_handle (int sig)
{
  kill (service_pid, 15);
  exit (0);
  (void)sig;
}

/* Return the last item in path */
char* get_name (char* path)
{
  int	sz = strlen (path);
  int	i = sz - 1;

  for (i = sz - 1; i >= 0 && path[i] != '/'; i--)
    ;

  if (path[i] == '/')
    i++;

  return path + i;
}

/* Log an event */
void log_event (s_service* s, const char* info, ...)
{
  FILE*		f;
  va_list	ap;
  time_t	t;

  if (!s->log)
    return;

  if (!(f = fopen (s->log_file, "a")))
    return;

  va_start (ap, info);

  t = time (NULL);
  fprintf (f, "%s => ", ctime (&t));
  vfprintf (f, info, ap);
  fprintf (f, "\n\n");

  va_end (ap);

  fclose (f);
}


/* Serveur to manage the services */
void run_serveur (s_service *services, int nservices)
{
  struct sockaddr_in	my_addr;
  struct sockaddr_in	their_addr;
  unsigned int		sin_size;
  int			yes = 1;
  int			sz;
  int			sock;
  int			csock;
  char			buffer[1024];

  /*
    Configure the server port & adress
  */
  my_addr.sin_family = AF_INET;
  my_addr.sin_port = htons(64999);
  my_addr.sin_addr.s_addr = INADDR_ANY;
  memset(&(my_addr.sin_zero), '\0', 8);

  /*
    Instanciate an fd for the listener socket
  */
  if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    exit (exit_cantsocket);

  /*
    Reuse the port we want to use (force the system, if possible, to give use the asked port)
  */
  if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof (int)) == -1)
    exit (exit_cantreuse);

  /*
    Link the listener socket to the port previously configured
  */
  if (bind(sock, (struct sockaddr *)&my_addr, sizeof (struct sockaddr)) == -1)
    exit (exit_cantbind);
  if (listen(sock, 1) == -1) /* 1 => no more one people at the same time */
    exit (exit_cantlisten);

  while (1) /* Reaccept connexions */
  {
    /*
      Wait for a connection. If there is one, a new fd (csock) is given
      to the socket who manager the new connection.
    */
    sin_size = sizeof (struct sockaddr);
    if ((csock = accept(sock, (struct sockaddr *)&their_addr, &sin_size)) == -1)
    {
      sleep (1);
      continue;
    }

    /*
      In this point a new connection was done on our server, and the new connection
      socket is pointed by csock. We say welcome and give the commands list.
    */
    send(csock, SOCK_MSG("=WELC=Hello on the rl server=\n"), 0);
    send(csock, SOCK_MSG("=WELCH=[=KILL=]=Kill just the server=\n"), 0);
    send(csock, SOCK_MSG("=WELCH=[=AKILL=]=Kill the server and the managed services=\n"), 0);
    send(csock, SOCK_MSG("=WELCH=[=KILLPID=pid=]=Kill the managed service with the pid 'pid'=\n"), 0);
    send(csock, SOCK_MSG("=WELCH=[=COUNT=]=Send back the number of managed services=\n"), 0);
    send(csock, SOCK_MSG("=WELCH=[=SEXEC=i=]=Send back the service path of i-th managed service=\n"), 0);
    send(csock, SOCK_MSG("=WELCH=[=SPID=i=]=Send back the service pid of i-th managed service=\n"), 0);
    send(csock, SOCK_MSG("=WELCH=[=CLOSE=]=Close the connexion=\n"), 0);

    while (1) /* Communicate with the client */
    {
      /* Wait, and get, datas from the client */
      sz = recv (csock, buffer, sizeof (buffer) - 1, 0);
      buffer [sizeof (buffer) - 1] = '\0';
      if (sz <= 0) /* Client error or disconnected */
	break;

      /* Kill just the server */
      if (strncmp (buffer, SOCK_MSG("=KILL=")) == 0)
      {
	send (csock, SOCK_MSG("=OK=KILL=\n"), 0);
	_exit (0);
	continue;
      }

      /* Kill the server and the managed services */
      else if (strncmp (buffer, SOCK_MSG("=AKILL=")) == 0)
      {
	int i;

	send (csock, SOCK_MSG("=OK=AKILL=\n"), 0);

	for (i = 0; i < nservices; i++)
	  kill(services[i].pid, 15);

	exit (0);
	continue;
      }

      /* Kill a managed service with a given PID */
      else if (strncmp (buffer, SOCK_MSG("=KILLPID=")) == 0)
      {
	pid_t	apid = atoi (buffer + 9);
	int	i;
	int	ok = 0;

	for (i = 0; i < nservices && !ok; i++)
	{
	  if (services[i].pid == apid)
	  {
	    if (kill(apid, 15) == -1)
	      break;
	    else
	    {
	      send (csock, SOCK_MSG("=OK=KILLPID=\n"), 0);
	      ok = 1;
	    }
	  }
	}

	if (!ok)
	  send (csock, SOCK_MSG("=KO=KILLPID=\n"), 0);

	continue;
      }

      /* Send back the number of managed services */
      else if (strncmp (buffer, SOCK_MSG("=COUNT=")) == 0)
      {
	sprintf (buffer, "=OK=COUNT=%011i=\n", nservices);
	send (csock, SOCK_MSG(buffer), 0);
	continue;
      }

      /* Send back the exec command line for a given managed service index  */
      else if (strncmp (buffer, SOCK_MSG("=SEXEC=")) == 0)
      {
	int	ind = atoi (buffer + 7);

	if (ind < 0 || ind >= nservices)
	  send (csock, SOCK_MSG("=KO=SEXEC=\n"), 0);
	else
	{
	  sprintf (buffer, "=OK=%s=\n", services[ind].tab_exec[0]);
	  send (csock, SOCK_MSG(buffer), 0);
	}

	continue;
      }

      /* Send back the PID for a given managed service index  */
      else if (strncmp (buffer, SOCK_MSG("=SPID=")) == 0)
      {
	int	ind = atoi (buffer + 6);

	if (ind < 0 || ind >= nservices)
	  send (csock, SOCK_MSG("=KO=SEXEC=\n"), 0);
	else
	{
	  sprintf (buffer, "=OK=%011i=\n", services[ind].pid);
	  send (csock, SOCK_MSG(buffer), 0);
	}

	continue;
      }

      /* Close connexion */
      else if (strncmp (buffer, SOCK_MSG("=CLOSE=")) == 0)
      {
	sprintf (buffer, "=OK=CLOSE=Good Bye !=\n");
	send (csock, SOCK_MSG(buffer), 0);
	close(csock);
	break;
      }

      else
	send (csock, SOCK_MSG("=KO=UNKNOW COMMAND=\n"), 0);
    }
  }
}
