/*
 * back_end.c
 *
 * part of dipcd source code
 *
 * Copyright (C) Kamran Karimi
 */

#include "dipcd.h"
#include "funcs.h"

/* back_end forks the necessary processes and the sleep in the kernel, wait 
   for work to become available. It then forks an employer to handle it */

struct sockaddr_in MY_LBACK, REFEREE_LBACK;
struct sockaddr_in MY_ADDRESS, REFEREE_ADDRESS;
clock_t start_time;
int num_allow;
long *allow = NULL; /* holds the trusted IP addresses */

int verbose;
int secure;
int network_byte_order;
int debug_protocol;
int debug_conversion;
int global_timeout;
int employer_referee_timeout;
int employer_shm_timeout;
int employer_worker_timeout;
int referee_timeout;
int shm_ak_timeout;
double shm_hold_timeout;
int transfer_mode;
int dipc_page_size;
int inhibit_shm_signal;
int use_udp;

char dipcd_home[80];

/* read my address and the referee address, plus time-out values and put them 
   in globaly known variables */
static BOOLEAN init_dipc(void)
{
 char *pos, buff[100];
 FILE *file;
 struct in_addr addr;

 /* default values */
 transfer_mode = SEGMENT;
 dipc_page_size = PAGE_SIZE;
 inhibit_shm_signal = NO;
 employer_referee_timeout = EMPLOYER_REFEREE_TIMEOUT;
 employer_shm_timeout = EMPLOYER_SHM_TIMEOUT;
 employer_worker_timeout = EMPLOYER_WORKER_TIMEOUT;
 shm_ak_timeout = SHM_AK_TIMEOUT;
 referee_timeout = REFEREE_TIMEOUT;
 shm_hold_timeout = SHM_HOLD_TIMEOUT;
 /* the address of this computer and the referee's computer SHOULD be given */ 
 REFEREE_ADDRESS.sin_addr.s_addr = -1;
 MY_ADDRESS.sin_addr.s_addr = -1;
 
 addr.s_addr = -1;
 file = fopen(CONFIG_FILE,"r");
 if(file == NULL) 
 {
  log(LOC,"ERROR: Can't Open Config File %s  BECAUSE %s",
                                       CONFIG_FILE, strerror(errno));
  return FAIL;
 } 
 while(fgets(buff,80,file))
 {
  pos = strchr(buff, '#'); /* no comments */
  if(pos) *pos = 0;
  strip_space(buff);
  pos = strchr(buff, '=');
  if(pos == NULL) continue;
  *pos = 0;
  if(!strcasecmp(buff,"this_machine"))
  {
   pos++;
   strip_space(pos); 
   if(!strcasecmp(pos, "dipc_auto_find"))
   {
    struct utsname utsn;
    struct hostent *hostent;
 
    if(uname(&utsn) < 0)
     log(LOC,"ERROR: Can't find hostname");
    hostent = gethostbyname(utsn.nodename);
    if(hostent != NULL)
    {
     strcpy(buff, inet_ntoa(* (struct in_addr *)*hostent->h_addr_list));
     pos = buff;
    }
   }
   else check_symbolic(pos);
   addr.s_addr = inet_addr(pos);
   if((addr.s_addr != -1) && strcmp((char *)inet_ntoa(addr),"0.0.0.0"))
   { 
    bzero((char *)&MY_ADDRESS, sizeof(MY_ADDRESS));
    MY_ADDRESS.sin_family = AF_INET;
    MY_ADDRESS.sin_addr.s_addr = addr.s_addr;
    MY_ADDRESS.sin_port = htons(DIPC_FRONT_END_PORT);
   } 
  }
  else if(!strcasecmp(buff,"referee"))
  {
   pos++;
   strip_space(pos);
   check_symbolic(pos);
   addr.s_addr = inet_addr(pos);
   if((addr.s_addr != -1) && strcmp((char *)inet_ntoa(addr),"0.0.0.0"))
   { 
    bzero((char *)&REFEREE_ADDRESS, sizeof(REFEREE_ADDRESS));
    REFEREE_ADDRESS.sin_family = AF_INET;
    REFEREE_ADDRESS.sin_addr.s_addr = addr.s_addr;
    REFEREE_ADDRESS.sin_port = htons(DIPC_REFEREE_PORT);
   }
  }
  else if(!strcasecmp(buff,"transfer_mode"))
  { 
   pos++;
   if(!strcasecmp(pos, "page"))
    transfer_mode = PAGE;
   else if(strcasecmp(pos, "segment"))
    log(LOC,"ERROR: Unknown value for transfer_mode: %s",pos);    
  }
  else if(!strcasecmp(buff,"dipc_page_size"))
  { 
   pos++;
   dipc_page_size = atoi(pos);
   if(dipc_page_size <= 0 || 
     ((dipc_page_size / PAGE_SIZE) * PAGE_SIZE) != dipc_page_size)
    dipc_page_size = PAGE_SIZE;
  }
  else if(!strcasecmp(buff,"inhibit_shm_signal"))
  { 
   pos++;
   if(!strcasecmp(pos, "yes"))
    inhibit_shm_signal = YES;
   else if(strcasecmp(pos, "no"))
    log(LOC,"ERROR: Unknown value for inhibit_shm_signal: %s",pos);
  }
  else if(!strcasecmp(buff,"employer_referee_timeout"))
  { 
   pos++;
   employer_referee_timeout = atoi(pos);  
  }
  else if(!strcasecmp(buff,"employer_shm_timeout"))
  { 
   pos++;
   employer_shm_timeout = atoi(pos);  
  }
  else if(!strcasecmp(buff,"employer_worker_timeout"))
  { 
   pos++;
   employer_worker_timeout = atoi(pos); 
  }
  else if(!strcasecmp(buff,"shm_ak_timeout"))
  { 
   pos++;
   shm_ak_timeout = atoi(pos);
  } 
  else if(!strcasecmp(buff,"referee_timeout"))
  { 
   pos++;
   referee_timeout = atoi(pos);  
  }
  else if(!strcasecmp(buff,"shm_hold_timeout"))
  { 
   pos++;
   shm_hold_timeout = atof(pos);
   if(shm_hold_timeout <= 0)
    shm_hold_timeout = SHM_HOLD_TIMEOUT;
  }
  else
   log(LOC,"ERROR: Unknown option %s",buff);
 }
 if(REFEREE_ADDRESS.sin_addr.s_addr == -1)
 {
  log(LOC,"ERROR: Can't Find Referee's Address");
  return FAIL;
 } 
 if(MY_ADDRESS.sin_addr.s_addr == -1)
 {
  log(LOC,"ERROR: Can't Find My Own Address");
  return FAIL;
 }        
 bzero((char *)&REFEREE_LBACK, sizeof(REFEREE_LBACK));
 REFEREE_LBACK.sin_family = AF_INET;
 REFEREE_LBACK.sin_addr.s_addr = htonl(INADDR_ANY);
 REFEREE_LBACK.sin_port = htons(DIPC_REFEREE_PORT);

 bzero((char *)&MY_LBACK, sizeof(MY_LBACK));
 MY_LBACK.sin_family = AF_INET;
 MY_LBACK.sin_addr.s_addr = htonl(INADDR_ANY);
 MY_LBACK.sin_port = htons(DIPC_FRONT_END_PORT);

 return SUCCESS;
}

int get_dipcd_home(void)
{
 struct passwd *dipcdpw;

 dipcdpw = getpwnam("dipcd");
 if(dipcdpw == NULL)
 {
  log(LOC,"ERROR: User 'dipcd' not found");
  return FAIL;
 }
 strcpy(dipcd_home, dipcdpw->pw_dir);
 if(dipcd_home[strlen(dipcd_home) - 1] == '/')
  dipcd_home[strlen(dipcd_home) - 1] = 0;
 return SUCCESS;
}


static void sig_handler(int signo)
{
 log(LOC,"Quiting For Signal No. %d",signo); 
 if(dipc(0,DIPC_REMMNG,NULL) != DIPC_SUCCESS)
  log(LOC,"ERROR: DIPC_REMMNG failed");
 /* do last works */ 
 exit(5);
}


int test_sockets(void)
{
 char name[40];
 struct sockaddr_un ux_addr;
 struct sockaddr_in in_addr;
 int ux_len, in_len;
 int sockfd;
 
 /* test UNIX sockets */
 sprintf(name,"%s/DIPC_%s.%d.%d", dipcd_home, "test", 1, getpid());
 bzero((char *)&ux_addr,sizeof(ux_addr));
 ux_addr.sun_family = AF_UNIX;
 strcpy(ux_addr.sun_path,name);
 ux_len = strlen(ux_addr.sun_path) + sizeof(ux_addr.sun_family);
 sockfd = plug(AF_UNIX, (struct sockaddr *) &ux_addr, ux_len);
 if(sockfd < 0)
 {
  log(LOC,"ERROR: Can't Create Test UNIX Socket (%s)", name);
  return DIPC_FAIL;
 }
 close(sockfd);
 unlink(name);
 
 /* test TCP/IP sockets */
 bzero((char *)&in_addr,sizeof(in_addr));
 in_addr.sin_family = AF_INET;
 in_addr.sin_addr.s_addr = htonl(INADDR_ANY);
 in_addr.sin_port = htons(DIPC_FRONT_END_PORT);
 in_len = sizeof(in_addr);

 sockfd = plug(AF_INET, (struct sockaddr *) &in_addr, in_len);
 if(sockfd < 0)
 {
  log(LOC,"ERROR: Can't Create TCP/IP Sockets: %s", 
         "dipcd is already running, or TCP/IP support is not present");
  return DIPC_FAIL;
 }
 close(sockfd);
 return DIPC_SUCCESS;
}


void main(int argc, char **argv)
{
 int count;
 struct dipc_info di;
 char data[MAXDATA];
 char date_time[40];
 time_t timet;
 void *pt[2];
 int param[3];
 pid_t child_pid;
 struct sigaction sact;
 int found_work;

 start_time = times(NULL);
 timet = time(NULL);
 strcpy(date_time, ctime(&timet));
 date_time[strlen(date_time) - 1] = 0;
 if(getuid() != 0)
 {
  log(LOC,"ERROR: dipcd can not run without root privileges");
  exit(20);
 } 
 if(fork() > 0)
  exit(0); 
 chdir("/");
 if(get_dipcd_home() == FAIL)
  exit(20);
 sprintf(data,"/bin/rm %s/* 2>/dev/null", dipcd_home);
 system(data); /* clean */
 if(test_sockets() == DIPC_FAIL)
  exit(20);
 log(LOC,"%s Version %s Started on %s", argv[0], DIPC_VERSION, date_time);
 verbose = 0;
 secure = 0;
 network_byte_order = 0;
 debug_protocol = 0;
 debug_conversion = 0;
 global_timeout = 1;
 use_udp = 0;
 for(count = 1; count < argc; count++)
 {
  if(argv[count][0] == '-')
  {
   int count2;
   
   for(count2 = 1; count2 < strlen(argv[count]); count2++)
   {
    if(argv[count][count2] == 'v')
     verbose = 1;
    else if(argv[count][count2] == 's')
     secure = 1;
    else if(argv[count][count2] == 'n')
     network_byte_order = 1; 
    else if(argv[count][count2] == 't')
     global_timeout = 0;
    else if(argv[count][count2] == 'd')
     debug_protocol = 1;
    else if(argv[count][count2] == 'c')
     debug_conversion = 1;
    else if(argv[count][count2] == 'u')
     use_udp = 1;
    else
     log(LOC,"Unknown argument: %s",&argv[count][count2]);
   }
  }
 }
 sact.sa_handler = sig_handler;
 sigemptyset(&sact.sa_mask);
 sact.sa_flags = 0;
  
 if(sigaction(SIGINT, &sact, NULL) < 0)
  log(LOC,"ERROR: Can't Catch Signal BECAUSE %s",strerror(errno));
 if(sigaction(SIGTERM, &sact, NULL) < 0)
  log(LOC,"ERROR: Can't Catch Signal  BECAUSE %s",strerror(errno));
 if(sigaction(SIGSEGV, &sact, NULL) < 0)
  log(LOC,"ERROR: Can't Catch Signal BECAUSE %s",strerror(errno));
 sact.sa_handler = child_handler;
 if(sigaction(SIGCHLD, &sact, NULL) < 0)
  log(LOC,"ERROR: Can't Catch Signal  BECAUSE %s",strerror(errno));
 
 if(init_dipc() == FAIL) exit(20);

 if(verbose)
 {
  log(LOC,"Using %s for Distributed Shared memory",
                   (transfer_mode == SEGMENT) ? "Segmentation" : "Paging");
  if(transfer_mode == PAGE)
   log(LOC, "Using %d Byte Distributed Pages", dipc_page_size);
  log(LOC,"Signals are %sused for Shared memory read/writes",
                                (inhibit_shm_signal == YES) ? "NOT " : "");
  log(LOC,"employer_worker Timeout is %d seconds",employer_worker_timeout);
  log(LOC,"employer_shm Timeout is %d seconds",employer_shm_timeout);
  log(LOC,"employer_referee Timeout is %d seconds",employer_referee_timeout);
  log(LOC,"shm_ak Timeout is %d seconds",shm_ak_timeout);  
  log(LOC,"referee Timeout is %d seconds",referee_timeout);
  log(LOC,"shm_hold Timeout is %f seconds",shm_hold_timeout);

  if(network_byte_order)
   log(LOC,"Using network byte order conversion for data transfer");

  if(use_udp)
   log(LOC,"Using UDP/IP");
  else
   log(LOC,"Using TCP/IP");

  if(secure)
   log(LOC, "Running in the Secure mode");
 }
 
 if(secure || SAME_MACHINE(&MY_ADDRESS,REFEREE_ADDRESS))
 {
  if(read_dipc_allow() == FAIL)
   exit(20);
 }
 /* register this task as the official dipcd task, responsible for handling 
    DIPC requests */
 param[0] = transfer_mode;
 param[1] = inhibit_shm_signal;
 param[2] = dipc_page_size;
 if(dipc(MY_ADDRESS.sin_addr.s_addr, DIPC_SETMNG, param) < DIPC_SUCCESS)
 {
  log(LOC,"ERROR: DIPC_SETMNG failed");
  exit(20);
 }
 pt[0] = &di;
 pt[1] = data;
 /* start the front_end task */
 if((child_pid = fork()) < 0)
 {
  log(LOC,"ERROR: Can't fork()  BECAUSE %s",strerror(errno));
  dipc(0,DIPC_REMMNG,NULL);
  exit(20);
 }
 else if(child_pid == 0)
 {
  front_end();
  exit(20); /* never reaches here */
 } 
 /* see if this is the referee machine */
 if(SAME_MACHINE(&MY_ADDRESS,REFEREE_ADDRESS))
 {
  if((child_pid = fork()) < 0)
  {
   log(LOC,"ERROR: Can't fork()  BECAUSE %s",strerror(errno));
   dipc(0,DIPC_REMMNG,NULL);
   exit(20);
  }
  else if(child_pid == 0)
  {
   referee();
   exit(20);
  }
 } 

 while(1) 
 { 
  if(dipc(0, DIPC_SLEEP, 0) != DIPC_SUCCESS)
   log(LOC,"ERROR: DIPC_SLEEP failed");
  found_work = dipc(0,DIPC_GETWORK, pt);
  if(found_work < DIPC_SUCCESS)
   log(LOC,"ERROR: DIPC_GETWORK failed");
  else if(found_work == DIPC_SUCCESS)
  {
   if(verbose || debug_protocol)
    log(LOC,"Found work: %s",work_string(di.func));
   /* only non-local requests will be returned, so create a handler 
      (employer) for them */
   if((child_pid = fork()) < 0)
   {
    log(LOC,"ERROR: Can't fork()  BECAUSE %s",strerror(errno));
    di.arg1 = -DIPC_E_FORK;
    if(dipc(di.pid, DIPC_RETWORK, (int *)pt) < DIPC_SUCCESS)
     log(LOC,"ERROR: DIPC_RETWORK failed");
   } 
   else if(child_pid == 0)
   {
    employer(&di, data);
    exit(20);
   }
  }
 } /* while(1) */
}
