/*************************
 *
 * Semaphore and shared memory compatibility routines.  Provide Xinu-like
 * access on top of (ugly) UNIX primitives.  
 * 
 * written by Craig Wills, Worcester Polytechnic Institute
 *
 * September 4, 1991
 *
 * Limitation: will only work on UNIX systems that provide semaphore and
 *	shared memory primitives.  Strict implementations of 4.3BSD do not
 *	have these primitives.   Ultrix, SunOS, Umax do have these primitives.
 *	I think System V should also have these primitives.
 */
#if defined(ns32000) || defined(mips) || defined(__hpux)
union semun {
    int val;
    struct semid_ds *buf;
    unsigned short *array;
};
#endif

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

extern int errno;

#define MAXSEM 12		/* maximum number of semaphores */

static int semBase = -1;	/* base semaphore value */
static int cSem = 0;		/* count of current semaphores */
static int iSem = 0;		/* current index of semaphore to use */
static int rgSem[MAXSEM];	/* keep track of semaphores used */

/*
 * swait() - wait on the given semaphore.  Return -1 on error, zero on
 *	success.
 */
swait(sem)
int sem;
{
    struct sembuf sembuf;

    sembuf.sem_num = sem;
    sembuf.sem_op = -1;
    sembuf.sem_flg = 0;
    while (semop(semBase, &sembuf, 1) == -1) {
	if (errno != EINTR)
	    return(-1);		/* do not return on an interrupted wait */
    }
    return(0);
}

/*
 * swaitn() - wait on the given semaphore for the given count.  Return -1 on 
 *	error, zero on success.
 */
swaitn(sem, cnt)
int sem, cnt;
{
    struct sembuf sembuf;

    sembuf.sem_num = sem;
    sembuf.sem_op = -cnt;
    sembuf.sem_flg = 0;
    while (semop(semBase, &sembuf, 1) == -1) {
	if (errno != EINTR)
	    return(-1);		/* do not return on an interrupted wait */
    }
    return(0);
}

/*
 * ssignal() - signal the given semaphore.  Returns -1 on failure.
 */
ssignal(sem)
int sem;
{
    struct sembuf sembuf;

    sembuf.sem_num = sem;
    sembuf.sem_op = 1;
    sembuf.sem_flg = 0;
    return(semop(semBase, &sembuf, 1));
}

/*
 * ssignaln() - signal the given semaphore, the given number of times.  Returns
 *	-1 on failure.
 */
ssignaln(sem, cnt)
int sem, cnt;
{
    struct sembuf sembuf;

    sembuf.sem_num = sem;
    sembuf.sem_op = cnt;
    sembuf.sem_flg = 0;
    return(semop(semBase, &sembuf, 1));
}

/*
 * screate() - create a semaphore with the given initial count.  Returns -1
 *	on failure, a semaphore id on success.  We have imposed a limit 12
 *	semaphores that can be created.
 */
screate(cnt)
int cnt;
{
    union semun arg;
    union semun null;
    key_t key;
    static int iUniq = 0;

    if (iSem >= MAXSEM)
	return(-1);		/* have reached semaphore limit */

    if (semBase == -1) {
	/* need to get a chunk of semaphores */
	/* give each user their own key */
	key = getuid();
	key = (key<<16) + iUniq++;
	if ((semBase = semget(key, MAXSEM, 0660|IPC_CREAT|IPC_EXCL)) < 0) {
	    if (errno != EEXIST)
		return(-1);
	    else {
		/* semaphore already exists, so delete and try again */
		if ((semBase = semget(key, MAXSEM, 0660)) < 0)
		    return(-1);
		/* problems if we try to just reuse, corruption on core dump?? */
		(void)semctl(semBase, 0, IPC_RMID, null);
		if ((semBase = semget(key, MAXSEM, 0660|IPC_CREAT|IPC_EXCL)) < 0)
		    return(NULL);
	    }
	}
    }
    
    /* ok, at this point we have a set of semaphores */
    arg.val = cnt;
    if (semctl(semBase, iSem, SETVAL, arg) < 0) {
	if (cSem == 0)
	    (void)semctl(semBase, 0, IPC_RMID, null);
	return(-1);
    }
    rgSem[iSem] = iSem;		/* put index value into array */
    cSem++;
    iSem++;
    return(iSem-1);
}

/*
 * sdelete() - delete the semaphore corresponding to the given sem id.  Returns
 *	-1 on failure, zero on success. 
 */
sdelete(sem)
int sem;
{
    int ret;
    union semun null;

    /* note we are not trying to reuse semaphore slots so we do nothing with
       iSem */
    if (rgSem[sem] != sem)
	return(-1);		/* does not match up with what it should */

    ret = 0;
    if (cSem <= 1) {
	ret = semctl(semBase, 0, IPC_RMID, null);
	if (ret == 0)
	    semBase = -1;
    }
    if (ret == 0) {
	cSem--;
	rgSem[sem] = -1;		/* no longer in use */
    }
    return(ret);
}

/*
 * scount() - return the count of the given sem id.  -1 on failure.
 */
scount(sem)
int sem;
{
    union semun null;

    return(semctl(semBase, sem, GETVAL, null));
}

/*
 * sreset() - reset the count of the given sem id.  Return -1 on failure,
 *	zero on success.
 */
sreset(sem, cnt)
int sem, cnt;
{
    union semun arg;

    /* need to free up processes in the queue, before reseting the count.
       need to check manual page to see if this action really happens,
       otherwise we need to do it. */
    arg.val = cnt;
    return(semctl(semBase, sem, SETVAL, arg));
}

/**************************************************************************/
#define MAXSHM 20		/* maximum number of memory chunks */
#define MAXMEM 1024		/* maximum amount of memory to be used */

static int cShm = 0;		/* count of shared memory regions */
static int cByteOffset = 0;	/* byte offset from sbShm */
static char *sbShmBase = (char *)-1; /* base location of shared memory chunk */
static int shm = -1;		/* shm value for shared memory chunk */

static char *rgsbShm[MAXSHM];	/* keep track of memory pointers */

/*
 * shmcreate() - create the given amount of shared memory and return the 
 *	pointer to this memory.  Return -1 on failure.  We have imposed
 *	a limit of 1024 bytes for the total amount of shared memory created.
 */
char *shmcreate(cnt)
int cnt;			/* number of bytes */
{
    key_t key;
    int i;
    static int iUniq = 0;

    if (cShm >= MAXSHM)
	return((char *)-1);		/* no more room to remember */

    if (sbShmBase == (char *)-1) {
	/* need to get a shared memory chunk */
	if (cnt > MAXMEM)
	    return((char *)-1);		/* will never get this much room */

	/* give each user their own key */
	key = getuid();
	key = (key<<16) + iUniq++;
	if ((shm = shmget(key, MAXMEM, 0660|IPC_CREAT|IPC_EXCL)) < 0) {
	    if (errno != EEXIST)
		return((char *)-1);
	    else {
		/* shared memory already exists, so delete and try again */
		if ((shm = shmget(key, MAXMEM, 0660)) < 0)
		    return((char *)-1);
		/* problems if we just tried to attach to old shared memory
		   after previous core dump---corruption?? */
		(void)shmctl(shm, IPC_RMID, (struct shmid_ds *)NULL);
		if ((shm = shmget(key, MAXMEM, 0660|IPC_CREAT|IPC_EXCL)) < 0)
		    return((char *)-1);
	    }
	}
	if ((int)(sbShmBase = (char *)shmat(shm, NULL, 0)) == -1) {
	    /* a problem */
	    (void)shmctl(shm, IPC_RMID, (struct shmid_ds *)NULL);
	    return((char *)-1);
	}
    }

    /* ok if we get to this point then we have a shared memory chunk */
    if ((cByteOffset + cnt) > MAXMEM)
	return((char *)-1);	/* more memory than we have */
    else {
	/* remember this offset */
	for (i = 0; i < cShm; i++)
	    if (rgsbShm[i] == (char *)-1)
		break;
	rgsbShm[i] = &sbShmBase[cByteOffset];
	cShm++;			/* one more mapping */
	if (cnt%8 == 0)
	    cByteOffset += cnt;	/* just update offset by the number of bytes */
	else
	    cByteOffset += cnt + 8 - cnt%8; /* make divisible by 8 */
	return(rgsbShm[i]);
    }
}

/*
 * shmdelete() - delete the shared memory associated with the given address.
 *	Return -1 on failure, zero on success.
 */
shmdelete(sbShm)
char *sbShm;
{
    int i, ret;

    for (i = 0; i < MAXSHM; i++) {
	if (rgsbShm[i] == sbShm) {
	    /* note we do not try and do compaction of the space! */
	    ret = 0;
	    if (cShm <= 1) {
		/* have removed the last piece, so remove entire chunk */
		(void)shmdt(sbShmBase);
		ret = shmctl(shm, IPC_RMID, (struct shmid_ds *)NULL);
		if (ret == 0) {
		    sbShmBase = (char *)-1;
		    shm = -1;
		}
	    }
	    if (ret == 0) {
		cShm--;
		rgsbShm[i] = (char *)-1; /* free up space */
	    }
	    return(ret);
	}
    }
    return(-1);			/* could not find this memory piece */
}
