/* Filename: p4.c
 * Created on Tuesday, Feb. 5, 2002
 *
 * In this program, two processes are accessing the same shared memory segment.
 * Semaphores are used for mutual exclusion.
 */

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

/* Solaris needs semun definition. */
union semun {
  int              val;
  struct semid_ds *buf;
  ushort          *array;
};

#define MAX_TRANS    2000000L

struct account_struct {
  long cnt;
  double amount;
};

int main() {
  int    mysemid;              /* semaphore identifier */
  key_t  mysemkey;             /* key for semaphores */
  ushort semarray[] = { 1 };   /* Initial value for semaphore */
  union semun mysemun;
  struct sembuf mysembuf;  

  int    myshmid;              /* shared mem identifier */
  key_t  myshmkey;             /* key for shared memory segment */
  struct account_struct *p_acc;   /* pointer to my shared data */

  /*--------------------------------------------------------------
   * Semaphore creation and initialization 
   *-------------------------------------------------------------*/
  mysemkey = ftok(".", 'D');  
  mysemid = semget(mysemkey, 1, IPC_CREAT | 0666);
  if (mysemid == -1 ) {
    perror("semget: error");
    exit(1);
  }

  printf("\nSuccessfully created the semaphore.\n");

  mysemun.array = semarray;
  if (semctl(mysemid, 0, SETALL, mysemun) == -1) {
    perror("semctl: Failed to initialize semaphores.");
    exit(2);
  }
  printf("Successfully initialized the semaphore.\n");

  /*--------------------------------------------------------------
   * Shared memory segment creation, initialization, and attaching 
   *-------------------------------------------------------------*/
  myshmkey = ftok(".", 'E');  

  myshmid = shmget(myshmkey, sizeof(struct account_struct), IPC_CREAT | 0666);
  if (myshmid == -1 ) {
    perror("shmget: error");
    exit(3);  
  }

  p_acc = (struct account_struct *) shmat(myshmid, 0, 0);
  if (p_acc == (struct account_struct *) -1) {
    perror("shmat: Failed to attach.");
    exit(4);
  }
  printf("Successfully initialized the shared mem.\n");

  /*----------------------------
   * Initialize the account. 
   *---------------------------*/
  p_acc->cnt = 0L;
  p_acc->amount = 0.;

  printf("Please be patient...\n\n");
  fflush(stdout); /* This is necessary right before calling fork(). */

  /*-----------------------------------------------
   * Create a process. 
   * Use the semaphore for mutual exclusion.
   *----------------------------------------------*/

  switch(fork()) {
  case -1: perror("fork: error");break;
  case  0: 
           { /* Child process adds 1 to the account MAX_TRANS times. */
             struct sembuf mysembuf_W = { 0, -1, 0 };
             struct sembuf mysembuf_S = { 0,  1, 0 };
             long i;

             for (i = 0L; i < MAX_TRANS; i++) { 
                 if (semop(mysemid, &mysembuf_W, 1) == -1) {     /* WAIT   */
                   perror("semop: Failed to decrement sem_A");
                   exit(10);
                 }

                 p_acc->amount += 1.0;
                 p_acc->cnt++;

                 if (semop(mysemid, &mysembuf_S, 1) == -1) {     /* SIGNAL */
                   perror("semop: Failed to increment sem_A");
                   exit(11);
                 }
             }
             
             shmdt((void *)p_acc);
             exit(0);
           } 
  default:          
           { /* Parent process subtracts the account by 1 MAX_TRANS times. */
             struct sembuf mysembuf_W = { 0, -1, 0 };
             struct sembuf mysembuf_S = { 0,  1, 0 };
             long i;

             for (i = 0L; i < MAX_TRANS; i++) { 
                 if (semop(mysemid, &mysembuf_W, 1) == -1) {     /* WAIT   */
                   perror("semop: Failed to decrement sem_A..");
                   exit(12);
                 }

                 p_acc->amount -= 1.0;
                 p_acc->cnt++;

                 if (semop(mysemid, &mysembuf_S, 1) == -1) {     /* SIGNAL */
                   perror("semop: Failed to increment sem_A..");
                   exit(13);
                 }
             }
             wait(NULL);

             printf("\naccount.cnt    = %10d\n", p_acc->cnt);
             printf("account.amount = %13.2f\n", p_acc->amount);
             shmdt((void *)p_acc);
           } 
  }

  /*---------------------------------------------
   * Remove a shared mem segment and semaphores. 
   *--------------------------------------------*/
  if (shmctl(myshmid, IPC_RMID, 0) == -1) {
    perror("shmctl: Failed to remove shared mem segment.");
    exit(5);
  }
  if (semctl(mysemid, 0, IPC_RMID, 0) == -1) {
    perror("semctl: Failed to remove semaphores.");
    exit(6);
  }
  printf("\n");
  return 0;
}

