#include "../head/param.h"
#include "../head/reg.h"
#include "../head/ipcomm.h"
#include "../head/ipcommx.h"
#include "../head/user.h"
#include "../head/userx.h"
#include "../head/proc.h"

/*
 * Semaphore System Call
 */

lflags()
{	register struct semi4s *sp, **p;
	register int n;
	if((n = u.u_arg[1]) >= NLOCK) {
		u.u_error = EINVAL;
		return;
	}
	if(n < 0) sp = NULL;
	else sp = &semi4s[n];
	switch(u.u_arg[0].lobyte) {
		default:
			u.u_error = EINVAL;
			return;
		case ASEMI:
			if(sp == NULL) {	/*dynamic alloc*/
				for(sp = &semi4s[0]; sp < &semi4s[NLOCK]; sp++)
					if(sp->sem_cnt == 0) {
						alocsem(sp);
						return;
					}
				u.u_error = ETABLE;
			}
			else	if(usemf(sp) == NULL) alocsem(sp);
				else u.u_error = ENOALOC;		/*already alloc*/
			return;
		case FSEMI:
			if(sp == NULL) {
				u.u_error = ENOALOC;
				for(p = &u.u_semi4[0];p < &u.u_semi4[NOLOCK]; p++)
					if(*p) {
						freesem(p);
						u.u_error = 0;
					}
			}
			else	if((p = usemf(sp)) != NULL) freesem(p);
				else u.u_error = ENOALOC;
			return;
		case LOCK:
		case UNLOCK:
		case TLOCK:
			n = S_LU;
			break;
		case P:
		case V:
		case TEST:
			n = S_PV;
	}
	/*ensure semi4 is alloc for purpose intended*/
	if(sp == NULL) {
		u.u_error = EINVAL;
		return;
	}
	if(usemf(sp) == NULL || (sp->sem_flag & S_TYPE) != n) {
		u.u_error = ENOALOC;
		return;
	}
	u.u_ar0[R0] = sp->sem_lock;
	switch(u.u_arg[0].lobyte) {
		case LOCK:
			if(sp->sem_lock == u.u_procp->p_pid) return;
			while(sp->sem_lock != 0) {
				sp->sem_flag =| IP_WANTED;
				sleep(sp, PSEMI4);
			}
			u.u_ar0[R0] = sp->sem_lock;
			sp->sem_lock = u.u_procp->p_pid;
			return;
		case UNLOCK:
			if(sp->sem_lock != u.u_procp->p_pid)
				u.u_error = EFUNC;
			else {
				sp->sem_lock = 0;
				if(sp->sem_flag & IP_WANTED) {
					sp->sem_flag =& ~IP_WANTED;
					wakeup(sp);
				}
			}
			return;
		case TLOCK:
			if(sp->sem_lock == 0)
				sp->sem_lock = u.u_procp->p_pid;
			return;
		case P:
			while(sp->sem_lock <= 0) {
				sp->sem_flag =| IP_WANTED;
				sleep(sp, PSEMI4);
			}
			u.u_ar0[R0] = sp->sem_lock;
			sp->sem_lock--;
			return;
		case V:
			sp->sem_lock++;
			if(sp->sem_flag & IP_WANTED) {
				sp->sem_flag =& ~IP_WANTED;
				wakeup(sp);
			}
			return;
		case TEST:
			if(sp->sem_lock > 0) sp->sem_lock--;
	}
}

/*
 * Free a semaphore
 */

freesem(up)
	int **up;
{	register struct semi4s *rsp;
	rsp = *up;
	/* unlock l-u semi4 if necessary */
	if(((rsp->sem_flag & S_TYPE) == S_LU) &&
	  (rsp->sem_lock == u.u_procp->p_pid)) {
		rsp->sem_lock = 0;
		if(rsp->sem_flag & IP_WANTED) {
			rsp->sem_flag =& ~IP_WANTED;
			wakeup(rsp);
		}
	}
	rsp->sem_cnt--;
	*up = NULL;
}

/*
 * See if the semaphore "sp" is already allocated
 * by a process. Note that NULL is a suitable argument.
 */

usemf(sp)
	struct semi4s *sp;
{	register struct semi4s *rsp, **p;
	rsp = sp;
	for(p = &u.u_semi4[0]; p < &u.u_semi4[NOLOCK]; p++)
		if(rsp == *p) return(p);
	return(NULL);
}

/*
 * Allocate the semaphore "sp".
 */

alocsem(sp)
	struct semi4s *sp;
{	register struct semi4s *rsp;
	struct semi4s **p;
	register int type, scope;
	if((p = usemf(NULL)) == NULL) {
		u.u_error = ETABLE;
		return;
	}
	rsp = sp;
	switch(scope = u.u_arg[0].hibyte) {
		default:
			u.u_error = EINVAL;
			return;
		case 0:
		case 1:
		case 2:
			type = S_LU;
			break;
		case 3:
		case 4:
		case 5:
			type = S_PV;
			scope =- 3;
	}
	if(rsp->sem_cnt == 0) {
		rsp->sem_flag = type | scope;
		rsp->sem_lock = 0;
		rsp->sem_id = ipidtyp(scope);
	}
	else	if((rsp->sem_flag & S_TYPE) != type)
			u.u_error = ENOALOC;
		else ipacces(&rsp->sem_flag);
	if(u.u_error) return;
	rsp->sem_cnt++;
	*p = rsp;
	u.u_ar0[R0] = rsp - &semi4s[0];
}

/*
 * Verify that a process has permission to access an
 * interprocess communication resource.
 */

ipacces(p)
	struct ipaword *p;
{	register int scope, id;
	register struct ipaword *rp;
	rp = p;
	scope = rp->ip_flag & IP_PERM;
	if(u.u_uid == 0 || scope == IP_ANY) return(1);
	id = rp->ip_id & 0377;
	if(id == ipidtyp(scope)) return(1);
	u.u_error = EACCES;
	return(0);
}

/*
 * Determine the appropriate id to to used
 * in checking acces permission.
 */

ipidtyp(scope)
{	switch(scope) {
		case IP_ANY:	return(0);
		case IP_UID:	return(u.u_uid&0377);
		case IP_GID:	return(u.u_gid&0377);
	}
	u.u_error = EINVAL;
	return(0);
}
