#define __USE_MISC

#include <minix/vm.h>

#include "inc.h"

struct waiting {
	endpoint_t who;			/* who is waiting */
	int val;			/* value he/she is waiting for */
};

struct semaphore {
	unsigned short semval;		/* semaphore value */
	unsigned short semzcnt;		/* # waiting for zero */
	unsigned short semncnt;		/* # waiting for increase */
	struct waiting *zlist;		/* process waiting for zero */
	struct waiting *nlist;		/* process waiting for increase */
	pid_t sempid;			/* process that did last op */
};

struct sem_struct {
	key_t key;
	int id;
	struct semid_ds semid_ds;
	struct semaphore sems[SEMMSL];
};

PRIVATE struct sem_struct sem_list[SEMMNI];
PRIVATE int sem_list_nr = 0;

PRIVATE struct sem_struct *sem_find_key(key_t key)
{
	int i;
	if (key == IPC_PRIVATE)
		return NULL;
	for (i = 0; i < sem_list_nr; i++)
		if (sem_list[i].key == key)
			return sem_list+i;
	return NULL;
}

PRIVATE struct sem_struct *sem_find_id(int id)
{
	int i;
	for (i = 0; i < sem_list_nr; i++)
		if (sem_list[i].id == id)
			return sem_list+i;
	return NULL;
}

/*===========================================================================*
 *				do_semget		     		     *
 *===========================================================================*/
PUBLIC int do_semget(message *m)
{
	key_t key;
	int nsems, flag, id;
	struct sem_struct *sem;

	key = m->SEMGET_KEY;
	nsems = m->SEMGET_NR;
	flag = m->SEMGET_FLAG;

	if ((sem = sem_find_key(key))) {
		if ((flag & IPC_CREAT) && (flag & IPC_EXCL))
			return EEXIST;
		if (!check_perm(&sem->semid_ds.sem_perm, who_e, flag))
			return EACCES;
		if (nsems > sem->semid_ds.sem_nsems)
			return EINVAL;
		id = sem->id;
	} else {
		if (!(flag & IPC_CREAT))
			return ENOENT;
		if (nsems < 0 || nsems >= SEMMSL)
			return EINVAL;
		if (sem_list_nr == SEMMNI)
			return ENOSPC;

		/* create a new semaphore set */
		sem = &sem_list[sem_list_nr];
		memset(sem, 0, sizeof(struct sem_struct));
		sem->semid_ds.sem_perm.cuid =
			sem->semid_ds.sem_perm.uid = getnuid(who_e);
		sem->semid_ds.sem_perm.cgid =
			sem->semid_ds.sem_perm.gid = getngid(who_e);
		sem->semid_ds.sem_perm.mode = flag & 0777;
		sem->semid_ds.sem_nsems = nsems;
		sem->semid_ds.sem_otime = 0;
		sem->semid_ds.sem_ctime = time(NULL);
		sem->id = id = identifier++;
		sem->key = key;

		sem_list_nr++;
	}

	m->SEMGET_RETID = id;
	return OK;
}

PRIVATE void send_message_to_process(endpoint_t who, int ret, int ignore)
{
	message m;
	int r;

	m.m_type = ret;
	r = sendnb(who, &m);
	if (r != OK && !ignore)
		printf("IPC send error!\n");
}

PRIVATE void remove_semaphore(struct sem_struct *sem)
{
	int i, nr;

	nr = sem->semid_ds.sem_nsems;

	for (i = 0; i < nr; i++) {
		if (sem->sems[i].zlist)
			free(sem->sems[i].zlist);
		if (sem->sems[i].nlist)
			free(sem->sems[i].nlist);
	}

	for (i = 0; i < sem_list_nr; i++) {
		if (&sem_list[i] == sem)
			break;
	}

	if (i < sem_list_nr && --sem_list_nr != i)
		sem_list[i] = sem_list[sem_list_nr];
}

PRIVATE void show_semaphore(void)
{
	int i, j, k;

	for (i = 0; i < sem_list_nr; i++) {
		int nr = sem_list[i].semid_ds.sem_nsems;

		printf("===== [%d] =====\n", i);
		for (j = 0; j < nr; j++) {
			struct semaphore *semaphore = &sem_list[i].sems[j];

			if (!semaphore->semzcnt && !semaphore->semncnt)
				continue;

			printf("  (%d): ", semaphore->semval);
			if (semaphore->semzcnt) {
				printf("zero(");
				for (k = 0; k < semaphore->semzcnt; k++)
					printf("%d,", semaphore->zlist[k].who);
				printf(")    ");
			}
			if (semaphore->semncnt) {
				printf("incr(");
				for (k = 0; k < semaphore->semncnt; k++)
					printf("%d-%d,",
						semaphore->nlist[k].who, semaphore->nlist[k].val);
				printf(")");
			}
			printf("\n");
		}
	}
	printf("\n");
}

PRIVATE void remove_process(endpoint_t pt)
{
	int i;

	for (i = 0; i < sem_list_nr; i++) {
		struct sem_struct *sem = &sem_list[i];
		int nr = sem->semid_ds.sem_nsems;
		int j;

		for (j = 0; j < nr; j++) {
			struct semaphore *semaphore = &sem->sems[j];
			int k;

			for (k = 0; k < semaphore->semzcnt; k++) {
				endpoint_t who_waiting = semaphore->zlist[k].who;

				if (who_waiting == pt) {
					/* remove this slot first */
					memmove(semaphore->zlist+k, semaphore->zlist+k+1,
						sizeof(struct waiting) * (semaphore->semzcnt-k-1));
					--semaphore->semzcnt;
					/* then send message to the process */
					send_message_to_process(who_waiting, EINTR, 1);

					break;
				}
			}

			for (k = 0; k < semaphore->semncnt; k++) {
				endpoint_t who_waiting = semaphore->nlist[k].who;

				if (who_waiting == pt) {
					/* remove it first */
					memmove(semaphore->nlist+k, semaphore->nlist+k+1,
						sizeof(struct waiting) * (semaphore->semncnt-k-1));
					--semaphore->semncnt;
					/* send the message to the process */
					send_message_to_process(who_waiting, EINTR, 1);

					break;
				}
			}
		}
	}
}

PRIVATE void update_one_semaphore(struct sem_struct *sem, int is_remove)
{
	int i, j, nr;
	struct semaphore *semaphore;
	endpoint_t who;

	nr = sem->semid_ds.sem_nsems;

	if (is_remove) {
		for (i = 0; i < nr; i++) {
			semaphore = &sem->sems[i];

			for (j = 0; j < semaphore->semzcnt; j++)
				send_message_to_process(semaphore->zlist[j].who, EIDRM, 0);
			for (j = 0; j < semaphore->semncnt; j++)
				send_message_to_process(semaphore->nlist[j].who, EIDRM, 0);
		}

		remove_semaphore(sem);
		return;
	}

	for (i = 0; i < nr; i++) {
		semaphore = &sem->sems[i];

		if (semaphore->zlist && !semaphore->semval) {
			/* choose one process, policy: FIFO. */
			who = semaphore->zlist[0].who;

			memmove(semaphore->zlist, semaphore->zlist+1,
				sizeof(struct waiting) * (semaphore->semzcnt-1));
			--semaphore->semzcnt;

			send_message_to_process(who, OK, 0);
		}

		if (semaphore->nlist) {
			for (j = 0; j < semaphore->semncnt; j++) {
				if (semaphore->nlist[j].val <= semaphore->semval) {
					semaphore->semval -= semaphore->nlist[j].val;
					who = semaphore->nlist[j].who;

					memmove(semaphore->nlist+j, semaphore->nlist+j+1,
						sizeof(struct waiting) * (semaphore->semncnt-j-1));
					--semaphore->semncnt;

					send_message_to_process(who, OK, 0);

					/* choose only one process */
					break;
				}
			}
		}
	}
}

PRIVATE void update_semaphores(void)
{
	int i;

	for (i = 0; i < sem_list_nr; i++)
		update_one_semaphore(sem_list+i, 0 /* not remove */);
}

/*===========================================================================*
 *				do_semctl		     		     *
 *===========================================================================*/
PUBLIC int do_semctl(message *m)
{
	int r, i;
	long opt;
	uid_t uid;
	int id, num, cmd, val;
	unsigned short *buf;
	struct semid_ds *ds, tmp_ds;
	struct sem_struct *sem;
	struct semaphore *semaphore;

	id = m->SEMCTL_ID;
	num = m->SEMCTL_NUM;
	cmd = m->SEMCTL_CMD;

	if (cmd == IPC_STAT || cmd == IPC_SET || cmd == IPC_INFO ||
		cmd == SEM_INFO || cmd == SEM_STAT || cmd == GETALL ||
		cmd == SETALL || cmd == SETVAL)
		opt = m->SEMCTL_OPT;

	if (!(sem = sem_find_id(id))) {
		printf("IPC: not found %d\n", id);
		return EINVAL;
	}

	/* IPC_SET and IPC_RMID as its own permission check */
	if (cmd != IPC_SET && cmd != IPC_RMID) {
		/* check read permission */
		if (!check_perm(&sem->semid_ds.sem_perm, who_e, 0444))
			return EACCES;
	}

	switch (cmd) {
	case IPC_STAT:
		ds = (struct semid_ds *) opt;
		if (!ds)
			return EFAULT;
		r = sys_datacopy(SELF_E, (vir_bytes) &sem->semid_ds,
			who_e, (vir_bytes) ds, sizeof(struct semid_ds));
		if (r != OK)
			return EINVAL;
		break;
	case IPC_SET:
		uid = getnuid(who_e);
		if (uid != sem->semid_ds.sem_perm.cuid &&
			uid != sem->semid_ds.sem_perm.uid &&
			uid != 0)
			return EPERM;
		ds = (struct semid_ds *) opt;
		r = sys_datacopy(who_e, (vir_bytes) ds,
			SELF_E, (vir_bytes) &tmp_ds, sizeof(struct semid_ds));
		if (r != OK)
			return EINVAL;
		sem->semid_ds.sem_perm.uid = tmp_ds.sem_perm.uid;
		sem->semid_ds.sem_perm.gid = tmp_ds.sem_perm.gid;
		sem->semid_ds.sem_perm.mode &= ~0777;
		sem->semid_ds.sem_perm.mode |= tmp_ds.sem_perm.mode & 0666;
		sem->semid_ds.sem_ctime = time(NULL);
		break;
	case IPC_RMID:
		uid = getnuid(who_e);
		if (uid != sem->semid_ds.sem_perm.cuid &&
			uid != sem->semid_ds.sem_perm.uid &&
			uid != 0)
			return EPERM;
		/* awaken all processes block in semop
		 * and remove the semaphore set.
		 */
		update_one_semaphore(sem, 1);
		break;
	case IPC_INFO:
		break;
	case SEM_INFO:
		break;
	case SEM_STAT:
		break;
	case GETALL:
		buf = malloc(sizeof(unsigned short) * sem->semid_ds.sem_nsems);
		if (!buf)
			return ENOMEM;
		for (i = 0; i < sem->semid_ds.sem_nsems; i++)
			buf[i] = sem->sems[i].semval;
		r = sys_datacopy(SELF_E, (vir_bytes) buf,
			who_e, (vir_bytes) opt,
			sizeof(unsigned short) * sem->semid_ds.sem_nsems);
		if (r != OK)
			return EINVAL;
		free(buf);
		break;
	case GETNCNT:
		if (num < 0 || num >= sem->semid_ds.sem_nsems)
			return EINVAL;
		m->SHMCTL_RET = sem->sems[num].semncnt;
		break;
	case GETPID:
		if (num < 0 || num >= sem->semid_ds.sem_nsems)
			return EINVAL;
		m->SHMCTL_RET = sem->sems[num].sempid;
		break;
	case GETVAL:
		if (num < 0 || num >= sem->semid_ds.sem_nsems)
			return EINVAL;
		m->SHMCTL_RET = sem->sems[num].semval;
		break;
	case GETZCNT:
		if (num < 0 || num >= sem->semid_ds.sem_nsems)
			return EINVAL;
		m->SHMCTL_RET = sem->sems[num].semzcnt;
		break;
	case SETALL:
		buf = malloc(sizeof(unsigned short) * sem->semid_ds.sem_nsems);
		if (!buf)
			return ENOMEM;
		r = sys_datacopy(who_e, (vir_bytes) opt,
			SELF_E, (vir_bytes) buf,
			sizeof(unsigned short) * sem->semid_ds.sem_nsems);
		if (r != OK)
			return EINVAL;
#ifdef DEBUG_SEM
		printf("SEMCTL: SETALL: opt: %p\n");
		for (i = 0; i < sem->semid_ds.sem_nsems; i++)
			printf("SEMCTL: SETALL val: [%d] %d\n", i, buf[i]);
#endif
		for (i = 0; i < sem->semid_ds.sem_nsems; i++) {
			if (buf[i] < 0 || buf[i] > SEMVMX) {
				free(buf);
				update_semaphores();
				return ERANGE;
			}
			sem->sems[i].semval = buf[i];
		}
		free(buf);
		/* awaken if possible */
		update_semaphores();
		break;
	case SETVAL:
		val = (int) opt;
		/* check write permission */
		if (!check_perm(&sem->semid_ds.sem_perm, who_e, 0222))
			return EACCES;
		if (num < 0 || num >= sem->semid_ds.sem_nsems)
			return EINVAL;
		if (val < 0 || val > SEMVMX)
			return ERANGE;
		sem->sems[num].semval = val;
#ifdef DEBUG_SEM
		printf("SEMCTL: SETVAL: %d %d\n", num, val);
#endif
		sem->semid_ds.sem_ctime = time(NULL);
		/* awaken if possible */
		update_semaphores();
		break;
	default:
		return EINVAL;
	}

	return OK;
}

/*===========================================================================*
 *				do_semop		     		     *
 *===========================================================================*/
PUBLIC int do_semop(message *m)
{
	int id, i, j, r;
	struct sembuf *sops;
	unsigned int nsops;
	struct sem_struct *sem;
	int no_reply = 0;

	id = m->SEMOP_ID;
	nsops = (unsigned int) m->SEMOP_SIZE;

	r = EINVAL;
	if (!(sem = sem_find_id(id)))
		goto out;

	if (nsops <= 0)
		goto out;

	r = E2BIG;
	if (nsops > SEMOPM)
		goto out;

	/* check for read permission */
	r = EACCES;
	if (!check_perm(&sem->semid_ds.sem_perm, who_e, 0444))
		goto out;

	/* get the array from user application */
	r = ENOMEM;
	sops = malloc(sizeof(struct sembuf) * nsops);
	if (!sops)
		goto out_free;
	r = sys_datacopy(who_e, (vir_bytes) m->SEMOP_OPS,
			SELF_E, (vir_bytes) sops,
			sizeof(struct sembuf) * nsops);
	if (r != OK) {
		r = EINVAL;
		goto out_free;
	}

#ifdef DEBUG_SEM
	for (i = 0; i < nsops; i++)
		printf("SEMOP: num:%d  op:%d  flg:%d\n",
			sops[i].sem_num, sops[i].sem_op, sops[i].sem_flg);
#endif
	/* check for value range */
	r = EFBIG;
	for (i = 0; i < nsops; i++)
		if (sops[i].sem_num < 0 ||
				sops[i].sem_num >= sem->semid_ds.sem_nsems)
			goto out_free;

	/* check for duplicate number */
	r = EINVAL;
	for (i = 0; i < nsops; i++)
		for (j = i + 1; j < nsops; j++)
			if (sops[i].sem_num == sops[j].sem_num)
				goto out_free;

	/* check for EAGAIN error */
	r = EAGAIN;
	for (i = 0; i < nsops; i++) {
		int op_n, val;

		op_n = sops[i].sem_op;
		val = sem->sems[sops[i].sem_num].semval;

		if ((sops[i].sem_flg & IPC_NOWAIT) &&
				((!op_n && val) ||
				 (op_n < 0 &&
				  -op_n > val)))
			goto out_free;

	}
	/* there will be no errors left, so we can go ahead */
	for (i = 0; i < nsops; i++) {
		struct semaphore *s;
		int op_n;

		s = &sem->sems[sops[i].sem_num];
		op_n = sops[i].sem_op;

		s->sempid = getnpid(who_e);

		if (op_n > 0) {
			/* check for alter permission */
			r = EACCES;
			if (!check_perm(&sem->semid_ds.sem_perm, who_e, 0222))
				goto out_free;
			s->semval += sops[i].sem_op;
		} else if (!op_n) {
			if (s->semval) {
				/* put the process asleep */
				s->semzcnt++;
				s->zlist = realloc(s->zlist, sizeof(struct waiting) * s->semzcnt);
				if (!s->zlist) {
					printf("IPC: zero waiting list lost...\n");
					break;
				}
				s->zlist[s->semzcnt-1].who = who_e;
				s->zlist[s->semzcnt-1].val = op_n;

#ifdef DEBUG_SEM
				printf("SEMOP: Put into sleep... %d\n", who_e);
#endif
				no_reply++;
			}
		} else {
			/* check for alter permission */
			r = EACCES;
			if (!check_perm(&sem->semid_ds.sem_perm, who_e, 0222))
				goto out_free;
			if (s->semval >= -op_n)
				s->semval += op_n;
			else {
				/* put the process asleep */
				s->semncnt++;
				s->nlist = realloc(s->nlist, sizeof(struct waiting) * s->semncnt);
				if (!s->nlist) {
					printf("IPC: increase waiting list lost...\n");
					break;
				}
				s->nlist[s->semncnt-1].who = who_e;
				s->nlist[s->semncnt-1].val = -op_n;

				no_reply++;
			}
		}
	}

	r = OK;
out_free:
	free(sops);
out:
	/* if we reach here by errors
	 * or with no errors but we should reply back.
	 */
	if (r != OK || !no_reply) {
		m->m_type = r;

		r = sendnb(who_e, m);
		if (r != OK)
			printf("IPC send error!\n");
	}

	/* awaken process if possible */
	update_semaphores();

	return 0;
}

/*===========================================================================*
 *				is_sem_nil		     		     *
 *===========================================================================*/
PUBLIC int is_sem_nil(void)
{
	return (sem_list_nr == 0);
}

/*===========================================================================*
 *				sem_process_vm_notify	     		     *
 *===========================================================================*/
PUBLIC void sem_process_vm_notify(void)
{
	endpoint_t pt;
	int r;

	while ((r = vm_query_exit(&pt)) >= 0) {
		/* for each enpoint 'pt', check whether it's waiting... */
		remove_process(pt);

		if (r == 0)
			break;
	}
	if (r < 0)
		printf("IPC: query exit error!\n");
}

