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

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

/* front_end is the door keeper of dipcd. All incoming messages over the 
   network pass through it. The incoming messages can either be results 
   for requests made from dipcd processes in this computer, or they can 
   be requests from processes on other machines for work in this computer.
   In the first case, the front_end passes the messages to its proper
   process. in the second case a new worker processes is forked. The new 
   worker inherits the front_end's socket file descriptor, and so can 
   exchange information directly with the remote process */

extern struct sockaddr recent_read; /* for UDP */

extern int verbose;
extern int secure;
extern int debug_protocol;
extern int debug_conversion;
extern int use_udp;
 
extern char dipcd_home[80];

static int in_sockfd = 0, in_newsockfd = -1;

static char ipc_type[3][4] =
{
 "SHM",
 "MSG",
 "SEM"
};

/* the newly arrived dipcd message is for a process that is waiting for it.
   Use a UNIX domain socket to deliver it. */
static BOOLEAN pass_message(int in_sock, struct message *message)
{
 int want_trans, act_trans;
 char temp[40];
 char ux_name[80];
 int ux_sockfd, ux_len;
 struct sockaddr_un ux_addr; 
 char data[MAXDATA];
 struct in_addr addr;
 
 if(message->info.type < 3)
  sprintf(temp,"%s/DIPC_%s",dipcd_home, ipc_type[message->info.type]);
 else
 { 
  log(LOC,"ERROR: Unknown Type");
  return FAIL;
 }
 /* other processes don't know the shared memory manager's pid */
 if(message->request == REQ_SHMMAN)  
  sprintf(ux_name,"%s/DIPC_%s.%d",dipcd_home, ipc_type[message->info.type],
                                                           message->info.key);
 else if(message->request == REQ_DOWORK && 
        (message->info.func == SEMOP || message->info.func == SEM_EXIT))
 {
  addr = message->address.sin_addr;
  sprintf(ux_name,"%s/PROXY_%s_%d", dipcd_home, inet_ntoa(addr), 
                                                      message->info.pid);
 } 
 else
  sprintf(ux_name,"%s/DIPC_%s.%d.%d",dipcd_home, ipc_type[message->info.type],
                                              message->info.key, message->pid);
 if(verbose)
  log(LOC,"Data came for socket %s",ux_name);
 if (debug_protocol) 
  log(LOC,"Result Came for socket %s, key %d, pid %d",
           ux_name, message->info.key, message->pid);
 bzero((char *) &ux_addr,sizeof(ux_addr));
 ux_addr.sun_family = AF_UNIX;
 strcpy(ux_addr.sun_path,ux_name);
 ux_len = strlen(ux_addr.sun_path) + sizeof(ux_addr.sun_family);                                
 ux_sockfd = ring(AF_UNIX, (struct sockaddr *) &ux_addr, ux_len, 1);
 if(ux_sockfd < 0)
 {
  if(message->request == REQ_DOWORK)
  {
   if(verbose)
    log(LOC, "Proxy process not found");
  }
  else
   log(LOC,"ERROR: Can't Connect To Waiting Task BECAUSE %s", strerror(errno));
  return FAIL;
 } 
 want_trans = sizeof(*message);
 act_trans = writen(ux_sockfd,(char *)message, want_trans, NULL);
 if(act_trans < want_trans)
 {
  log(LOC,"ERROR: Sent %d From %d Bytes  BECAUSE %s",
          act_trans,want_trans,strerror(errno));
  return FAIL;
 }  
 /* see if there is any data other than the message */ 
 if(message->request == REQ_DOWORK && message->info.func == SEMOP)
 {
  want_trans = sizeof(struct usr_info);
  act_trans = readn(in_sock, data, want_trans);
  if(act_trans < want_trans)
  {
   log(LOC,"ERROR: Received %d From %d Bytes  BECAUSE %s",
           act_trans, want_trans,strerror(errno));
   return FAIL;
  }     
  want_trans = sizeof(struct usr_info);
  act_trans = writen(ux_sockfd, data, want_trans, NULL);
  if(act_trans < want_trans)
  {
   log(LOC,"ERROR: Sent %d From %d Bytes  BECAUSE %s",
           act_trans, want_trans,strerror(errno));
   return FAIL;
  }
 }

 if((message->request == REQ_DOWORK && message->info.func == SEMOP) ||
    (message->info.func == MSGRCV) ||
    ((message->info.func == MSGCTL) && (message->info.arg2 == IPC_STAT)) ||
    ((message->info.func == SEMCTL) && (message->info.arg3 == IPC_STAT)) ||
    ((message->info.func == SEMCTL) && (message->info.arg3 == GETALL)) ||
    ((message->info.func == SHMCTL) && (message->info.arg2 == IPC_STAT)))
 {
  want_trans = message->info.dsize;
  act_trans = get_in_data(in_sock, data, message);
  if(act_trans < want_trans)
  {
   log(LOC,"ERROR: Received %d From %d Bytes  BECAUSE %s",
           act_trans, want_trans,strerror(errno));
   return FAIL;
  }     
  want_trans = message->info.dsize;
  act_trans = writen(ux_sockfd, data, message->info.dsize, NULL);
  if(act_trans < want_trans)
  {
   log(LOC,"ERROR: Sent %d From %d Bytes  BECAUSE %s",
           act_trans, want_trans,strerror(errno));
   return FAIL;
  }
 }
 close(ux_sockfd);
 return SUCCESS;
}

static void sig_handler(int signo)
{
 log(LOC,"Quiting For Signal No. %d",signo);
 /* do last works */ 
 exit(5);
}

void front_end(void)
{
 int new_udp_sock = -1;
 fd_set rSelFD;
 struct timeval TimeVal;
 int want_trans, act_trans;
 int in_client_len, in_len;
 struct sockaddr_in in_addr, in_client_addr;
 struct message message;
 struct sigaction sact;
 int child_pid;
 
 log(LOC,"front_end Started");
 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));
  
 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);

 in_sockfd = plug(AF_INET, (struct sockaddr *) &in_addr, in_len);
 if(in_sockfd < 0)
 {
  log(LOC,"ERROR: Can't Create Socket");
  exit(20);
 }
 /* check interrupts from now on */
 while(1) 
 {
  if(new_udp_sock >= 0 && new_udp_sock != in_newsockfd)
  {
   log(LOC, "Closing new_udp_sock %d, in_new_sockfd: %d", new_udp_sock,
       in_newsockfd); 
   close(new_udp_sock);
   new_udp_sock = -1;
  }
  TimeVal.tv_sec = 0;
  TimeVal.tv_usec = 500000; /* this time out is unused for now */
  FD_ZERO(&rSelFD);
  FD_SET(in_sockfd,&rSelFD);
  while(1)
  {
   if(select(FD_SETSIZE,&rSelFD,NULL,NULL,&TimeVal) == -1)
   {
    if(errno != EINTR)
    {
     log(LOC,"ERROR: select() Failed  BECAUSE %s",strerror(errno));
     close(in_sockfd);
     exit(20);
    }
   }
   else break;
  }

  if(FD_ISSET(in_sockfd,&rSelFD))
  {
   if(use_udp)
    in_newsockfd = in_sockfd;
   else
   {
    in_client_len = sizeof(in_client_addr);
    while(1)
    {
     if((in_newsockfd = accept(in_sockfd, (struct sockaddr *)&in_client_addr,
                                                       &in_client_len)) < 0)
     {
      if(errno != EINTR)
      {
       log(LOC,"ERROR: accept() Failed  BECAUSE %s",strerror(errno));
       close(in_sockfd);
       exit(20);
      } 
     }
     else break;
    }
   }
   want_trans = sizeof(message);
   act_trans = get_in_message(in_newsockfd,&message);
   if(act_trans < want_trans)
   {
    log(LOC,"ERROR: Received %d From %d Bytes  BECAUSE %s",
            act_trans, want_trans,strerror(errno));
    exit(20);
   }
   if(verbose)
    log(LOC,"Remote Call From %s",inet_ntoa(message.address.sin_addr)); 
   if(secure)
   { /* check to see if the computer is allowed to access the front_end */
    if(check_allow(message.address.sin_addr.s_addr) == FAIL)
    {
     log(LOC,"Warning: Address %s is not Allowed",inet_ntoa(message.address.sin_addr)); 
     continue; /* just ignore it */
    } 
   }
   
   if (debug_protocol) 
    log(LOC,"Front_End: Remote Call From %s",
                             inet_ntoa(message.address.sin_addr));
   switch(message.request)
   {
    case RES_REFEREE:
    case RES_DOWORK:
    case RES_SHMMAN:
    case REQ_SHMMAN: /* for shm_man */
     if (debug_protocol) 
      log(LOC,"passing: %s sent func %d args %d %d %d %d owner %d",
                  inet_ntoa(message.address.sin_addr), message.info.func,
                  message.info.arg1, message.info.arg2, message.info.arg3,
                  message.info.arg4, message.info.owner );
     pass_message(in_newsockfd, &message);
     break;
    
    case REQ_DOWORK:
     if(use_udp)
     {
      short temp; /* just  something to send */
      
      /* get a new port number and send it to the remote */
      new_udp_sock = ring(AF_INET, (struct sockaddr *)&(message.address), 
                                               sizeof(message.address), 0);
      if(new_udp_sock < 0)
      {
       log(LOC, "Can't create a UDP socket for the worker\n");
       break;
      }
      want_trans = sizeof(short);
      act_trans = writen(new_udp_sock, (char *)&temp, want_trans, 
                                        (struct sockaddr_in *)&(recent_read));
      if(act_trans < want_trans)
      {
       log(LOC,"ERROR: Sent %d From %d Bytes  BECAUSE %s",
                   act_trans, want_trans,strerror(errno));
       break; 
      }
     }
     else
      new_udp_sock = in_newsockfd;
     /* test to see if a proxy exists */
     if(message.info.func == SEMOP || message.info.func == SEM_EXIT)
     { 
      if (debug_protocol)
       log(LOC,"checking: %s sent func %d args %d %d %d %d owner %d",
                   inet_ntoa(message.address.sin_addr), message.info.func,
                   message.info.arg1, message.info.arg2, message.info.arg3,
                   message.info.arg4, message.info.owner );
      /* the remote employer will use new_udp_sock to send info */
      if(pass_message(new_udp_sock, &message) != FAIL)
       break;
     }  
     
    default: 
     if (debug_protocol)
      log(LOC,"forking: %s sent func %d args %d %d %d %d owner %d",
                  inet_ntoa(message.address.sin_addr), message.info.func,
                  message.info.arg1, message.info.arg2, message.info.arg3,
                  message.info.arg4, message.info.owner );

     if((child_pid = fork()) < 0)
      log(LOC,"ERROR: fork() failed  BECAUSE %s",strerror(errno));
     else if(child_pid == 0)
     {
      /* give the worker the file descriptor */
      worker(new_udp_sock, message);
      exit(20);
     }
     break; 
   }
   if(!use_udp)
    close(in_newsockfd); 
  }
  /* else ;  time out */
 }
 exit(0);
}
