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

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

/* this is forked by the front_end. it receives a file descriptor, which
   it uses to exchange information with the remote task that caused its
   creation. Worker executes the function it is supposed to do and sends 
   the replies back to the computer that requested it. The front_end on
   that remote computer will deliver the results to the waiting process */

extern struct sockaddr recent_read;

extern char dipcd_home[80];

extern struct sockaddr_in MY_ADDRESS, REFEREE_ADDRESS;
extern int verbose;
extern int dipc_page_size;
extern int network_byte_order;
extern int debug_protocol;
extern int debug_conversion;
extern int use_udp;

static int become_proxy = 0;
static char ux_name[80];

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


static void exit_handler(int Code, void *argv)
{
 unlink(ux_name);
 if(verbose)
  log(LOC,"Exited With Code %d",Code);
}


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


/* handle a remove request from the referee. Do it as root. There is
   no acknowledgement here */
static void req_rmid(struct message *message)
{
 int id;

 if(message->info.type == MSG)
 {
  id = msgget(message->info.key, IPC_DIPC);
  if(id != -1)
  {
   if(msgctl(id, IPC_RMID, NULL) < 0)
    log(LOC,"ERROR: Can't remove message queue %d  BECAUSE %s",
            message->info.key,strerror(errno));
   else if(verbose)
    log(LOC,"Removed message queue %d",message->info.key);
  }  
  else
   log(LOC,"ERROR: Can't find message queue %d",message->info.key);  
 }   
 else if(message->info.type == SHM)
 {
  id = shmget(message->info.key, 0, IPC_DIPC);
  if(id != -1)
  {
   if(shmctl(id, IPC_RMID, NULL) < 0)
    log(LOC,"ERROR: Can't remove shared memory segment %d  BECAUSE %s",
            message->info.key,strerror(errno));
   else if(verbose)
    log(LOC,"Removed shared memory segment %d",message->info.key);
  }  
  else
   log(LOC,"ERROR: Can't find shared memory segment %d",
                                                message->info.key);
 }
 else if(message->info.type == SEM)
 {
  id = semget(message->info.key, 0, IPC_DIPC);
  if(id != -1)
  {
   if(semctl(id, 0, IPC_RMID, NULL) < 0)
    log(LOC,"ERROR: Can't remove semaphore set %d  BECAUSE %s",
            message->info.key,strerror(errno));
   else if(verbose)
    log(LOC,"Removed semaphore set %d",message->info.key); 
  }  
  else
   log(LOC,"ERROR: Can't find semaphore set %d",message->info.key);
 }
}

/* execute an IPC remote procedure call */
static void do_RPC(int in_sock, struct message *message)
{
 char data[MAXDATA];
 int count, retval, id;
 int arg2, arg3;
 int rep_sock;
 struct usr_info uinfo;
 int want_trans, act_trans;
 union semun arg;
 int type = message->info.type; 
 struct in_addr info_owner_addr;
 struct sockaddr_in rep_addr = message->address;

 want_trans = sizeof(struct usr_info);
 act_trans = readn(in_sock, (char *)&uinfo, want_trans);
 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 User Is: %s",uinfo.name);

 if( (message->info.func == SEMOP) ||
    ((message->info.func == SEMCTL) && (message->info.arg3 == IPC_SET)) ||
    ((message->info.func == SEMCTL) && (message->info.arg3 == SETALL)) ||
    ((message->info.func == MSGSND) ||
    ((message->info.func == MSGCTL) && (message->info.arg2 == IPC_SET)) ||
    ((message->info.func == SHMCTL) && (message->info.arg2 == IPC_SET))))
 {
  /* these functions have some data */
  want_trans = message->info.dsize;   
  if (become_proxy) 
   act_trans = readn(in_sock, data, want_trans);
  else
   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));
   exit(20);
  }
 } 
 if(verbose)
  log(LOC,"Required information for RPC received");
 /* no further need for in_sock */
 if(message->info.func == TSTGET)
 {
  arg2 = message->info.arg2;
  arg3 = message->info.arg3;
 }
 else
 {
  arg2 = 0;
  arg3 = IPC_DIPC;
 }
 /* continue as (become) the remote user */
 if(become(uinfo.name) == FAIL)
 {
  errno = EPERM;
  retval = -1; 
 }
 else
 {
  /* this is will be tested When we use xxxget() */  
  if(message->info.func == TSTGET)
  { 
   if(verbose)
    log(LOC,"TSTGET");
  }
  if(type == SEM) 
   id = semget(message->info.arg1, arg2, arg3); 
  else if(type == MSG)  
   id = msgget(message->info.arg1, arg3);
  else 
   id = shmget(message->info.arg1, arg2, arg3);

  if(id < 0)
  {
   if(verbose)
    log(LOC,"Could not access IPC structure");
   retval = id;
  }
  else switch(message->info.func)
  {
   case TSTGET:
    retval = id;
    break;
    
   case SEMCTL:
    if(verbose)
     log(LOC,"SEMCTL");
    switch(message->info.arg3)
    {
     case SETVAL:
      arg.val = message->info.arg4;
      break;
     
     case IPC_SET:
     case IPC_STAT:
      arg.buf = (struct semid_ds *) data;
      break;
      
     case SETALL:
     case GETALL:
      arg.array = (ushort *) data;
      break;
    }  
    retval = semctl(id, message->info.arg2, message->info.arg3, arg); 
    break;
   
   case SEMOP:
    if(verbose)
     log(LOC,"SEMOP");
    for(count = 0; count < message->info.arg2; count++)
    {
     if((((struct sembuf *)data)[count]).sem_flg & SEM_UNDO)
      become_proxy = 1;
    }
    retval = semop(id, (struct sembuf *)data, message->info.arg2);  
    break;
    
   case MSGCTL:
    if(verbose)
     log(LOC,"MSGCTL");
    retval = msgctl(id, message->info.arg2, (struct msqid_ds *) data); 
    break;
   
   case MSGSND:
    if(verbose)
     log(LOC,"MSGSND");
    retval = msgsnd(id, (struct msgbuf *) data, message->info.arg2, 
                                                  message->info.arg3);  
    break;
   
   case MSGRCV:
    if(verbose)
     log(LOC,"MSGRCV");
    retval = msgrcv(id, (struct msgbuf *)data, message->info.arg2, 
                             message->info.arg3, message->info.arg4);
    break; 
  
   case SHMCTL:
    if(verbose)
     log(LOC,"SHMCT");
    retval = shmctl(id, message->info.arg2, (struct shmid_ds *) data); 
    break;
  
   default:
    log(LOC,"ERROR: Unknown Function %d", message->info.func);
    errno = EINVAL;
    retval = -1; 
    break;
  }
  if(become("root") == FAIL)
   log(LOC,"ERROR: Can't Become Root");
 }

 if(retval < 0) message->info.arg1 = -errno;
 else message->info.arg1 = retval;
 if(verbose)
  log(LOC,"Returning results. Return value is %d (%s)",
     message->info.arg1, (retval < 0) ? strerror(errno) : "No Error");
 rep_sock = ring(AF_INET, (struct sockaddr *)&(message->address),
                                              sizeof(message->address), 0);
 if(rep_sock < 0)
 {
  log(LOC,"ERROR: Can't connect back  BECAUSE %s",strerror(errno));
  exit(20);
 }
 message->request = RES_DOWORK;
 message->address = MY_ADDRESS;
   
 if (debug_protocol) {
   log(LOC,"Worker: 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", inet_ntoa( info_owner_addr)); 
   log(LOC,"message info.args: %d %d %d %d", message->info.arg1, 
  	  message->info.arg2, message->info.arg3, message->info.arg4);
 }
 want_trans = sizeof(*message);
 if(network_byte_order)
 {
  /* MS: convert & send message!! */
  message->arch = LINUX_NBOR;
  act_trans = send_net_message(rep_sock, message, &rep_addr);
 } 
 else
 {
  message->arch = MY_DIPC_ARCH;
  act_trans = writen(rep_sock, (char *)message, want_trans, &rep_addr);
 }
 
 if(act_trans < want_trans)
 {
  log(LOC,"ERROR: Sent %d From %d Bytes (message) BECAUSE %s",
          act_trans, want_trans,strerror(errno));
  exit(20);
 }
 if(verbose)
  log(LOC,"Reply Message sent");
 if(((message->info.func == SEMCTL) && (message->info.arg3 == IPC_STAT)) ||
    ((message->info.func == SEMCTL) && (message->info.arg3 == GETALL))   ||
    ((message->info.func == MSGRCV) ||
    ((message->info.func == MSGCTL) && (message->info.arg2 == IPC_STAT)) ||
    ((message->info.func == SHMCTL) && (message->info.arg2 == IPC_STAT))))
 {
  /* these calls require some results to be sent back */
  want_trans = message->info.dsize;   
  if(network_byte_order)
  {
   /* MS: convert & send message-associated data */
   act_trans = send_net_data(rep_sock, data, message, &rep_addr);
  }
  else
   act_trans = writen(rep_sock, data, want_trans, &rep_addr);

  if(act_trans < want_trans)
  {
   log(LOC,"ERROR: Sent %d From %d Bytes (data) BECAUSE %s",
           act_trans, want_trans,strerror(errno));
   exit(20);
  }
 } 
 close(rep_sock);
 if(verbose)
  log(LOC,"RPC done");
}

/* transfer the shared memory contents to another computer */
static BOOLEAN shm_send_contents(struct message *message)
{
 char *attach;
 int in_sock;
 int id, offset;
 int want_trans, act_trans;
 struct message response;
 struct sockaddr_in my_worker, *recent_read_p;

 id = shmget(message->info.key, 0, IPC_DIPC);
 if(id < 0)
 {
  log(LOC,"ERROR: shmget() failed for key %d  BECAUSE %s",
          message->info.key,strerror(errno));
  return FAIL;
 }
 if(verbose)
  log(LOC,"shmget() was successful. Size = %d",message->info.arg1); 

 attach = (char *)shmat(id,0,0);
 if(attach == (char *) -1)
 { 
  log(LOC,"ERROR: shmat() Failed  BECAUSE %s",strerror(errno));
  return FAIL;
 }
 response = *message;
 if(network_byte_order)
  response.arch = LINUX_NBOR;
 else
  response.arch = MY_DIPC_ARCH;

 response.address = MY_ADDRESS;
 response.address2 = message->address; /* owner */
 response.info.func = RCV;
 /* sender determines the size */
 if(verbose || debug_protocol)
  log(LOC,"Sending contents to %s. segnum %d, mode %s",
                 inet_ntoa(message->address2.sin_addr), 
                 message->info.arg2 / dipc_page_size, 
                 (message->info.arg3 == SEGMENT) ? "Segment" : "Page");
 in_sock = ring(AF_INET, (struct sockaddr *)&(message->address2),
                                      sizeof(message->address2), 0);
 if(in_sock < 0)
 {
  log(LOC,"ERROR: connect() Failed  BECAUSE %s",strerror(errno));
  shmdt(attach);
  return FAIL;
 }
 want_trans = sizeof(response);
 if(network_byte_order)
 {
  /* convert & send message part */
  act_trans = send_net_message(in_sock, &response, &(message->address2));
 }
 else
  act_trans = writen(in_sock, (char *) &response, want_trans, 
                                             &(message->address2));
 if(act_trans < want_trans)
 {
  log(LOC,"ERROR: Sent %d From %d Bytes (message) BECAUSE %s",
          act_trans, want_trans,strerror(errno));
  shmdt(attach); 
  return FAIL;
 }

 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)
  {
   log(LOC,"ERROR: Sent %d From %d Bytes  BECAUSE  %s",
         act_trans, want_trans,strerror(errno));
   shmdt(attach); 
   return FAIL;
  }
  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) 
  {
   log(LOC,"ERROR: ring() Failed  BECAUSE  %s",strerror(errno));
   shmdt(attach);
   return FAIL;
  }
  my_worker = *recent_read_p;
 }
 else
  my_worker = message->address2;

 if(message->info.arg3 == SEGMENT)
 {
  want_trans = message->info.arg1;
  offset = 0;
 }
 else
 {
  want_trans = dipc_page_size;
  offset = message->info.arg2;
  offset = (offset / dipc_page_size) * dipc_page_size;
  if(want_trans + offset > message->info.arg1)
   want_trans = message->info.arg1 - offset; 
 } 
 act_trans = writen(in_sock, &attach[offset], want_trans, &my_worker);  

 if(act_trans < want_trans)
 {
  log(LOC,"ERROR: Sent %d From %d Bytes (data) BECAUSE %s",
          act_trans, want_trans,strerror(errno));
  shmdt(attach);        
  return FAIL;
 }
 if(verbose)
  log(LOC,"Sent %d bytes at segnum %d", want_trans, offset / dipc_page_size);
 shmdt(attach);
 return SUCCESS;
}


/* receive the shared memory contents. After the shared memory contents
   are successfully received, sends an acknowledge message to the shared
   memory manager */
static BOOLEAN shm_receive_contents(int in_sock, struct message *message)
{
 char *attach;
 int id, offset;
 int want_trans, act_trans;
 struct message response;

 id = shmget(message->info.key, 0, IPC_DIPC);
 if(id < 0)
 {
  log(LOC,"ERROR: shmget() Failed  BECASUE %s",strerror(errno));
  return FAIL;
 }
 if(verbose)
  log(LOC,"shmget() was successful. Size = %d",message->info.arg1); 
 attach = (char *)shmat(id,0,0);
 if(attach == (char *) -1)
 { 
  log(LOC,"ERROR: shmat() Failed  BECAUSE %s",strerror(errno));
  return FAIL;
 }
 if(message->info.arg3 == 0)
 {
  want_trans = message->info.arg1;
  offset = 0;
 }
 else
 {
  want_trans = dipc_page_size;
  offset = message->info.arg2;
  offset = (offset / dipc_page_size) * dipc_page_size;
  if(want_trans + offset > message->info.arg1)
   want_trans = message->info.arg1 - offset; 
 }
 if (debug_protocol)
   log(LOC,"Receiving contents from %s. segnum %d, mode %s",
                 inet_ntoa(message->address.sin_addr), 
                 message->info.arg2 / dipc_page_size, 
                 (message->info.arg3 == SEGMENT) ? "Segment" : "Page");
 act_trans = readn(in_sock, &attach[offset], want_trans);
 if(act_trans < want_trans)
 {
  log(LOC,"ERROR: Received %d From %d Bytes (data) BECAUSE %s",
          act_trans, want_trans,strerror(errno));
  shmdt(attach);
  return FAIL;
 }
 if(verbose)
  log(LOC,"Received %d bytes at segnum %d", 
                           want_trans, offset / dipc_page_size);
 shmdt(attach);
 
 response = *message; 
 if(network_byte_order)
 {
  if (response.arch != LINUX_NBOR)
   log(LOC,"ERROR: Wrong arch on send AK to %s ??",
                            inet_ntoa(message->address2.sin_addr));

 }
 response.address = MY_ADDRESS;
 response.request = REQ_SHMMAN;
 response.info.func = AK;
 if(send_in_message(&response, message->address2) == FAIL)
 {
  log(LOC,"ERROR: Can't send AK to %s",
                            inet_ntoa(message->address2.sin_addr));
  return FAIL;
 }
 if(verbose)
  log(LOC,"sent AK to %s", inet_ntoa(message->address2.sin_addr));

 return SUCCESS;                               
}


/* read protect a shared memory */
static void read_protect(key_t key, int offset, int mode)
{
 int id, arg[3];
 
 id = shmget(key, 0, IPC_DIPC);
 if(id < 0)
 {
  log(LOC,"ERROR: shmget() Failed  BECASUE %s",strerror(errno));
  exit(20);
 }
 shmctl(id, SHM_UNLOCK, NULL);
 arg[0] = DISABLE_RD;
 arg[1] = offset;
 arg[2] = mode;
 if(dipc(key,DIPC_REMPROT, arg) < DIPC_SUCCESS)
  log(LOC,"DIPC_REMPROT failed");
}


static void shm_dowork(int in_sock, struct message *message)
{
 int arg[3];
 
 if(verbose)
  log(LOC,"shm_dowork");
 switch(message->info.func)
 {
  /* first write protect, then send shm contents */  
  case WRPROT_SND:
   if(verbose)
    log(LOC,"WRPROT_SND");
   arg[0] = DISABLE_WR;
   arg[1] = message->info.arg2;
   arg[2] = message->info.arg3;
   if(dipc(message->info.key, DIPC_REMPROT, arg) < DIPC_SUCCESS)
    log(LOC,"ERROR: DIPC_REMPROT failed");
   shm_send_contents(message);  
   break;  
  
  /* just read-protect */
  case RDPROT:
   if(verbose)
    log(LOC,"RDPROT");
   read_protect(message->info.key, message->info.arg2, message->info.arg3); 
   break;
  
  /* first read protect, then send the contents */
  case RDPROT_SND:
   if(verbose)
    log(LOC,"RDPROT_SND");
   read_protect(message->info.key, message->info.arg2, message->info.arg3);
   shm_send_contents(message);  
   break; 
  
  case NOP_SND: /* just send it */
   if(verbose)
    log(LOC,"NOP_SND");
   shm_send_contents(message);  
   break;
   
  case RCV: /* receive it as a result of direct request */
   if(verbose)
    log(LOC,"RCV");
   shm_receive_contents(in_sock, message);  
   break; 
   
  default:
   log(LOC,"ERROR: Unknown Function %d", message->info.func);
   break; 
 }  
}

/* this routine handles requests comming from other machines */
void worker(int new_sockfd, struct message message)
{
 int ux_client_len, ux_len;
 struct sockaddr_un ux_client_addr, ux_addr;
 struct sigaction sact;
 struct in_addr addr;
 int ux_sockfd;
 int want_trans, act_trans;
  fd_set rSelFD;

 if(verbose)
  log(LOC,"Started: Request %s, funcion: %s", 
     request_string(message.request), work_string(message.info.func));
 sact.sa_handler = sig_handler;
 sigemptyset(&sact.sa_mask);
 sact.sa_flags = 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,"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));

 ux_name[0] = 0;
 if(message.info.func == SEMOP)
 {
  /* should set the proxy UNIX socket now. It may not be necessary if
     the SEM_UNDO flag is not used, but we don't know it yet. */
  addr = message.address.sin_addr;
  sprintf(ux_name,"%s/PROXY_%s_%d", dipcd_home, inet_ntoa(addr), 
                                                       message.info.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); 

  unlink(ux_name); /* just in case... */
  ux_sockfd = plug(AF_UNIX, (struct sockaddr *)&ux_addr, ux_len);
  if(ux_sockfd < 0)
  { 
   log(LOC,"ERROR: Can't Create Socket %s",ux_name);
   exit(20);
  }
 }
 do
 {
  switch(message.info.func)
  {
   case RMID:
    if(verbose) 
     log(LOC,"RMID  type %s",ipc_type[message.info.type]);
    req_rmid(&message);
    break;                 
  
   case SEMCTL:
   case SEMOP:
   case MSGCTL:
   case MSGSND:
   case MSGRCV:
   case SHMCTL:
   case TSTGET:
    do_RPC(new_sockfd, &message);
    break;
  
   case SEM_EXIT:
    exit(0); 
   
   default:
    if(verbose)
     log(LOC,"DOWORK  type %s", ipc_type[message.info.type]);
    if(message.info.type == SHM)
     shm_dowork(new_sockfd, &message); 
    else
    {
     log(LOC,"ERROR: Unknown Type");
     exit(20);
    }
    break;
  }
  close(new_sockfd);
  if(!become_proxy)
   break;
  
  FD_ZERO(&rSelFD);
  FD_SET(ux_sockfd,&rSelFD);
  if(select(FD_SETSIZE,&rSelFD,NULL,NULL,NULL) == -1)
  {
   log(LOC,"ERROR: select() Failed  BECAUSE %s",strerror(errno));
   close(ux_sockfd);
   exit(20);
  }
  if(FD_ISSET(ux_sockfd,&rSelFD))
  { 
   ux_client_len = sizeof(ux_client_addr);
   if((new_sockfd = accept(ux_sockfd, (struct sockaddr *)&ux_client_addr,
                                                      &ux_client_len)) < 0)
   {
    log(LOC,"ERROR: accept() Failed  BECAUSE %s",strerror(errno));
    close(ux_sockfd);
    exit(20);
   }
   want_trans = sizeof(message);
   act_trans = readn(new_sockfd,(char *)&message, want_trans);
   if(act_trans < want_trans)
   {
    log(LOC,"ERROR: Received %d From %d Bytes (proxy message) BECAUSE %s",
            act_trans, want_trans,strerror(errno));
    exit(20);
   }
  }
  /* else;  won't reach here */
 } while(1); 
 exit(0);
}
