
/* rpcsearch.c */
/* Andrew Davison (ad@ratree.psu.ac.th) */
/* February 11th, 1997 */

/* Fork off MAXPIDS children processes to search a 4-part large
   text 'database' (sored in a2f.txt, g2l.txt, m2r.txt, and s2z.txt).
   The processes call function search_db_1() located on
   the machines in hostnms[].
   The results are saved in shared memory connected to ms[]
   The parent waits until all the children finish, and 
   records their exit status and time of operation in seconds.
*/

#include <stdio.h>
#include <string.h>
#include <unistd.h>    /* for fork(), getpid(), _exit() */
#include <sys/types.h>
#include <sys/wait.h>  /* wait() */
#include <time.h>      /* for time() */
#include <sys/ipc.h>
#include <sys/shm.h>
#include <rpc/rpc.h>
#include "match.h"     /* XDR data types in C */


#define MAXLEN    130   /* max length of a string */
#define MAXMATCH  10    /* max number of reported matches in a component file */
#define MAXPIDS   4     /* max number of forked processes */

typedef struct {
  int pid;
  int exit_status;
  time_t start_time, end_time;
} pid_info;      /* process information */

typedef struct {
  int pid;
  int shmid;        /* shared memory ID */
  int count;        /* number of matching lines */
  char lines[MAXMATCH][MAXLEN];    /* matching lines */
} Matches;


void create_shared(Matches *ms[]);
void search_db(char *match_str, char *fnm, char *host, Matches *ms);
void wait_finish(pid_info pids[], int wait_num);
int in_pids(int pid, pid_info pids[]);
void print_results(Matches *ms[]);
void delete_shared(Matches *ms[]);


int main()
/* Try to invoke MAXPIDS children and then wait for their 
   completion. Print the execution time for main() in secs.
*/
{
  int i, pid, wait_num = 0;
  time_t start_time;
  pid_info pids[MAXPIDS];
  char *fnms[] = {"/home/ad/pldbs/a2f.txt", 
                  "/export/user/home/ad/pldbs/g2l.txt",
                  "/export/user/home/ad/pldbs/m2r.txt",
                  "/home/ad/s2z.txt"};
  char *hosts[] = {"catsix", "fivedots", "fivedots", "darkgrey"};
  char match_str[MAXLEN];
  Matches *ms[MAXPIDS];     /* an array of shared memory */

  printf("Input a match string: ");
  gets(match_str);

  start_time = time(NULL);
  create_shared(ms);

  for (i = 0; i < MAXPIDS; i++) {
    pid = fork();
    if (pid == 0)           /* in the child */
      search_db(match_str, fnms[i], hosts[i], ms[i]);
    else if (pid > 0) {     /* in the parent */
      pids[i].start_time = time(NULL);
      pids[i].pid = pid;
      wait_num++;
    }
    else {  /* pid < 0 */
      fprintf(stderr, "fork %d returned an error\n", i);
      pids[i].pid = -1;
    }
  }
  wait_finish(pids, wait_num);
  print_results(ms);
  delete_shared(ms);

  printf("\nExecution time for main(): %.0f secs\n", 
                       difftime(time(NULL), start_time) );
  return 0;
}


void create_shared(Matches *ms[])
/* Allocate shared memory (with shmget()) and then attach
   it to a pointer in ms. Do this for each of the MAXPIDS
   elements in ms[]. */
{
  int i, id, msize, shmid;

  msize = sizeof(Matches);
  for (i = 0; i < MAXPIDS; i++) {
    shmid = shmget((key_t)i, msize, (IPC_CREAT | 0666));
    if (id < 0) {
      perror("shmget failure: ");
      exit(1);
    }
    ms[i] = (Matches *)shmat(shmid, 0, 0);
    if (ms[i] < (Matches *)0) {
      perror("shmget failed: ");
      exit(2);
    }
    ms[i]->pid = -1;     /* dummy value for now */
    ms[i]->shmid = shmid;
  }
}


void search_db(char *match_str, char *fnm, char *host, Matches *ms)
/* Open a UDP style connection to the server holding the search
   function. Call the function, search_db_1(), and store the 
   results in ms[]. 
*/
{
  CLIENT *clnt;
  search_info si;         /* input argument to the search function */
  search_matches *sm;     /* output argument */
  int i, matchnum;

  ms->pid = getpid();
  ms->count = 0;

  clnt = clnt_create(host, SEARCHPROG, SEARCHVERS, "udp");
  if (clnt == NULL) {
    clnt_pcreateerror(host);
    exit(1);
  }

  /* build si input */
  si.match_str = strdup(match_str);
  si.fnm = strdup(fnm);

  sm = search_db_1(&si, clnt);
  if (sm == NULL) {
    clnt_perror(clnt, host);
    exit(2);
  }

  /* build ms[] output from sm */
  ms->count = sm->count;
  matchnum = (ms->count < MAXMATCH) ? ms->count : MAXMATCH;
  for (i=0; i < matchnum; i++)
    strcpy(ms->lines[i], (char *)sm->lines.lines_val[i]);  

  printf("%d: number of matches = %d\n", ms->pid, ms->count);

  clnt_destroy(clnt);
  exit(0);
}


void wait_finish(pid_info pids[], int wait_num)
/* Wait for the child processes to finish, and record
   their time of operation (in secs) and exit status in
   pids[]. The exit status value is 0 for success or 1
   for a read file error.
*/
{
  int num_returned = 0, deadpid, posn;
  int status;

  while (num_returned < wait_num) {
    deadpid = wait(&status);
    if ((posn = in_pids(deadpid, pids)) != -1) {
      pids[posn].end_time = time(NULL);
      /* printf("%d: end time: %s\n", deadpid,
                           ctime(&pids[posn].end_time)); */
      if (WIFEXITED(status) != 0) {    /* exited */
        pids[posn].exit_status = (int) WEXITSTATUS(status);
        printf("%d exited with value %d\n", 
                        deadpid, pids[posn].exit_status);
      }
      else {
        fprintf(stderr, "%d exited abnormally\n", deadpid);
        pids[posn].exit_status = -1;
      }
      printf("%d executed for %.0f secs\n", deadpid, 
                 difftime(pids[posn].end_time, pids[posn].start_time) );
      num_returned++;
    }
    else
      fprintf(stderr, "Unknown process %d exited\n", deadpid);
  }
}


int in_pids(int pid, pid_info pids[])
/* If pid is in pids[] return the index position, 
   otherwise -1 */
{
  int i;

  for (i = 0; i < MAXPIDS; i++)
    if (pid == pids[i].pid)
      return i;
  return -1;
}


void print_results(Matches *ms[])
/* Print out the matches, and the pid of the process that
   did it. */
{
  int i, j, matchnum;

  for (i=0; i < MAXPIDS; i++)
    if (ms[i]->pid != -1) {
      printf("%d matches (%d)\n", ms[i]->pid, ms[i]->count);
      if (ms[i]->count < MAXMATCH)
        matchnum = ms[i]->count;
      else
        matchnum = MAXMATCH;
      for (j = 0; j < matchnum; j++)
        /* printf("%2d. %s\n", j+1, ms[i]->lines[j]) */ ;
      putchar('\n');
    }
    else
      printf("ms element %i pid = -1\n", i);
}


void delete_shared(Matches *ms[])
/* Detach and remove shared memory pointed to by ms[] */
{
  int i, shmid;

  for (i = 0; i < MAXPIDS; i++) {
    shmid = ms[i]->shmid;          /* record shared memory ID */
    if (shmdt((char *)ms[i]) < 0)
      perror("shmdt error: ");
    if (shmctl(shmid, IPC_RMID, (struct shmid_ds *)0) < 0)
      perror("shmctl error: ");
  }
}


