/* This program simulates a pencil factory.
 * 
 * Pencils are made from four parts. Each manufacturer gets the four
 * parts and makes a pencil from them. However if one of the bins is
 * empty, he calls the supplier who then comes to refill some of the
 * bins.
 * 
 * When enough pencils have been made, all the processes quit.
 * 
 * usage: pencils m p
 * where m is the number of manufacturers to use
 *       p is the number of pencils to make
 * 
 * link (ln -s): ipc.h and libipc.a to your working directory before
 * you try to compile the program.  
 *   ln -s /stud/docs/kurs/os/process_synch/ipc.h .
 *   ln -s /stud/docs/kurs/os/process_synch/libipc.a .  
 * 
 * compile with: 
 * gcc -Wall -D__USE_FIXED_PROTOTYPES__ -o pencils pencils.c -L. -lipc
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include "ipc.h"

/* these aren't declared properly in the headers */
extern void srand48(long);
extern double drand48(void);

/* declarations of the functions in this file */
void pencil_factory(int manu_count, int pen_count);
void clean_exit();
void clean_ipc(void);
void supply(int count);
void manufacture(int id, int count);
int some_number(int min, int max);
void coffee_break(void);
int check_supplies(int id);
int make_pencil(int id);
void refill();
void rest();

/* these are the parts needed to make a pencil */
enum PARTS {WOOD=0, LEAD, RING, ERASER};
char *partnames[]={"wood", "lead", "ring", "eraser"};
#define NUMPARTS 4

/* these are the shared data structures */
int *parts=NULL;   /* contents of part bins */
int *pencils=NULL; /* num of pencils we've made so far */

/* we need two semaphores to make this work - one to protect the
 * shared data, and one to synchronize the supplier with the other
 * processes. Observe that both semaphores are declared below but
 * created and initialised in the pencil_factory() function.
 */
semid_t parts_sem;      /* for shared data */
semid_t supplier_sem;   /* to call supplier */

/* main just parses the command line before calling pencil_factory to
 * do the actual job.
 */
int main(int argc, char **argv)
{
  int manu_count;        /* num of manufacturers to use */
  int pen_count;         /* num of pencils to make */

  if (argc!=3) {
    fprintf(stderr, "Usage: pencils m p\n");
    fprintf(stderr, "where m is number of manufacturers to use\n");
    fprintf(stderr, "      p is number of pencils to make\n");
    fprintf(stderr, "ex: pencils 4 25\n");
    exit(1);
  }

  manu_count=atoi(argv[1]);
  if (manu_count < 1) manu_count=1;

  pen_count=atoi(argv[2]);
  if (pen_count < 1) pen_count=1;

  fprintf(stderr,"%d manufacturer%s will make %d pencil%s.\n",
	  manu_count, (manu_count==1?"":"s"),
	  pen_count, (pen_count==1?"":"s"));

  /* start production */
  pencil_factory(manu_count, pen_count);

  return 0;
}

/* This function allocates all needed resources (shared memory,
 * semaphores), then starts production by creating the supplier and
 * manufacturer processes.
 */
void pencil_factory(int manu_count, int pen_count)
{
  int i;
  int n=0;               /* no of successful forks */

  /* I catch this so that I can interrupt the program (Ctrl-C) 
     without leaving a lot of ipc objects 
     */
  signal(SIGINT,clean_exit);

  /* 
   * allocate the ipc (shared memory, semaphores) 
   */
  if ((parts=(int *)shmalloc(NUMPARTS*sizeof(*parts))) == NULL) {
    fprintf(stderr,"can't allocate shared memory!\n"); /* failed */
    clean_exit();
  }

  if ((pencils=(int *)shmalloc(sizeof(*pencils))) == NULL) {
    fprintf(stderr,"can't allocate shared memory!\n"); /* failed */
    clean_exit();
  }

  supplier_sem=-1;   /* to indicate failure */
  if ((supplier_sem=semcreate(0)) == -1) {
    perror("semcreate"); /* failed */
    clean_exit();
  }

  parts_sem=-1;      /* to indicate failure */
  if ((parts_sem=semcreate(1)) == -1) {
    perror("semcreate"); /* failed */
    clean_exit();
  }
  
  /* we've made no pencils yet */
  *pencils=0;

  /* we start with empty bins (no parts available) */
  for (i=0; i<NUMPARTS; i++) {
    parts[i]=0;
  }

  /* start 'manu_count+1' processes */
  for (i=0; i<=manu_count; i++) {
    switch (fork()) {
    case -1:
      /* this is the parent process after an unsuccessful fork */

      /* show the error */
      perror("fork");

      /* this will cause children to exit */
      *pencils=pen_count;
      clean_ipc();
      break;           

    case 0:
      /* this is the child after a successful fork */
      sleep(2);

      /* let each process get unique sequence of random nums */
      srand48(getpid());

      /* we start one supplier, and manu_count manufacturers */
      if (i==0) supply(pen_count);
      else manufacture(i,pen_count);

      /* all children exit here */
      exit(0);

    default:
      /* this is the parent after a successful fork */
      /* count the process */
      n++;
    }
  }

  /* main process waits for children to finish and cleans up zombies */
  for (i=0; i<n; i++)
    wait(NULL);

  /* remove any remaining ipc */
  clean_ipc();

  return;
}

/* signal handler for SIGINT so we don't leave stray ipc */
void clean_exit()
{
  clean_ipc();
  exit(1);
}

/* Since I do this in so many places I made a general function for it.
 * Make sure this is called before every exit once ipc has been
 * allocated, otherwise they stick around forever in the system. 
 */
void clean_ipc(void)
{
  if (parts) shfree(parts);
  if (pencils) shfree(pencils);
  if (parts_sem != -1) semdestroy(parts_sem);
  if (supplier_sem != -1) semdestroy(supplier_sem);
}

/******************************************************** 
 * This is the supplier process. It runs when one of the 
 * manufacturers discover that one of the four components 
 * has run out. It will check all of the bins and refill 
 * the ones that are low. 
 ********************************************************/
void supply(int count)
{
  while (1) {
    if (*pencils>=count) break;
    fprintf(stderr,"supplier refilling: \n");

    /* the supplier cannot be trusted... */
    coffee_break();

    /* refill the bins that needs it */
    refill();

    fprintf(stderr,"supplier done for now\n");
  }

  fprintf(stderr,"supplier exiting\n");
}

/******************************************************** 
 * This is the function used by all the manufacturer processes. 
 * It gathers the four components and makes a pencil, until all 
 * the pencils have been made. If any of the components has run 
 * out, it calls the supplier to refill the bins.
 ********************************************************/
void manufacture(int id, int count)
{
  int num=0;   /* holds number of pencils made by the manufacture */
  int s=id*16; /* this lets each process get his own message column */

  while (1) {
    if (*pencils >= count) break;
    fprintf(stderr,"%*s%d: getting parts\n",s,"",id);
    if (check_supplies(id)) {
      make_pencil(id);
      num++;
    }
    else {
      /* The supplier must add parts */
      fprintf(stderr,"%*s%d: calling supplier\n",s,"",id);
    }
    /* When ready, the manufacturer is allowed to rest for a while */
    rest();
  }
  fprintf(stderr,"%*s%d: made %d pencils\n",s,"",id,num);
  fprintf(stderr,"%*s%d: exiting\n",s,"",id);
}



/* 
 * When the supplier is called it is allowed to sometimes take 
 * a little break.
 */
void coffee_break(void)
{    
  if ((some_number(1,4)) == 4) {
    fprintf(stderr,"taking coffee break...\n");
    sleep(4);
  }
}

/*
 * Add supplies to the bins
 */
void refill() 
{
  int i, n, supplied = 0;

  /* Do refilling on each bin */
  for (i=0; i<NUMPARTS; i++) {
    if (parts[i] < 3) {
      /* Check that one of the bins where actually empty */
      if (parts[i] < 1)
	supplied = 1;
      n=some_number(4,10);
      /* It takes some time to put parts into each bin */
      sleep(1);
      /* Add the supplies */
      parts[i]+=n;
      fprintf(stderr," %6ss:  +%d -> %d\n",partnames[i],n,parts[i]);
    }
    else {
      fprintf(stderr," %6ss: = %d\n",partnames[i],parts[i]);
    }
  }
  /* Check that we actually has refilled some bins */
  if (!supplied) 
    fprintf(stderr, "** Error: Supplier was called without being needed\n");
}


/* 
 * Let some other process get the semaphore 
 */
void rest() 
{
  sleep(some_number(1, 3));
}

/* 
 * This function checks to make sure we have at least one of each
 * component needed to make a pencil.
 */
int check_supplies(int id)
{
  int i=0;

  for (i=0; i<NUMPARTS; i++) {
    if (parts[i] <= 0) {
      fprintf(stderr,"%*s%d: no %ss left!\n",id*16,"",id,partnames[i]);
      return 0;
    }
  }

  return 1;
}

/* This function makes a single pencil, but we must have 
 * supplies available (no check is made) 
 */
int make_pencil(int id)
{
  int num;
  int s=id*16;

  fprintf(stderr,"%*s%d: making pencil\n",s,"",id);
  /* It takes some time to make a pencil */
  sleep(1);
  parts[WOOD]--;
  parts[LEAD]--;
  parts[RING]--;
  parts[ERASER]--;
  num=++(*pencils);
  fprintf(stderr,"%*s%d: made pencil %d\n",s,"",id,num);

  return num;
}

/*
 * return a random number betweeen min and max
 */
int some_number(int min, int max)
{
  return (drand48() * (max-min+1))+min;
}

/* end */

