/* @(#)sem.c	1.5 */
/*
**	Inter-Process Communication Semaphore Facility.
*/

#include "sys/types.h"
#include "sys/param.h"
#include "sys/dir.h"
#ifdef	u3b
#include "sys/istk.h"
#endif
#include "sys/map.h"
#include "sys/errno.h"
#include "sys/signal.h"
#include "sys/ipc.h"
#include "sys/sem.h"
#include "sys/user.h"
#include "sys/seg.h"
#include "sys/proc.h"
#include "sys/buf.h"

#ifdef pdp11
#define MOVE	sempimove
#else
#define	MOVE	iomove
#endif

extern struct semid_ds	sema[];		/* semaphore data structures */
extern struct sem	sem[];		/* semaphores */
extern struct map	semmap[];	/* sem allocation map */
extern struct sem_undo	*sem_undo[];	/* undo table pointers */
extern struct sem_undo	semu[];		/* operation adjust on exit table */
extern struct seminfo seminfo;		/* param information structure */
extern union {
	short			semvals[1]; /* set semaphore values */
	struct	semid_ds	ds;	/* set permission values */
	struct sembuf		semops[1];	/* operation holding area */
} semtmp;
struct sem_undo	*semunp;		/* ptr to head of undo chain */
struct sem_undo	*semfup;		/* ptr to head of free undo chain */

extern time_t	time;			/* system idea of date */

struct ipc_perm		*ipcget();
struct semid_ds		*semconv();

/*
**	semaoe - Create or update adjust on exit entry.
*/

semaoe(val, id, num)
short	val,	/* operation value to be adjusted on exit */
	num;	/* semaphore # */
int	id;	/* semid */
{
	register struct undo		*uup,	/* ptr to entry to update */
					*uup2;	/* ptr to move entry */
	register struct sem_undo	*up,	/* ptr to process undo struct */
					*up2;	/* ptr to undo list */
	register int			i,	/* loop control */
					found;	/* matching entry found flag */

	if(val == 0)
		return(0);
	if(val > seminfo.semaem || val < -seminfo.semaem) {
		u.u_error = ERANGE;
		return(1);
	}
	if((up = sem_undo[u.u_procp - proc]) == NULL)
		if (up = semfup) {
			semfup = up->un_np;
			up->un_np = NULL;
			sem_undo[u.u_procp - proc] = up;
		} else {
			u.u_error = ENOSPC;
			return(1);
		}
	for(uup = up->un_ent, found = i = 0;i < up->un_cnt;i++) {
		if(uup->un_id < id || (uup->un_id == id && uup->un_num < num)) {
			uup++;
			continue;
		}
		if(uup->un_id == id && uup->un_num == num)
			found = 1;
		break;
	}
	if(!found) {
		if(up->un_cnt >= seminfo.semume) {
			u.u_error = EINVAL;
			return(1);
		}
		if(up->un_cnt == 0) {
			up->un_np = semunp;
			semunp = up;
		}
		uup2 = &up->un_ent[up->un_cnt++];
		while(uup2-- > uup)
			*(uup2 + 1) = *uup2;
		uup->un_id = id;
		uup->un_num = num;
		uup->un_aoe = -val;
		return(0);
	}
	uup->un_aoe -= val;
	if(uup->un_aoe > seminfo.semaem || uup->un_aoe < -seminfo.semaem) {
		u.u_error = ERANGE;
		uup->un_aoe += val;
		return(1);
	}
	if(uup->un_aoe == 0) {
		uup2 = &up->un_ent[--(up->un_cnt)];
		while(uup++ < uup2)
			*(uup - 1) = *uup;
		if(up->un_cnt == 0) {

			/* Remove process from undo list. */
			if(semunp == up)
				semunp = up->un_np;
			else
				for(up2 = semunp;up2 != NULL;up2 = up2->un_np)
					if(up2->un_np == up) {
						up2->un_np = up->un_np;
						break;
					}
			up->un_np = NULL;
		}
	}
	return(0);
}

/*
**	semconv - Convert user supplied semid into a ptr to the associated
**		semaphore header.
*/

struct semid_ds *
semconv(s)
register int	s;	/* semid */
{
	register struct semid_ds	*sp;	/* ptr to associated header */

	sp = &sema[s % seminfo.semmni];
	if((sp->sem_perm.mode & IPC_ALLOC) == 0 ||
		s / seminfo.semmni != sp->sem_perm.seq) {
		u.u_error = EINVAL;
		return(NULL);
	}
	return(sp);
}

/*
**	semctl - Semctl system call.
*/

semctl()
{
	register struct a {
		int	semid;
		uint	semnum;
		int	cmd;
		int	arg;
	}	*uap = (struct a *)u.u_ap;
	register struct	semid_ds	*sp;	/* ptr to semaphore header */
	register struct sem		*p;	/* ptr to semaphore */
	register int			i;	/* loop control */
	register struct user *up;

	if((sp = semconv(uap->semid)) == NULL)
		return;
	up = &u;
	up->u_rval1 = 0;
	switch(uap->cmd) {

	/* Remove semaphore set. */
	case IPC_RMID:
		if(up->u_uid != sp->sem_perm.uid && up->u_uid != sp->sem_perm.cuid
			&& !suser())
			return;
		semunrm(uap->semid, 0, sp->sem_nsems);
		for(i = sp->sem_nsems, p = sp->sem_base;i--;p++) {
			p->semval = p->sempid = 0;
			if(p->semncnt) {
				wakeup((caddr_t)&p->semncnt);
				p->semncnt = 0;
			}
			if(p->semzcnt) {
				wakeup((caddr_t)&p->semzcnt);
				p->semzcnt = 0;
			}
		}
		mfree(semmap, (int)sp->sem_nsems, (sp->sem_base - sem) + 1);
		if(uap->semid + seminfo.semmni < 0)
			sp->sem_perm.seq = 0;
		else
			sp->sem_perm.seq++;
		sp->sem_perm.mode = 0;
		return;

	/* Set ownership and permissions. */
	case IPC_SET:
		if(up->u_uid != sp->sem_perm.uid && up->u_uid != sp->sem_perm.cuid
			 && !suser())
			return;
		if(copyin((caddr_t)uap->arg, (caddr_t)&semtmp.ds, sizeof(semtmp.ds))) {
			up->u_error = EFAULT;
			return;
		}
		sp->sem_perm.uid = semtmp.ds.sem_perm.uid;
		sp->sem_perm.gid = semtmp.ds.sem_perm.gid;
		sp->sem_perm.mode = semtmp.ds.sem_perm.mode & 0777 | IPC_ALLOC;
		sp->sem_ctime = time;
		return;

	/* Get semaphore data structure. */
	case IPC_STAT:
		if(ipcaccess(&sp->sem_perm, SEM_R))
			return;
		if(copyout((caddr_t)sp, (caddr_t)uap->arg, sizeof(*sp))) {
			up->u_error = EFAULT;
			return;
		}
		return;

	/* Get # of processes sleeping for greater semval. */
	case GETNCNT:
		if(ipcaccess(&sp->sem_perm, SEM_R))
			return;
		if(uap->semnum >= sp->sem_nsems) {
			up->u_error = EINVAL;
			return;
		}
		up->u_rval1 = (sp->sem_base + uap->semnum)->semncnt;
		return;

	/* Get pid of last process to operate on semaphore. */
	case GETPID:
		if(ipcaccess(&sp->sem_perm, SEM_R))
			return;
		if(uap->semnum >= sp->sem_nsems) {
			up->u_error = EINVAL;
			return;
		}
		up->u_rval1 = (sp->sem_base + uap->semnum)->sempid;
		return;

	/* Get semval of one semaphore. */
	case GETVAL:
		if(ipcaccess(&sp->sem_perm, SEM_R))
			return;
		if(uap->semnum >= sp->sem_nsems) {
			up->u_error = EINVAL;
			return;
		}
		up->u_rval1 = (sp->sem_base + uap->semnum)->semval;
		return;

	/* Get all semvals in set. */
	case GETALL:
		if(ipcaccess(&sp->sem_perm, SEM_R))
			return;
		up->u_base = (caddr_t)uap->arg;
		up->u_offset = 0;
		up->u_segflg = 0;
		for(i = sp->sem_nsems, p = sp->sem_base;i--;p++) {
			MOVE((caddr_t)&p->semval, sizeof(p->semval), B_READ);
			if(up->u_error)
				return;
		}
		return;

	/* Get # of processes sleeping for semval to become zero. */
	case GETZCNT:
		if(ipcaccess(&sp->sem_perm, SEM_R))
			return;
		if(uap->semnum >= sp->sem_nsems) {
			up->u_error = EINVAL;
			return;
		}
		up->u_rval1 = (sp->sem_base + uap->semnum)->semzcnt;
		return;

	/* Set semval of one semaphore. */
	case SETVAL:
		if(ipcaccess(&sp->sem_perm, SEM_A))
			return;
		if(uap->semnum >= sp->sem_nsems) {
			up->u_error = EINVAL;
			return;
		}
		if((unsigned)uap->arg > seminfo.semvmx) {
			up->u_error = ERANGE;
			return;
		}
		if((p = sp->sem_base + uap->semnum)->semval = uap->arg) {
			if(p->semncnt) {
				p->semncnt = 0;
				wakeup((caddr_t)&p->semncnt);
			}
		} else
			if(p->semzcnt) {
				p->semzcnt = 0;
				wakeup((caddr_t)&p->semzcnt);
			}
		p->sempid = up->u_procp->p_pid;
		semunrm(uap->semid, uap->semnum, uap->semnum);
		return;

	/* Set semvals of all semaphores in set. */
	case SETALL:
		if(ipcaccess(&sp->sem_perm, SEM_A))
			return;
		up->u_base = (caddr_t)uap->arg;
		up->u_offset = 0;
		up->u_segflg = 0;
		MOVE((caddr_t)semtmp.semvals,
			(int)(sizeof(semtmp.semvals[0]) * sp->sem_nsems),
			B_WRITE);
		if(up->u_error)
			return;
		for(i = 0;i < sp->sem_nsems;)
			if(semtmp.semvals[i++] > seminfo.semvmx) {
				up->u_error = ERANGE;
				return;
			}
		semunrm(uap->semid, 0, sp->sem_nsems);
		for(i = 0, p = sp->sem_base;i < sp->sem_nsems;
			(p++)->sempid = up->u_procp->p_pid) {
			if(p->semval = semtmp.semvals[i++]) {
				if(p->semncnt) {
					p->semncnt = 0;
					wakeup((caddr_t)&p->semncnt);
				}
			} else
				if(p->semzcnt) {
					p->semzcnt = 0;
					wakeup((caddr_t)&p->semzcnt);
				}
		}
		return;
	default:
		up->u_error = EINVAL;
		return;
	}
}

/*
**	semexit - Called by exit(sys1.c) to clean up on process exit.
*/

semexit()
{
	register struct sem_undo	*up,	/* process undo struct ptr */
					*p;	/* undo struct ptr */
	register struct semid_ds	*sp;	/* semid being undone ptr */
	register int			i;	/* loop control */
	register long			v;	/* adjusted value */
	register struct sem		*semp;	/* semaphore ptr */

	if((up = sem_undo[u.u_procp - proc]) == NULL)
		return;
	if(up->un_cnt == 0)
		goto cleanup;
	for(i = up->un_cnt;i--;) {
		if((sp = semconv(up->un_ent[i].un_id)) == NULL)
			continue;
		v = (long)(semp = sp->sem_base + up->un_ent[i].un_num)->semval +
			up->un_ent[i].un_aoe;
		if(v < 0 || v > seminfo.semvmx)
			continue;
		semp->semval = v;
		if(v == 0 && semp->semzcnt) {
			semp->semzcnt = 0;
			wakeup((caddr_t)&semp->semzcnt);
		}
		if(up->un_ent[i].un_aoe > 0 && semp->semncnt) {
			semp->semncnt = 0;
			wakeup((caddr_t)&semp->semncnt);
		}
	}
	up->un_cnt = 0;
	if(semunp == up)
		semunp = up->un_np;
	else
		for(p = semunp;p != NULL;p = p->un_np)
			if(p->un_np == up) {
				p->un_np = up->un_np;
				break;
			}
cleanup:
	up->un_np = semfup;
	semfup = up;
	sem_undo[u.u_procp - proc] = NULL;
}

/*
**	semget - Semget system call.
*/

semget()
{
	register struct a {
		key_t	key;
		int	nsems;
		int	semflg;
	}	*uap = (struct a *)u.u_ap;
	register struct semid_ds	*sp;	/* semaphore header ptr */
	register int			i;	/* temp */
	int				s;	/* ipcget status return */

	if((sp = (struct semid_ds *)
		ipcget(uap->key, uap->semflg, (struct ipc_perm *)sema,
		seminfo.semmni, sizeof(*sp), &s)) == NULL)
		return;
	if(s) {

		/* This is a new semaphore set.  Finish initialization. */
		if(uap->nsems <= 0 || uap->nsems > seminfo.semmsl) {
			u.u_error = EINVAL;
			sp->sem_perm.mode = 0;
			return;
		}
		if((i = malloc(semmap, uap->nsems)) == NULL) {
			u.u_error = ENOSPC;
			sp->sem_perm.mode = 0;
			return;
		}
		sp->sem_base = sem + (i - 1);
		sp->sem_nsems = uap->nsems;
		sp->sem_ctime = time;
	} else
		if(uap->nsems && sp->sem_nsems < uap->nsems) {
			u.u_error = EINVAL;
			return;
		}
	u.u_rval1 = sp->sem_perm.seq * seminfo.semmni + (sp - sema);
}

/*
**	seminit - Called by main(main.c) to initialize the semaphore map.
*/

seminit()
{
	register i;

	mapinit(semmap, seminfo.semmap);
	mfree(semmap, seminfo.semmns, 1);

	semfup = semu;
	for (i = 0; i < seminfo.semmnu - 1; i++) {
		semfup->un_np = (struct sem_undo *)((uint)semfup+seminfo.semusz);
		semfup = semfup->un_np;
	}
	semfup->un_np = NULL;
	semfup = semu;
}

/*
**	semop - Semop system call.
*/

semop()
{
	register struct a {
		int		semid;
		struct sembuf	*sops;
		uint		nsops;
	}	*uap = (struct a *)u.u_ap;
	register struct sembuf		*op;	/* ptr to operation */
	register int			i;	/* loop control */
	register struct semid_ds	*sp;	/* ptr to associated header */
	register struct sem		*semp;	/* ptr to semaphore */
	int	again;

	if((sp = semconv(uap->semid)) == NULL)
		return;
	if(uap->nsops > seminfo.semopm) {
		u.u_error = E2BIG;
		return;
	}
	u.u_base = (caddr_t)uap->sops;
	u.u_offset = 0;
	u.u_segflg = 0;
	MOVE((caddr_t)semtmp.semops, (int)(uap->nsops * sizeof(*op)), B_WRITE);
	if(u.u_error)
		return;

	/* Verify that sem #s are in range and permissions are granted. */
	for(i = 0, op = semtmp.semops;i++ < uap->nsops;op++) {
		if(ipcaccess(&sp->sem_perm, (ushort)(op->sem_op?SEM_A:SEM_R)))
			return;
		if(op->sem_num >= sp->sem_nsems) {
			u.u_error = EFBIG;
			return;
		}
	}
	again = 0;
check:
	/* Loop waiting for the operations to be satisified atomically. */
	/* Actually, do the operations and undo them if a wait is needed
		or an error is detected. */
	if (again) {
		/* Verify that the semaphores haven't been removed. */
		if(semconv(uap->semid) == NULL) {
			u.u_error = EIDRM;
			return;
		}
		/* copy in user operation list after sleep */
		u.u_base = (caddr_t)uap->sops;
		u.u_offset = 0;
		u.u_segflg = 0;
		MOVE((caddr_t)semtmp.semops,
			(int)(uap->nsops * sizeof(*op)), B_WRITE);
		if(u.u_error)
			return;
	}
	again = 1;

	for(i = 0, op = semtmp.semops;i < uap->nsops;i++, op++) {
		semp = sp->sem_base + op->sem_num;
		if(op->sem_op > 0) {
			if(op->sem_op + (long)semp->semval > seminfo.semvmx ||
				(op->sem_flg & SEM_UNDO &&
				semaoe(op->sem_op, uap->semid, (short)op->sem_num))) {
				if(u.u_error == 0)
					u.u_error = ERANGE;
				if(i)
					semundo(semtmp.semops, i, uap->semid, sp);
				return;
			}
			semp->semval += op->sem_op;
			if(semp->semncnt) {
				semp->semncnt = 0;
				wakeup((caddr_t)&semp->semncnt);
			}
			continue;
		}
		if(op->sem_op < 0) {
			if(semp->semval >= -op->sem_op) {
				if(op->sem_flg & SEM_UNDO &&
					semaoe(op->sem_op, uap->semid, (short)op->sem_num)) {
					if(i)
						semundo(semtmp.semops, i, uap->semid, sp);
					return;
				}
				semp->semval += op->sem_op;
				if(semp->semval == 0 && semp->semzcnt) {
					semp->semzcnt = 0;
					wakeup((caddr_t)&semp->semzcnt);
				}
				continue;
			}
			if(i)
				semundo(semtmp.semops, i, uap->semid, sp);
			if(op->sem_flg & IPC_NOWAIT) {
				u.u_error = EAGAIN;
				return;
			}
			semp->semncnt++;
			if(sleep((caddr_t)&semp->semncnt, PCATCH | PSEMN)) {
				if((semp->semncnt)-- <= 1) {
					semp->semncnt = 0;
					wakeup((caddr_t)&semp->semncnt);
				}
				u.u_error = EINTR;
				return;
			}
			goto check;
		}
		if(semp->semval) {
			if(i)
				semundo(semtmp.semops, i, uap->semid, sp);
			if(op->sem_flg & IPC_NOWAIT) {
				u.u_error = EAGAIN;
				return;
			}
			semp->semzcnt++;
			if(sleep((caddr_t)&semp->semzcnt, PCATCH | PSEMZ)) {
				if((semp->semzcnt)-- <= 1) {
					semp->semzcnt = 0;
					wakeup((caddr_t)&semp->semzcnt);
				}
				u.u_error = EINTR;
				return;
			}
			goto check;
		}
	}

	/* All operations succeeded.  Update sempid for accessed semaphores. */
	for(i = 0, op = semtmp.semops;i++ < uap->nsops;
		(sp->sem_base + (op++)->sem_num)->sempid = u.u_procp->p_pid);
	sp->sem_otime = time;
	u.u_rval1 = 0;
}

#ifdef pdp11
/*
**	sempimove - PDP 11 pimove interface for possibly large copies.
*/

sempimove(base, count, mode)
paddr_t			base;	/* base address */
register unsigned	count;	/* byte count */
int			mode;	/* transfer mode */
{
	register unsigned	tcount;	/* current transfer count */

	while(u.u_error == 0 && count) {
		tcount = count > 8064 ? 8064 : count;
		pimove(base, tcount, mode);
		base += tcount;
		count -= tcount;
	}
}
#endif

/*
**	semsys - System entry point for semctl, semget, and semop system calls.
*/

semsys()
{
	int	semctl(),
		semget(),
		semop();
	static int	(*calls[])() = {semctl, semget, semop};
	register struct a {
		uint	id;	/* function code id */
	}	*uap = (struct a *)u.u_ap;

	if(uap->id > 2) {
		u.u_error = EINVAL;
		return;
	}
	u.u_ap = &u.u_arg[1];
	(*calls[uap->id])();
}

/*
**	semundo - Undo work done up to finding an operation that can't be done.
*/

semundo(op, n, id, sp)
register struct sembuf		*op;	/* first operation that was done ptr */
register int			n,	/* # of operations that were done */
				id;	/* semaphore id */
register struct semid_ds	*sp;	/* semaphore data structure ptr */
{
	register struct sem	*semp;	/* semaphore ptr */

	for(op += n - 1;n--;op--) {
		if(op->sem_op == 0)
			continue;
		semp = sp->sem_base + op->sem_num;
		semp->semval -= op->sem_op;
		if(op->sem_flg & SEM_UNDO)
			(void) semaoe(-op->sem_op, id, (short)op->sem_num);
	}
}

/*
**	semunrm - Undo entry remover.
**
**	This routine is called to clear all undo entries for a set of semaphores
**	that are being removed from the system or are being reset by SETVAL or
**	SETVALS commands to semctl.
*/

semunrm(id, low, high)
int	id;	/* semid */
ushort	low,	/* lowest semaphore being changed */
	high;	/* highest semaphore being changed */
{
	register struct sem_undo	*pp,	/* ptr to predecessor to p */
					*p;	/* ptr to current entry */
	register struct undo		*up;	/* ptr to undo entry */
	register int			i,	/* loop control */
					j;	/* loop control */

	pp = NULL;
	p = semunp;
	while(p != NULL) {

		/* Search through current structure for matching entries. */
		for(up = p->un_ent, i = 0;i < p->un_cnt;) {
			if(id < up->un_id)
				break;
			if(id > up->un_id || low > up->un_num) {
				up++;
				i++;
				continue;
			}
			if(high < up->un_num)
				break;
			for(j = i;++j < p->un_cnt;
				p->un_ent[j - 1] = p->un_ent[j]);
			p->un_cnt--;
		}

		/* Reset pointers for next round. */
		if(p->un_cnt == 0)

			/* Remove from linked list. */
			if(pp == NULL) {
				semunp = p->un_np;
				p->un_np = NULL;
				p = semunp;
			} else {
				pp->un_np = p->un_np;
				p->un_np = NULL;
				p = pp->un_np;
			}
		else {
			pp = p;
			p = p->un_np;
		}
	}
}
