/*	sem.c -- semaphores and shared memory routines
	(c) 1989, 1994, Howard E. Motteler


NAME:   sem.c -- semaphores and shared memory routines

SYNOPSIS:

This set of routines creates both a set of NSEM semaphores and
SBYTES of shared memory, to be used by a set of communicating
processes.  

sinit()		initialize or attach to semaphores and shared memory
sclear()	delete semaphore set and shared memory

ssig(s)		signal semaphore s
swait(s)	wait on semaphore s
stest(s)	returns the value of semaphore s

showall()	prints out all semaphores

sbase()		returns a char pointer to the shared memory block


DESCRIPTION:

Semaphores are indexed by integers, in the range 0 to NSEM-1; thus
for example ssig(3) signals on semaphore number 3.

Semaphores stay set when a process exits normally.  You can change
this behavior by setting SEM_UNDO in ssig() and swait(), but you
probably don't want to do that.

You can have more than one process waiting on a semaphore, and wake
them 1 at a time with signals, but the "wake order" is not specified
(at least not by me).

The first call to sinit() actually creates the semaphores and shared
memory regions, and subsequent calls simply "attach" to them.  All
processes that want to use semaphores or shared memory should call
sinit() once, before any semaphore or shared memory operations are
performed.

The sclear() routine deletes the semaphore and shared memory blocks.
It should be called whenever the last process in a group of
communication processes finishes normally.  It can also be called
from a separate program.  If you do not use sclear(), old values can
remain in semaphores.  Also, semaphores are a finite resource.  If
too many users forget to call sclear(), the system can run out of
semaphores, as they stay allocated even when you log out.  If any
processes are doing an swait() when sclear is called they will
terminate with an error message of the form "Process nn semop error
on swait()."

When any process that calls sinit() receives a Unix signal such as
^C or KILL, it calls a routine sigclear() that deletes the semaphore
and shared memory blocks, and prints a message of the form "Process
nnn terminated on unix signal."  An ^C or KILL may generate a
mixture of termination and semop error messages.

*/

#include <sys/types.h> 
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/signal.h>

#define NSEM 	16		/* number of semaphores           */
#define SBYTES	16384 		/* size of shared memory block    */
#define DEBUG	0		/* non-zero gives error messages  */
#define SPROT   0600     	/* sem/shmem protection flags     */

int shmid;			/* user's shared memory id 	  */
int semid;			/* user's semaphore id 		  */
char *shmaddr;			/* pointer to shared memory  	  */


/* Initialize or attach to semaphores and shared memory region 
 */

void sinit() {

   int i, pid;
   key_t shmkey;		/* sys V shared memory key 	*/
   key_t semkey;		/* sys V semaphore key		*/

   short outarray[NSEM];

/* extern void *shmat(); */
   extern void sigtrap();

   pid = getpid();

   for (i=0; i<NSIG; i++) signal(i, sigtrap);  /* trap signals */
   signal(SIGCLD, SIG_IGN);	/* ignore death of child */

   shmkey = 32767 & getuid();	/* shared memory key	 */
   semkey = shmkey | 32768;	/* semaphore key	 */

   /*  Get identifier of the semaphore block.  
    *  The first call to semget() with a particular key
    *  will create the block and return an index to it,
    *  while subsequent calls just return the index.
    */
   semid = semget (semkey, NSEM, SPROT | IPC_CREAT);

   if (semid == -1) {
      printf("Process %d key %d could not get %d semaphores\n",
	     pid, semkey, NSEM);
      exit(1);
      }
   else if (DEBUG)
      printf("Process %d key %d got %d semaphores ok\n", 
	     pid, semkey, NSEM);

   if (DEBUG) {
      semctl(semid, NSEM, GETALL, outarray);
      printf("Process %d initial semaphore values:", pid);
      for (i=0; i<NSEM; i++) printf(" %d", outarray[i]);
      putchar('\n');
      }

   /* Set up the shared memory region
    */
   shmid = shmget(shmkey, SBYTES, SPROT | IPC_CREAT );
   if (shmid == -1) {
      printf("Process %d key %d could not get %d bytes of shared memory\n",
	     pid, shmkey, SBYTES);
      exit(1);
      }
   else if (DEBUG)
      printf("Process %d key %d got %d bytes of shared memory ok\n",
	     pid, shmkey, SBYTES);

   /* Attach to the shared memory 
    */
   shmaddr = (char *) shmat(shmid, (char *) 0, 0);
   if (shmaddr == (char *) -1) {
      printf("Process %d key %d could not attach to shared memory\n",
	     pid, shmkey);
      exit(1);
      }
   else if (DEBUG)
      printf("Process %d key %d attached to shared memory ok\n",
	     pid, shmkey);
   }


/*  swait(s) -- the WAIT semaphore
 *
 *  s is an integer in the range 0 to NSEM-1
 */

void swait(s) int s; {
   struct sembuf psembuf;
   psembuf.sem_op = -1;
/* psembuf.sem_flg = SEM_UNDO;  */
   psembuf.sem_flg = 0;
   psembuf.sem_num = s;
   if (semop(semid, &psembuf, 1) == -1) {
      printf("Process %d: semop() error on swait(%d)\n", getpid(), s);
      exit(1);
      }
   }


/*  ssig(s) -- the SIGNAL semaphore
 *
 *  s is an integer in the range 0 to NSEM-1 
 */

void ssig(s) int s; {
   struct sembuf vsembuf;
   vsembuf.sem_op = 1;
/* vsembuf.sem_flg = SEM_UNDO;  */
   vsembuf.sem_flg = 0;
   vsembuf.sem_num = s;
   if (semop(semid, &vsembuf, 1) == -1) {
      printf("Process %d: semop error on ssig(%d)\n", getpid(), s);
      exit(1);
      }
   }


/*  stest(s) -- examine the value of semaphore s
 */

int stest(s) int s; {

   short outarray[NSEM];
   if (semctl(semid, NSEM, GETALL, outarray) == -1) {
      printf("Process %d: semctl() error on stest(%d)\n", getpid(), s);
      exit(127);
      }
   else
      return (int ) outarray[s];
   }


/*  sbase() -- return a char pointer to shared memory
 *
 *  This procedure simply returns the base address of the user's 
 *  shared memory block, cast as a character pointer
 */

char * sbase() {
   return (char *) shmaddr;
   }


/*  showall() -- print the values of all NSEM semaphores
 */

void showall() {

   int i;
   short outarray[NSEM];

   printf("semaphores:");
   if (semctl(semid, NSEM, GETALL, outarray) == -1) {
      printf("Process %d: semctl() error on showall()\n", getpid());
      exit(1);
      }
   else {
      for (i=0; i<NSEM; i++) printf(" %2d", outarray[i]);
      putchar('\n');
      printf("sem. index:");
      for (i=0; i<NSEM; i++) printf(" %2d", i);
      putchar('\n');
      }

   }


/*  sclear() -- release semaphores and shared memory
 *
 *  This procedure can be called without calling sinit() first.
 *  sclear() exits quietly if no semaphores or shared memory is
 *  allocated.
 */

void sclear() {

/* extern int semget(),  shmget(), shmdt(); */

   key_t shmkey;		/* sys V shared memory key 	*/
   key_t semkey;		/* sys V semaphore key		*/

   shmkey = 32767 & getuid();	/* shared memory key	*/
   semkey = shmkey | 32768;	/* semaphore key	*/

   /* get id of semaphore block (if any)  */
   semid = semget (semkey, NSEM, SPROT);

   if (semid != -1) semctl(semid, NSEM, IPC_RMID, 0);

   /* get id of shared memory region (if any) */
   shmid = shmget(shmkey, SBYTES, SPROT);

   if (shmid != -1) {
      shmdt(shmaddr);
      shmctl(shmid, IPC_RMID, (struct shmid_ds *) 0 );
      }
   }


/*  sigtrap() is called on unix signals (software interrupts).
 *  It detaches the process from the shared memory segment
 *  and deletes the semaphore block.
 */

void sigtrap() {
   int i;
/* extern int shmdt(); */

   /* ignore further signals (only needed for sys V) */
   for (i=0; i<NSIG; i++) signal(i, SIG_IGN);

   shmdt(shmaddr); /* detach this process from shared mem seg */
/* shmctl(shmid, IPC_RMID, 0); */
   semctl(semid, NSEM, IPC_RMID, 0);
   printf("Process %d terminated on unix signal\n", getpid());
   exit(0);
   }


/*  detach from shared memory 
 */

void shmdetatch() {
   shmdt(shmaddr); /* detach this process from shared mem seg */
   }

