/* 
 * syncSysV.c --
 *
 *	Do system V compatibility synchronization functions:
 *		semctl, semget, semop
 *
 * Copyright (C) 1990 Regents of the University of California
 * All rights reserved.
 */

#ifndef lint
static char rcsid[] = "$Header: /cdrom/src/kernel/Cvsroot/kernel/sync/syncSysV.c,v 1.7 91/09/10 18:43:37 rab Exp $ SPRITE (Berkeley)";
#endif /* not lint */

#include <sprite.h>
#include <sync.h>
#include <spriteTime.h>
#include <fs.h>
#include <stdio.h>
#include <bstring.h>
#include <vm.h>
#include <stdlib.h>


Sync_SysVSem semTable[SEMMNI];
static int totalSems = 0;

static Sync_Lock semLock = Sync_LockInitStatic("Sync:sysVSemaphores");
#define LOCKPTR (&semLock)

extern int vmShmDebug;

/*
 * Debugging print statement declaration.
 */
#ifndef lint 
#define dprintf if (vmShmDebug) printf
#else
#define dprintf printf
#endif

/*
 * Compat. defs.
 */
#define EPERM	GEN_EPERM
#define ENOENT	GEN_ENOENT
#define EINTR	GEN_EINTR
#define EACCES	GEN_EACCES
#define EEXIST	GEN_EEXIST
#define EINVAL	GEN_EINVAL
#define ENOSPC	GEN_ENOSPC

#define EFBIG	GEN_EFBIG
#define	E2BIG	GEN_E2BIG
#define EAGAIN	GEN_EAGAIN
#define EFAULT	GEN_EFAULT
#define ERANGE	GEN_ERANGE
#define EIDRM	GEN_EIDRM

#define DEFAULTGID 115

static int semInit = 0;

/*
 *----------------------------------------------------------------------
 *
 * Sync_SemInit --
 *
 *	Perform semaphore initialization functions.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Initializes datastructures.
 *
 *----------------------------------------------------------------------
 */
void
Sync_SemInit()
{
    int i;
    if (semInit == 1234) {
	return;
    }
    semInit = 1234;
    LOCK_MONITOR;
    for (i=0;i<SEMMNI;i++) {
	semTable[i].sem_base = (struct sem *)NIL;
    }
    UNLOCK_MONITOR;
}

/*
 *----------------------------------------------------------------------
 *
 * Sync_SemctlStub --
 *
 *	Perform control functions on semaphores.
 *
 * Results:
 *	Returns result of the function.
 *
 * Side effects:
 *	Performs a function on the semaphore.
 *
 *----------------------------------------------------------------------
 */
ReturnStatus
Sync_SemctlStub(semid, semnum, cmd, arg, retValOut)
    int		semid;		/* Semaphore group identifier. */
    int		semnum;		/* Semaphore number. */
    int		cmd;		/* Command to perform. */
    union semun	arg;		/* Command argument. */
    int		*retValOut;	/* Result of command. */
{

    ReturnStatus	status;
    int			perm;
    Sync_SysVSem	*semPtr;
    int			uid;
    struct semid_ds	semBuf;
    int			i;
    int			nsems;
    struct sem		*curSem;
    ushort		array[SEMMSL];
    Time		timeVal;
    int			retVal=0;

    dprintf("call: semctl(%d, %d, %d, %x)\n", semid, semnum, cmd, arg);
    Sync_SemInit();
    LOCK_MONITOR;
    if (cmd != GETKEYS) {
	status = Sync_SemStruct(semid, &perm, &semPtr);
	if (status != SUCCESS) {
	    UNLOCK_MONITOR;
	    return status;
	}
    }
    switch (cmd) {

	case SETVAL:
	case SETALL:
	    if (!(perm & SEM_A)) {
		UNLOCK_MONITOR;
		dprintf("Access denied\n");
		return EACCES;
	    }
	    break;
	case IPC_SET:
	case IPC_RMID:
	    uid=Proc_GetEffectiveProc()->effectiveUserID;
	    if (uid != 0 && uid != semPtr->sem_perm.uid &&
		    uid != semPtr->sem_perm.cuid) {
		UNLOCK_MONITOR;
		dprintf("Permission denied\n");
		return EPERM;
	    }
	    break;
	case GETKEYS:
	    break;
	case GETVAL:
	case GETPID:
	case GETNCNT:
	case GETZCNT:
	case GETALL:
	case IPC_STAT:
	    if (!(perm & SEM_R)) {
		UNLOCK_MONITOR;
		dprintf("Access denied\n");
		return EACCES;
	    }
	    break;
	default:
	    UNLOCK_MONITOR;
	    dprintf("invalid command\n");
	    return EINVAL;
    }
	    
    if (cmd == GETVAL || cmd == SETVAL || cmd == GETPID || cmd == GETNCNT ||
	    cmd == GETZCNT) {
	if (semnum < 0 || semnum >= semPtr->sem_nsems) {
	    UNLOCK_MONITOR;
	    dprintf("Invalid semnum: %d\n",semnum);
	    return EINVAL;
	}
	curSem = &semPtr->sem_base[semnum];
    }
    switch (cmd) {
	case GETVAL:
	    retVal = curSem->semval;
	    dprintf("semctl(GETVAL) returns %d\n", retVal);
	    break;
	case SETVAL:
	    if (arg.val < 0 || arg.val > SEMVMX) {
		UNLOCK_MONITOR;
		dprintf("Invalid value %d\n",arg.val);
		return ERANGE;
	    }
	    curSem->semval = arg.val;
	    if ( (arg.val == 0 && curSem->semzcnt > 0) ||
		    (arg.val > 0 && curSem->semncnt > 0)) {
		Sync_Broadcast(&curSem->semLock);
	    }
	    dprintf("semctl(SETVAL) returns %d\n", retVal);
	    break;
	case GETPID:
	    retVal = curSem->sempid;
	    dprintf("semctl(GETPID) returns %d\n", retVal);
	    break;
	case GETNCNT:
	    retVal = curSem->semncnt;
	    dprintf("semctl(GETNCNT) returns %d\n", retVal);
	    break;
	case GETZCNT:
	    retVal = curSem->semzcnt;
	    dprintf("semctl(GETZCNT) returns %d\n", retVal);
	    break;
	case GETALL:
	    for (i=0;i<semPtr->sem_nsems;i++) {
		array[i] = semPtr->sem_base[i].semval;
	    }
	    status = Vm_CopyOut(sizeof(ushort)*semPtr->sem_nsems,
		    (Address) array, (Address) arg.array);
	    if (status != SUCCESS) {
		UNLOCK_MONITOR;
		dprintf("Copy-out fault\n");
		return EFAULT;
	    }
	    dprintf("semctl(GETALL) done\n");
	    break;
	case GETKEYS:
	    bzero((Address)&semBuf, sizeof(struct semid_ds));
	    for (i=0;i<SEMMNI;i++) {
		semBuf.sem_perm.mode = semTable[i].sem_perm.mode;
		semBuf.sem_perm.key = semTable[i].sem_perm.key;
		semBuf.sem_perm.seq = semTable[i].sem_perm.seq;
		semBuf.sem_perm.uid = semTable[i].sem_perm.uid;
		semBuf.sem_perm.gid = semTable[i].sem_perm.gid;
		semBuf.sem_nsems =
			(semTable[i].sem_base == (struct sem *)NIL) ? 0 : 1;
		status = Vm_CopyOut(sizeof(struct semid_ds), (Address) &semBuf,
			(Address) (arg.buf+i));
		if (status != SUCCESS) {
		    UNLOCK_MONITOR;
		    dprintf("Copy-out fault\n");
		    return EFAULT;
		}
	    }
	    dprintf("semctl(GETALL) done\n");
	    break;
	case IPC_STAT:
	    status = Vm_CopyOut(sizeof(struct semid_ds), (Address) semPtr,
		    (Address) arg.buf);
	    if (status != SUCCESS) {
		UNLOCK_MONITOR;
		dprintf("Copy-out fault\n");
		return EFAULT;
	    }
	    dprintf("semctl(IPC_STAT) done\n");
	    break;
	case SETALL:
	    status = Vm_CopyIn(sizeof(ushort)*semPtr->sem_nsems,
		    (Address) arg.array, (Address) array);
	    if (status != SUCCESS) {
		UNLOCK_MONITOR;
		dprintf("Copy-in fault\n");
		return EFAULT;
	    }

	    for (i=0;i<semPtr->sem_nsems;i++) {
		if (array[i] < 0 || array[i] > SEMVMX) {
		    UNLOCK_MONITOR;
		    dprintf("Invalid value: %d\n",array[i]);
		    return ERANGE;
		}
	    }
	    for (i=0;i<semPtr->sem_nsems;i++) {
		semPtr->sem_base[i].semval = array[i];
	    }
	    for (i=0;i<semPtr->sem_nsems;i++) {
		curSem = &semPtr->sem_base[i];
		if ( (array[i] == 0 && curSem->semzcnt > 0) ||
			(array[i] > 0 && curSem->semncnt > 0)) {
		    Sync_Broadcast(&curSem->semLock);
		}
	    }
	    dprintf("semctl(SETALL) done\n");
	    break;
	case IPC_SET:
	    status = Vm_CopyIn(sizeof(struct semid_ds), (Address) arg.buf,
		    (Address) &semBuf);
	    if (status != SUCCESS) {
		UNLOCK_MONITOR;
		dprintf("Copy-in fault\n");
		return EFAULT;
	    }
	    semPtr->sem_perm.uid = semBuf.sem_perm.uid;
	    semPtr->sem_perm.gid = semBuf.sem_perm.gid;
	    semPtr->sem_perm.mode = semBuf.sem_perm.mode & 0666;
	    dprintf("semctl(IPC_SET) done\n");
	    break;
	case IPC_RMID:
	    semPtr->sem_perm.seq++;
	    nsems = semPtr->sem_nsems;
	    if (nsems > SEMMNI) nsems = SEMMNI;
	    curSem = semPtr->sem_base;
	    semPtr->sem_base = (struct sem *)NIL;
	    semPtr->sem_perm.key = 0;
	    for (i=0;i<nsems;i++) {
		if (curSem[i].semzcnt != 0 || curSem[i].semncnt != 0) {
		    Sync_Broadcast(&curSem->semLock);
		}
	    }
	    free((char *)curSem);
	    dprintf("semctl(IPC_RMID) done\n");
	    break;
    }
    if (cmd == SETVAL || cmd == SETALL || cmd == IPC_SET) {
	Timer_GetRealTimeOfDay(&timeVal, (int *)NIL, (Boolean *)NIL);
	semPtr->sem_ctime = timeVal.seconds;
    }
    UNLOCK_MONITOR;
    status = Vm_CopyOut(sizeof(int), (Address) &retVal, (Address) retValOut);
    if (status != SUCCESS) {
	dprintf("Copy-out fault\n");
	return EFAULT;
    }
    return SUCCESS;
}

/*
 *----------------------------------------------------------------------
 *
 * Sync_SemgetStub --
 *
 *	Gets a set of semaphores.
 *
 * Results:
 *	Returns result of the function.
 *
 * Side effects:
 *	May create semaphore datastructures.
 *
 *----------------------------------------------------------------------
 */
ReturnStatus
Sync_SemgetStub(key, nsems, semflg, retValOut)
    long	key;		/* Semaphore key. */
    int		nsems;		/* Number of semaphores requested. */
    int		semflg;		/* Creation flag. */
    int		*retValOut;	/* Result of operation. */
    
{
    int			i;
    Sync_SysVSem	*semPtr;
    Proc_ControlBlock	*procPtr;
    int			uid;
    Time		timeVal;
    int			retVal;
    ReturnStatus	status;
    Fs_ProcessState	*fsPtr;

    dprintf("call: semget(%d, %d, %d)\n", key, nsems, semflg);
    Sync_SemInit();
    if (nsems < 0 || nsems > SEMMSL) {
	UNLOCK_MONITOR;
	dprintf("Invalid number of semaphores: %d\n",nsems);
	return EINVAL;
    }
    LOCK_MONITOR;
    if (key != IPC_PRIVATE) {
	/*
	 * Check if the key is in the semaphore table.
	 */
	semPtr = NULL;
	for (i=0;i<SEMMNI;i++) {
	    if ((long)semTable[i].sem_perm.key == key &&
		    semTable[i].sem_base != (struct sem *)NIL) {
		semPtr = &semTable[i];
		break;
	    }
	}
	if (semPtr == NULL && !(semflg & IPC_CREAT)) {
	    UNLOCK_MONITOR;
	    dprintf("No semaphore %d\n",key);
	    return ENOENT;
	}
    }
    if (key == IPC_PRIVATE || semPtr == NULL) {
	/*
	 * Want to create a new semaphore set.
	 */
	if (nsems == 0) {
	    UNLOCK_MONITOR;
	    dprintf("Can't create 0 semaphores.\n");
	    return EINVAL;
	}
	semPtr = NULL;
	for (i=0;i<SEMMNI;i++) {
	    if (semTable[i].sem_base == (struct sem *)NIL) {
		semPtr = &semTable[i];
		break;
	    }
	}
	if (semPtr == NULL) {
		UNLOCK_MONITOR;
		dprintf("Semaphore table full\n");
		return ENOSPC;
	}
	if (totalSems+nsems > SEMMNS) {
	    UNLOCK_MONITOR;
	    dprintf("Semaphore table full\n");
	    return ENOSPC;
	}
	procPtr=Proc_GetEffectiveProc();
	uid = procPtr->effectiveUserID;
	semPtr->sem_perm.uid = procPtr->effectiveUserID;
	semPtr->sem_perm.cuid = procPtr->effectiveUserID;
	fsPtr = procPtr->fsPtr;
	if (fsPtr->numGroupIDs > 0) {
	    semPtr->sem_perm.gid = fsPtr->groupIDs[0];
	} else {
	    semPtr->sem_perm.gid = DEFAULTGID;
	}
	semPtr->sem_perm.cgid = semPtr->sem_perm.gid;
	semPtr->sem_perm.mode = semflg&0666;
	semPtr->sem_perm.key = key;
	semPtr->sem_base = (struct sem *)malloc(sizeof(struct sem)*nsems);
	Timer_GetRealTimeOfDay(&timeVal, (int *) NIL, (Boolean *) NIL);
	semPtr->sem_nsems = nsems;
	semPtr->sem_otime = 0;
	semPtr->sem_ctime = timeVal.seconds;
	for (i=0;i<nsems;i++) {
	    semPtr->sem_base[i].semval = 0;
	    semPtr->sem_base[i].sempid = 0;
	    semPtr->sem_base[i].semncnt = 0;
	    semPtr->sem_base[i].semzcnt = 0;
	}
    } else {
	if (semPtr->sem_nsems < nsems) {
	    UNLOCK_MONITOR;
	    dprintf("Semaphore exists, but not enough semaphores.\n");
	    return EINVAL;
	}
	if ((semflg & IPC_CREAT) && (semflg & IPC_EXCL)) {
	    UNLOCK_MONITOR;
	    dprintf("Semaphore set already exists\n");
	    return EEXIST;
	}
	if (((semflg & 0333) & semPtr->sem_perm.mode) != (semflg & 0333)) {
	    UNLOCK_MONITOR;
	    dprintf("Permission denied: mode %o\n",semPtr->sem_perm.mode);
	    return EACCES;
	}
    }
    retVal = semPtr->sem_perm.seq*SEMMNI+(semPtr-semTable);
    UNLOCK_MONITOR;
    status = Vm_CopyOut(sizeof(int), (Address) &retVal, (Address) retValOut);
    if (status != SUCCESS) {
	dprintf("Copy-out failure\n");
	return EFAULT;
    }
    dprintf("semget: returns %d\n",retVal);
    return SUCCESS;
}

/*
 *----------------------------------------------------------------------
 *
 * Sync_SemopStub --
 *
 *	Perform semaphore operations on semaphores.
 *
 * Results:
 *	Returns result of the function.
 *
 * Side effects:
 *	Performs a function on the semaphore.
 *
 *----------------------------------------------------------------------
 */
ReturnStatus
Sync_SemopStub(semid, sopsIn, nsops, retValOut)
    int			semid;		/* Semaphore group identifier. */
    struct sembuf	*sopsIn;	/* Operations to perform. */
    int			nsops;		/* Number of operations. */
    int			retValOut;	/* Result of operation. */
    
{
    int			perms;
    Sync_SysVSem	*semPtr;
    ReturnStatus	status;
    int			i,j;
    struct sembuf	sops[SEMOPM];
    int			seq;
    int			semmod[SEMMSL];
    Time		timeVal;
    int			pid = Proc_GetEffectiveProc()->processID;
    struct sem		*curSem;
    int			retVal;
    Boolean		sig;

    dprintf("call: semop(%d, %x, %d)\n", semid, sopsIn, nsops);

    if (nsops <= 0 || nsops > SEMOPM) {
	dprintf("Too many semaphore operations\n");
	return E2BIG;
    }

    Sync_SemInit();

    status = Vm_CopyIn(sizeof(struct sembuf)*nsops, (Address) sopsIn,
	    (Address) sops);
    if (status != SUCCESS) {
	dprintf("Copy-in fault\n");
	return EFAULT;
    }
    LOCK_MONITOR;
    status = Sync_SemStruct(semid, &perms, &semPtr);
    if (status != SUCCESS) {
	UNLOCK_MONITOR;
	return status;
    }
    for (i=0;i<nsops;i++) {
	if (sops[i].sem_num < 0 || sops[i].sem_num >= semPtr->sem_nsems) {
	    UNLOCK_MONITOR;
	    dprintf("Semaphore value too big: #%d = %d, max: %d\n",
		    i, sops[i].sem_num, semPtr->sem_nsems);
	    return EFBIG;
	}
	if ((sops[i].sem_op != 0 && !(perms & SEM_A)) ||
		(sops[i].sem_op == 0 && !(perms & SEM_R))) {
	    UNLOCK_MONITOR;
	    dprintf("Semaphore access denied\n");
	    return EACCES;
	}
	if (sops[i].sem_flg & SEM_UNDO) {
	    printf("semop: SEM_UNDO not implemented\n");
	    UNLOCK_MONITOR;
	    return EINVAL;
	}
    }

retry:

    status = SUCCESS;
    for (i=0;i<nsops;i++) {
	int sem_op = sops[i].sem_op;
	curSem = &semPtr->sem_base[sops[i].sem_num];
	retVal = curSem->semval;
	if (sem_op < 0) {
	    if (curSem->semval >= -sem_op) {
		curSem->semval -= -sem_op;
	    } else {
		break;
	    }
	} else if (sops[i].sem_op > 0) {
	    if (curSem->semval + sem_op > SEMVMX) {
		status = ERANGE;
		break;
	    }
	    curSem->semval += sem_op;
	} else {
	    if (curSem->semval != 0) {
		break;
	    }
	}
    }

    if (i==nsops) {
	/*
	 * Success.
	 */
	for (i=0;i<semPtr->sem_nsems;i++) {
	    semmod[i] = 0;
	}
	for (i=0;i<nsops;i++) {
	    semmod[sops[i].sem_num]++;
	}
	Timer_GetRealTimeOfDay(&timeVal, (int *)NIL, (Boolean *) NIL);
	semPtr->sem_otime = timeVal.seconds;
	for (i=0;i<semPtr->sem_nsems;i++) {
	    if (semmod[i] != 0) {
		curSem = &semPtr->sem_base[i];
		curSem->sempid = pid;
		if ((curSem->semzcnt != 0 && curSem->semval == 0) ||
			(curSem->semncnt != 0 && curSem->semval != 0)) {
		    Sync_Broadcast(&curSem->semLock);
		}
	    }
	}
	UNLOCK_MONITOR;
	status = Vm_CopyOut(sizeof(int), (Address) &retVal,
		(Address) retValOut);
	if (status != SUCCESS) {
	    dprintf("Copy-out fault\n");
	    return EFAULT;
	}
	return SUCCESS;
    } else {
	/*
	 * Failure;
	 */
	/*
	 * Undo any changes.
	 */
	for (j=0;j<i;j++) {
	    semPtr->sem_base[sops[j].sem_num].semval -= sops[j].sem_op;
	}
	if (status != SUCCESS) {
	    UNLOCK_MONITOR;
	    return status;
	}
	/*
	 * Wait if necessary.  Then try again.
	 */
	if (!(sops[i].sem_flg & IPC_NOWAIT)) {
	    if (sops[i].sem_op == 0) {
		curSem->semzcnt++;
	    } else {
		curSem->semncnt++;
	    }
	    seq = semPtr->sem_perm.seq;
	    sig = Sync_Wait(&curSem->semLock, TRUE);
	    if (seq != semPtr->sem_perm.seq) {
		UNLOCK_MONITOR;
		dprintf("Semaphore removed\n");
		return EIDRM;
	    }
	    if (sops[i].sem_op == 0) {
		curSem->semzcnt--;
	    } else {
		curSem->semncnt--;
	    }
	    if (sig) {
		UNLOCK_MONITOR;
		dprintf("Semaphore interrupted\n");
		return EINTR;
	    } else {
		goto retry;
	    }
	} else {
	    UNLOCK_MONITOR;
	    dprintf("Semaphore would block\n");
	    return EAGAIN;
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * Sync_SemStruct --
 *
 *	Get the semaphore structure, given the identifier.
 *	This routine checks the associated permissions.
 *
 * Results:
 *	Returns SUCCESS or error condition.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */
ReturnStatus
Sync_SemStruct(id, perm, retPtr)
    int			id;		/* Semaphore id value. */
    int			*perm;		/* (Return) permissions. */
    Sync_SysVSem	**retPtr;	/* (Return) Semaphore structure. */

{
    Sync_SysVSem	*semPtr;
    Proc_ControlBlock	*procPtr;
    int uid, gid;
    int uperm;
    Fs_ProcessState	*fsPtr;

    if (id < 0) return EINVAL;

    semPtr = &semTable[id%SEMMNI];
    *retPtr = semPtr;
    if (semPtr->sem_perm.seq != id/SEMMNI ||
	    semPtr->sem_base == (struct sem *) NIL ) {
	/*
	 * No such semaphore.
	 */
	dprintf("No semaphore %d\n",id);
	return EINVAL;
    }
    /*
     * Check permissions.
     */
    procPtr=Proc_GetEffectiveProc();
    uid = procPtr->effectiveUserID;
    fsPtr = procPtr->fsPtr;
    if (fsPtr->numGroupIDs > 0) {
	gid = fsPtr->groupIDs[0];
    } else {
	gid = DEFAULTGID;
	printf("Warning: process has no gid\n");
    }
    if (uid == 0) {
	*perm = SEM_A | SEM_R;
	return SUCCESS;
    }
    if (uid == semPtr->sem_perm.uid || uid == semPtr->sem_perm.cuid) {
	uperm = semPtr->sem_perm.mode;
    } else if (gid == semPtr->sem_perm.gid || gid == semPtr->sem_perm.cgid) {
	uperm = semPtr->sem_perm.mode<<3;
    } else {
	uperm = semPtr->sem_perm.mode<<6;
    }
    *perm = uperm & (SEM_A | SEM_R);
    return SUCCESS;
}
