/* A bounded-buffer program using a shared buffer and semaphores  
 * Created by Henry Walker, 2004
 * Revised 
 *   Janet Davis, 26 September 2006
 *   Jerod Weinman, 25 June 2008
 */

#include <stdlib.h>
#include <unistd.h>          
#include <stdio.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/sem.h>
#include <sys/ipc.h>

#define BUF_SIZE 5     /* logical size of buffer */
#define SHARED_MEM_SIZE (BUF_SIZE+2)*sizeof(int) 
                       /* size of shared memory */
#define RUN_LENGTH 10  /* number of iterations in test run */

#define BUF_USED  0    /* semaphore array index to check buffer elts used */
#define BUF_SPACE 1    /* semaphore array index to check buffer elts empty */
#define NUM_SEMS  2    /* total number of semaphores needed */


/* create and initialize semaphores 
 * return the semaphore id */
int sem_init(void) {
   int semid;

   /* create new semaphore set of 2 semaphores */
   /* create with permission 0600 = read/alter by user only */
   if ( (semid = semget (IPC_PRIVATE, NUM_SEMS, IPC_CREAT | 0600)) < 0 ) {
     perror( "error in creating semaphores" );
     exit( 1 );
   }

   /* initialization of semaphores */

   /* Initially, all BUF_SIZE spaces in the buffer are empty */
   if ( semctl (semid, BUF_SPACE, SETVAL,  BUF_SIZE) < 0 ) {
     perror( "error in initializing first semaphore" );
     exit( 1 );
   }

   /* Initially, 0 items in the buffer */
   if ( semctl (semid, BUF_USED, SETVAL, 0) < 0 )  {
     perror ("error in initializing second semaphore");
     exit (1);
   }

   return semid;
}

/* perform a wait (down, P) operation on a semaphore of given id and index
 */
void sem_wait( int semid, int index ) {

  struct sembuf sops[1];  /* only one semaphore operation to be executed */
   
  sops[0].sem_num = index;/* define operation on semaphore with given index */
  sops[0].sem_op  = -1;   /* subtract 1 to value for P operation */
  sops[0].sem_flg = 0;    /* type "man semop" in shell window for details */

  if ( semop( semid, sops, 1 ) < 0 ) {
    perror ("error in semaphore operation");
    exit (1);
  }
}

/* perform a signal (up, V) operation on semaphore of given id and index 
 */
void sem_signal( int semid, int index ) {
   struct sembuf sops[1];  /* only one semaphore operation to be executed */

   sops[0].sem_num = index;/* define operation on semaphore with given index */
   sops[0].sem_op  = 1;    /* add 1 to value for V operation */
   sops[0].sem_flg = 0;    /* type "man semop" in shell window for details */

   if ( semop( semid, sops, 1 ) < 0) {
     perror( "error in semaphore operation" );
     exit( 1 );
   }
}

/* Parent process: writes values to shared memory buffer */
void producer( int* buffer, int* in, int* out, int semid ) {
  int i; /* counter variable */     
  for ( i = 0; i < RUN_LENGTH; i++ ) {
    sem_wait( semid, BUF_SPACE );  /* wait semaphore for space available */

    buffer[*in] = i*i;    /* put data in buffer */
    *in = (*in + 1) % BUF_SIZE;
    printf( "Writer's report: item %2d put in buffer\n", i );

    sem_signal( semid, BUF_USED ); /* signal semaphore for something used */

    if ((i % 4) == 0) sleep( 1 ); 
        /* to slow things down,
	 * take time to generate every fourth element */
  }
}

/* Child process: reads and reports values from shared memory buffer */
void consumer( int* buffer, int* in, int* out, int semid ) {
  int i;      /* counter variable */
  int value;  /* value read from share memory */

  for ( i = 0; i < RUN_LENGTH; i++ ) {
    sem_wait( semid, BUF_USED );  /* wait on semaphore for something used */

    value = buffer[*out];
    *out = (*out + 1) % BUF_SIZE;
    printf( "Reader's report: item %2d == %2d\n", i, value );

    sem_signal( semid, BUF_SPACE ); /* signal semaphore for space available */

    if ((i % 3) == 1) sleep( 1 ); /* to slow things down,
	                           * take time to process every third element */
  }
}

int main( void ) {
  pid_t pid;          /* variable to record process id of child */

  /* shared memory elements */
  caddr_t shared_memory;   /* shared memory base address */
  int *in;         /* pointer to logical 'in' address for producer */
  int *out;        /* pointer to logical 'out' address for consumer */
  int *buffer;     /* logical base address for buffer */

  /* semaphore elements */
  int semid;       /* identifier for a semaphore set */

  /* set up shared memory segment */
  shared_memory = mmap( 0, SHARED_MEM_SIZE, 
                        PROT_READ | PROT_WRITE, 
                        MAP_ANONYMOUS | MAP_SHARED, 
		        -1, 0 );
  if ( shared_memory == (caddr_t) -1 ) {
    perror( "error in mmap while allocating shared memory\n" );
    exit (1);
  }

  /* set up pointers to appropriate places in shared memory segment */
  buffer = (int*) shared_memory;
           /* logical buffer starts at beginning of shared segment */
  in  = (int*) shared_memory + BUF_SIZE * sizeof(int);
  out = (int*) shared_memory + (BUF_SIZE+1) * sizeof(int);

  *in = *out = 0;          /* initial starting points */

  /* create and initialize semaphore */
  semid = sem_init();

  /* spawn child process and check for error */
  if ( (pid = fork()) < 0 )  {
    perror( "error in fork" );
    exit( 1 );
  }

  if ( 0 == pid ) {
    /* processing for child == consumer */
    printf( "The consumer process begins.\n" );
    consumer( buffer, in, out, semid );
    printf( "Reader done.\n" );

  } else { 
    /* processing for parent == producer */

    printf( "The producer process begins.\n" );
    producer( buffer, in, out, semid );
    wait( NULL ); /* wait for child process */
    printf( "Writer done.\n") ;  

    /* Remove the semaphore from the system and destroy the set of
     * semaphores and data structure associated with it. */
    if ( semctl( semid, 0, IPC_RMID ) < 0 ) {
      perror( "error in removing semaphore from the system" );
      exit( 1 );
    }
    printf( "Semaphore cleanup complete.\n" );
  }

  exit( 0 );
}
