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

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

/* employer is forked whenever some request has to be satisfied remotely.
   It connects to the remote machine (sometimes this 'remote' machine can 
   be the local computer, like when a process on this computer wants to 
   refere to the manager of a distributed shared memory that is running in
   the same machine), informs it of the request and then waits for a reply.
   It uses Internet sockets to send the request, but relies on UNIX domain 
   sockets for the reply (through the front_end) The results are delivered 
   to the original process that caused the creation of the employer */ 

extern struct sockaddr recent_read;
extern struct sockaddr_in MY_ADDRESS, REFEREE_ADDRESS;

extern int verbose;
extern int network_byte_order;
extern int debug_protocol;
extern int debug_conversion;
extern int global_timeout;
extern int employer_worker_timeout;
extern int employer_shm_timeout;
extern int employer_referee_timeout;
extern int use_udp;

extern char dipcd_home[80];

static int orig_pid = 0;

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

static int ux_sockfd;
static char ux_name[80];
static struct dipc_info *global_di;

/* return the results to the kernel */
static void return_result(struct dipc_info *di, char *data)
{
 void *pt[2];

 pt[0] = di;
 pt[1] = data;
 if(orig_pid != 0)
  di->pid = orig_pid; 
 if(dipc(di->pid, DIPC_RETWORK, (int *)pt) < DIPC_SUCCESS)
  log(LOC,"ERROR: DIPC_RETWORK failed");
}
 
static void sig_handler(int signo)
{
 if(verbose)
  log(LOC,"Quiting For Signal No. %d",signo); 
 /* do last works */
 global_di->arg1 = -EINTR;
 return_result(global_di, NULL);
 exit(5);
}

static void exit_handler(int Code, void *arg)
{
 unlink(ux_name);
 if(verbose)
  log(LOC,"Exited With Code %d",Code); 
}
  
void employer(struct dipc_info *di, char *data)
{
 fd_set rSelFD; /* for select */
 struct timeval TimeVal;
 struct message message;
 struct sockaddr_in owner_addr, my_worker, *recent_read_p;
 struct sockaddr_un ux_addr, ux_client_addr;
 int ux_len, ux_client_len;
 int in_sock, ux_newsockfd;
 struct sigaction sact;
 int want_trans, act_trans;
 struct passwd *pw;
 struct usr_info uinfo;
 int sel_res, TimeOut;
 
 struct in_addr info_owner_addr;
 
 if(verbose)
  log(LOC,"Started with Function %s type %s", work_string(di->func), 
                        (di->type < 3) ? ipc_type[di->type] : "N/A");
 global_di = di; /* for interrupt handler */
 sact.sa_handler = sig_handler;
 sigemptyset(&sact.sa_mask);
 sact.sa_flags = 0;
 ux_name[0] = 0;
 
 if(on_exit(exit_handler, NULL) < 0)
  log(LOC,"ERROR: Can't install exit handler BECAUSE %s",strerror(errno)); 
 if(sigaction(SIGINT, &sact, NULL) < 0)
  log(LOC,"WARNING: Can't Catch Signal  BECAUSE  %s",strerror(errno));
 if(sigaction(SIGTERM, &sact, NULL) < 0)
  log(LOC,"WARNING: 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));

 /* first set up a UNIX domain socket to receive the results */
 if(di->type < 3)
  sprintf(ux_name,"%s/DIPC_%s.%d.%d",
             dipcd_home, ipc_type[di->type], di->arg1, getpid());
 else
 {
  di->arg1 = -DIPC_E_TYPE;
  log(LOC,"ERROR: Unknown Type");
  return_result(di, NULL);
  exit(20);
 } 
 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); 
 unlink(ux_name); /* just in case... */                             
 ux_sockfd = plug(AF_UNIX, (struct sockaddr *) &ux_addr, ux_len);
 if(ux_sockfd < 0)
 {
  di->arg1 = -errno; 
  log(LOC,"ERROR: Can't Create Socket %s",ux_name);
  return_result(di, NULL);
  exit(20);
 } 
 /* prepare the dipcd message to be sent */      
 orig_pid = di->pid;
 message.info = *di;
 switch(message.info.func)
 {
  case WANTRD:
  case WANTWR:
   message.request = REQ_SHMMAN;
   TimeOut = employer_shm_timeout;
   break;
   
  case RMID:
  case DELKEY:
  case SEARCH:
  case COMMIT:
   message.request = REQ_REFEREE;
   TimeOut = employer_referee_timeout;
   break;
   
  case SEMCTL:
  case SEMOP:
  case MSGCTL:
  case MSGSND:
  case MSGRCV:
  case SHMCTL:
  case TSTGET:
  case SEM_EXIT: 
   message.request = REQ_DOWORK;
   TimeOut = employer_worker_timeout;
   break;
  
  default:
   di->arg1 = -errno; 
   log(LOC,"ERROR: Unknown funcion");
   return_result(di, NULL);
   exit(20); 
 }

 if(network_byte_order)
  message.arch = LINUX_NBOR;
 else
  message.arch = MY_DIPC_ARCH;

 message.address = MY_ADDRESS;
 message.pid = getpid();
 
 if (debug_protocol) {
   log(LOC,"Employer: message PID Field: %d", message.pid);
   log(LOC,"message info.func %d info.key %d", 
		message.info.func, message.info.key);
   info_owner_addr.s_addr = htonl(message.info.owner);
   log(LOC,"message info.owner: %s %d", inet_ntoa( info_owner_addr),
		message.info.owner);
   log(LOC,"message info.args: %d %d %d %d", message.info.arg1, 
  	  message.info.arg2, message.info.arg3, message.info.arg4);
 }
  
 bzero((char *) &owner_addr, sizeof(struct sockaddr_in));
 owner_addr.sin_family = AF_INET;
 if(message.request == REQ_REFEREE) {
  message.info.owner = REFEREE_ADDRESS.sin_addr.s_addr;
  owner_addr.sin_addr.s_addr = message.info.owner;
  if (debug_protocol)
    log(LOC,"Request is Referee; addr %s", inet_ntoa(REFEREE_ADDRESS.sin_addr));
 } else {
  if (debug_protocol)
    log(LOC,"Regular Request, Message Owner Field Is: %d", message.info.owner);
  owner_addr.sin_addr.s_addr = htonl(message.info.owner);
 }

 if(message.request == REQ_REFEREE)
  owner_addr.sin_port = htons(DIPC_REFEREE_PORT);
 else 
  owner_addr.sin_port = htons(DIPC_FRONT_END_PORT);
  
 if(verbose || debug_protocol)
  log(LOC,"Destination Address Is: %s %ld ", inet_ntoa(owner_addr.sin_addr), 
		(long) owner_addr.sin_addr.s_addr);
 /* get user name */
 if(message.info.uid != 0)
 {
  pw = getpwuid(message.info.uid);
  if(pw)
   strcpy(uinfo.name, pw->pw_name);
 } 
 if((message.info.uid == 0) || (pw == NULL)) /* no super user! */
  strcpy(uinfo.name, "dipcd");
 
 in_sock = ring(AF_INET, (struct sockaddr *)&owner_addr, sizeof(owner_addr),0);
 if(in_sock < 0) 
 {
  message.info.arg1 = -errno;
  log(LOC,"ERROR: ring() Failed  BECAUSE  %s",strerror(errno));
  return_result(&(message.info), NULL);
  exit(20);
 }
 want_trans = sizeof(message);

 if(network_byte_order)
 {
  /* MS: convert & send message!! */
  act_trans = send_net_message(in_sock, &message, &owner_addr);
 }
 else
  act_trans = writen(in_sock, (char *)&message, want_trans, &owner_addr);

 if(act_trans < want_trans)
 {
  message.info.arg1 = -errno;
  log(LOC,"ERROR: Sent %d From %d Bytes  BECAUSE  %s",
          act_trans, want_trans,strerror(errno));
  return_result(&(message.info), NULL);
  exit(20);
 } 
 if(message.request == REQ_DOWORK)
 {
  if(use_udp)
  { /* get the new remote port number, and use it */
   short temp;
   
   connect(in_sock, NULL, 0);
   want_trans = sizeof(short);   
   act_trans = readn(in_sock, (char *)&temp, want_trans);
   if(act_trans < want_trans)
   {
    message.info.arg1 = -errno;
    log(LOC,"ERROR: Sent %d From %d Bytes  BECAUSE  %s",
          act_trans, want_trans,strerror(errno));
    return_result(&(message.info), NULL);
    exit(20);
   }
   recent_read_p = (struct sockaddr_in *)&recent_read;
   if(verbose)
    log(LOC,"UDP Destination Address Is: %s, port: %d ", 
              inet_ntoa(recent_read_p->sin_addr), recent_read_p->sin_port); 
   close(in_sock);
   in_sock = ring(AF_INET,&recent_read,sizeof(recent_read), 0);
   if(in_sock < 0) 
   {
    message.info.arg1 = -errno;
    log(LOC,"ERROR: ring() Failed  BECAUSE  %s",strerror(errno));
    return_result(&(message.info), NULL);
    exit(20);
   }
   my_worker = *recent_read_p;
  }
  else
   my_worker = owner_addr;
  /* send the user name */
  want_trans = sizeof(struct usr_info);

  /* MS: user info is char *, no conversion */
  act_trans = writen(in_sock, (char *)&uinfo, want_trans, &my_worker);
  if(act_trans < want_trans)
  {
   message.info.arg1 = -errno;
   log(LOC,"ERROR: Sent %d From %d Bytes  BECAUSE  %s",
           act_trans, want_trans,strerror(errno));
   return_result(&(message.info), NULL);
   exit(20);
  }
 }
 /* send data for msgsnd() and  msgctl() with IPC_SET */ 
 if((message.info.func == MSGSND) ||
    ((message.info.func == MSGCTL) && (message.info.arg2 == IPC_SET)) ||
    ((message.info.func == SHMCTL) && (message.info.arg2 == IPC_SET)) ||
    (message.info.func == SEMOP) || 
    ((message.info.func == SEMCTL) && (message.info.arg3 == IPC_SET)) ||
    ((message.info.func == SEMCTL) && (message.info.arg3 == SETALL)))
 {
  want_trans = message.info.dsize;   
  if(network_byte_order)
  {
   /* MS: convert & send message-associated data */
   act_trans = send_net_data(in_sock, data, &message, &my_worker);
  }
  else
   act_trans = writen(in_sock, data, want_trans, &my_worker);

  if(act_trans < want_trans)
  {
   message.info.arg1 = -errno;
   log(LOC,"ERROR: Sent %d From %d Bytes  BECAUSE %s",
           act_trans, want_trans, strerror(errno));
   return_result(&(message.info), NULL);
   exit(20);
  }
 }  
 close(in_sock); 
 if(verbose)
  log(LOC,"Request Sent");
 /* SEM_EXIT does not require a reply */
 if(message.info.func == SEM_EXIT)
 {
  message.info.arg1 = 0;
  return_result(&(message.info), NULL);
  exit(0);
 }
 TimeVal.tv_sec = TimeOut;
 TimeVal.tv_usec = 0;
 FD_ZERO(&rSelFD);
 FD_SET(ux_sockfd,&rSelFD);
  
 if(TimeOut <= 0 || global_timeout == 0)
  sel_res = select(FD_SETSIZE, &rSelFD, NULL, NULL, NULL);
 else
  sel_res = select(FD_SETSIZE, &rSelFD, NULL, NULL, &TimeVal); 
 if( sel_res < 0)
 {
  message.info.arg1 = -errno;
  log(LOC,"ERROR: select() Failed  BECAUSE %s",strerror(errno));
  return_result(&(message.info), NULL);
  exit(20);
 }  
 if(FD_ISSET(ux_sockfd,&rSelFD))
 {
  ux_client_len = sizeof(ux_client_addr);
  if((ux_newsockfd = accept(ux_sockfd, (struct sockaddr *)&ux_client_addr,
                                                       &ux_client_len)) < 0)
  {
   message.info.arg1 = -errno;
   log(LOC,"ERROR: accept() Failed  BECAUSE %s",strerror(errno));
   return_result(&(message.info), NULL);
   exit(20);
  }
  /* Now message will be over written */
  want_trans = sizeof(message);
  act_trans = readn(ux_newsockfd, (char *)&message, want_trans);
  if(act_trans < want_trans)
  {
   message.info.arg1 = -errno;
   log(LOC,"ERROR: Received %d From %d Bytes BECAUSE %s",
           act_trans, want_trans,strerror(errno));
   return_result(&(message.info), NULL);
   exit(20);
  } 

  if (debug_protocol) {
    log(LOC,"Employer received on unix domain socket:");
    log(LOC,"message PID Field: %d", message.pid);
    log(LOC,"message info.func  %d info.key %d", 
		 message.info.func, message.info.key);
    /* no htonl() seems required here; should we check the info.owner semantics? */
    info_owner_addr.s_addr = message.info.owner;
    log(LOC,"message info.owner: %s %d", 
            inet_ntoa(info_owner_addr), message.info.owner);
    log(LOC,"message info.args: %d %d %d %d", message.info.arg1, 
	    message.info.arg2, message.info.arg3, message.info.arg4);
  }

  /* receive data for msgrcv() and  msgctl() with IPC_STAT */ 
  if((message.info.func == MSGRCV) ||
     ((message.info.func == MSGCTL) && (message.info.arg2 == IPC_STAT)) ||
     ((message.info.func == SHMCTL) && (message.info.arg2 == IPC_STAT)) ||
     
     ((message.info.func == SEMCTL) && (message.info.arg3 == IPC_STAT)) ||
     ((message.info.func == SEMCTL) && (message.info.arg3 == GETALL)))
  {
   want_trans = message.info.dsize;   
   act_trans = readn(ux_newsockfd, data, want_trans);
   if(act_trans < want_trans)
   {
    message.info.arg1 = -errno;
    log(LOC,"ERROR: Received %d From %d Bytes  BECAUSE %s",
            act_trans, want_trans,strerror(errno));
    return_result(&(message.info), NULL);
    exit(20);
   } 
  }   
  close(ux_newsockfd);
  close(ux_sockfd);
  if(verbose)
   log(LOC,"Reply Received. Result is %d",message.info.arg1);
  if(message.info.func == COMMIT && (message.info.arg3 & IPC_OWN) && 
       message.info.type == SHM)
  {
   shm_man(&(message.info), data);
   exit(0);
  }   
  return_result(&(message.info), data);
 }
 else
 { /* time out */
  log(LOC,"ERROR: No Reply");
  message.info.arg1 = -DIPC_E_TIMEOUT;
  return_result(&(message.info), data);
  exit(20);
 }   
 exit(0);
}
