/*-------------------------------------------------------------------------
 *
 * sem.c
 *	  BeOS System V Semaphores Emulation
 *
 * Copyright (c) 1999-2000, Cyril VELTER
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <errno.h>
#include <unistd.h>
#include <OS.h>

/*#define TDBG*/
#ifdef TDBG
#define TRACEDBG(x) printf(x);printf("\n")
#define TRACEDBGP(x,y) printf(x,y);printf("\n")
#define TRACEDBGPP(x,y,z) printf(x,y,z);printf("\n")
#else
#define TRACEDBG(x)
#define TRACEDBGP(x,y)
#define TRACEDBGPP(x,y,z)
#endif

/* Control of a semaphore pool. The pool is an area in which we stored all
the semIds of the pool. The first 4 bytes are the number of semaphore allocated
in the pool followed by SemIds */

int
semctl(int semId, int semNum, int flag, union semun semun)
{
	int32	   *Address;
	area_info	info;

	TRACEDBG("->semctl");
	/* Try to find the pool */
	if (get_area_info(semId, &info) != B_OK)
	{
		/* pool is invalid (BeOS area id is invalid) */
		errno = EINVAL;
		TRACEDBG("<-semctl invalid pool");
		return -1;
	}

	/* Get the pool address */
	Address = (int32 *) info.address;
	TRACEDBGP("--semctl address %d", Address);


	/* semNum might be 0 */
	/* semun.array contain the sem initial values */

	/* Fix the count of all sem of the pool to semun.array */
	if (flag == SETALL)
	{
		long		i;

		TRACEDBG("--semctl setall");
		for (i = 0; i < Address[0]; i++)
		{
			int32		cnt;

			/* Get the current count */
			get_sem_count(Address[2 * i + 1], &cnt);

			TRACEDBGP("--semctl setall %d", semun.array[i]);

			/* Compute and set the new count (relative to the old one) */
			cnt -= semun.array[i];
			TRACEDBGPP("--semctl acquire id : %d cnt : %d", Address[2 * i + 1], cnt);
			if (cnt > 0)
				while (acquire_sem_etc(Address[2 * i + 1], cnt, 0, 0) == B_INTERRUPTED);
			if (cnt < 0)
				release_sem_etc(Address[2 * i + 1], -cnt, 0);
		}
		return 1;
	}

	/* Fix the count of one semaphore to semun.val */
	if (flag == SETVAL)
	{
		int32		cnt;

		TRACEDBGP("--semctl setval %d", semun.val);
		/* Get the current count */
		get_sem_count(Address[2 * semNum + 1], &cnt);

		/* Compute and set the new count (relative to the old one) */
		cnt -= semun.val;
		TRACEDBGPP("--semctl acquire id : %d cnt : %d", Address[2 * semNum + 1], cnt);
		if (cnt > 0)
			while (acquire_sem_etc(Address[2 * semNum + 1], cnt, 0, 0) == B_INTERRUPTED);
		if (cnt < 0)
			release_sem_etc(Address[2 * semNum + 1], -cnt, 0);
		return 1;
	}

	/* Get the last pid which accessed the sem */
	if (flag == GETPID)
	{
		TRACEDBG("->semctl getpid");
		return Address[2 * semNum + 2];
	}

	/* Delete the pool */
	if (flag == IPC_RMID)
	{
		long		i;

		thread_info ti;

		TRACEDBG("->semctl rmid");
		get_thread_info(find_thread(NULL), &ti);

		/* Loop over all semaphore to delete them */
		TRACEDBGP("->semctl nmbre %d", Address[0]);
		for (i = 0; i < Address[0]; i++)
		{
			/*
			 * Make sure to have ownership of the semaphore (if created by
			 * another team)
			 */
			TRACEDBGP("->semctl id %d", Address[2 * i + 1]);
			set_sem_owner(Address[2 * i + 1], ti.team);

			/* Delete the semaphore */
			delete_sem(Address[2 * i + 1]);

			/*
			 * Reset to an invalid semId (in case other process try to get
			 * the infos from a cloned area
			 */
			Address[2 * i + 1] = 0;
		}

		/* Set the semaphore count to 0 */
		Address[0] = 0;

		/*
		 * Delete the area (it might be cloned by other process. Let them
		 * live with it, in all cases semIds are 0 so if another process
		 * try to use it, it will fail
		 */
		delete_area(semId);

		return 1;
	}

	/* Get the current semaphore count */
	if (flag == GETNCNT)
	{
		/* TO BE IMPLEMENTED */
		TRACEDBG("--semctl getncnt");
		elog(ERROR, "semctl error: GETNCNT not implemented");
		return 0;
	}

	/* Get the current semaphore count of the first semaphore in the pool */
	if (flag == GETVAL)
	{
		int32		cnt;

		TRACEDBG("--semctl getval");
		get_sem_count(Address[2 * semNum + 1], &cnt);
		TRACEDBGP("--semctl val %d", cnt);
		return cnt;
	}

	elog(ERROR, "semctl error: unknown flag");

	TRACEDBG("<-semctl unknown flag");
	return 0;
}

/* Find a pool id based on IPC key */
int
semget(int semKey, int semNum, int flags)
{
	char		Nom[50];
	area_id		parea;
	void	   *Address;

	TRACEDBGPP("->semget key : %d num : %d", semKey, semNum);
	/* Name of the area to find */
	sprintf(Nom, "SYSV_IPC_SEM : %d", semKey);

	/* find area */
	parea = find_area(Nom);

	/* Test of area existence */
	if (parea != B_NAME_NOT_FOUND)
	{
		/* Area exist and creation is requested, error */
		if ((flags & IPC_CREAT) && (flags & IPC_EXCL))
		{
			errno = EEXIST;
			return -1;
		}

		/* Get an area clone (in case it's not in our address space) */

		/*
		 * TODO : a check of address space might be done to avoid
		 * duplicate areas in the same address space
		 */
		parea = clone_area(Nom, &Address, B_ANY_ADDRESS, B_READ_AREA | B_WRITE_AREA, parea);
		return parea;
	}
	else
	{
		/* Area does not  exist, but creation is requested, so create it */
		if (flags & IPC_CREAT)
		{
			int32	   *Address;
			void	   *Ad;
			long		i;

			/*
			 * Limit to 250 (8 byte per sem : 4 for the semid and 4 for
			 * the last pid which accessed the semaphore in a pool
			 */
			if (semNum > 250)
			{
				errno = ENOSPC;
				return -1;
			}

			/* Create the shared memory area which will hold the pool */
			parea = create_area(Nom, &Ad, B_ANY_ADDRESS, 4096, B_NO_LOCK, B_READ_AREA | B_WRITE_AREA);
			if ((parea == B_BAD_VALUE) || (parea == B_NO_MEMORY) || (parea == B_ERROR))
			{
				errno = ENOMEM;
				return -1;
			}

			/* fill up informations (sem number and sem ids) */
			Address = (int32 *) Ad;
			Address[0] = semNum;
			for (i = 0; i < Address[0]; i++)
			{
				/* Create the semaphores */
				Address[2 * i + 1] = create_sem(0, Nom);

				if ((Address[2 * i + 1] == B_BAD_VALUE) || (Address[2 * i + 1] == B_NO_MEMORY) || (Address[2 * i + 1] == B_NO_MORE_SEMS))
				{
					errno = ENOMEM;
					return -1;
				}
			}

			return parea;
		}
		else
		{
			/* Area does not exist and no creation is requested */
			errno = ENOENT;
			return -1;
		}
	}
}

/* Acquire or release in the semaphore pool */
int
semop(int semId, struct sembuf * sops, int nsops)
{
	int32	   *Address;		/* Pool address */
	area_info	info;
	long		i;
	long		ret;

	/* Get the pool address (semId IS an area id) */
	get_area_info(semId, &info);
	Address = (int32 *) info.address;

	/* Check the validity of semId (it should be an area id) */
	if ((semId == B_BAD_VALUE) || (semId == B_NO_MEMORY) || (semId == B_ERROR))
	{
		errno = EINVAL;
		return -1;
	}

	/* Perform acquire or release */
	for (i = 0; i < nsops; i++)
	{
		/* remember the PID */
		Address[2 * (sops[i].sem_num) + 2] = getpid();

		/* For each sem in the pool, check the operation to perform */
		if (sops[i].sem_op < 0)
		{
			/*
			 * Try acquiring the semaphore till we are not interrupted by a
			 * signal
			 */
			if (sops[i].sem_flg == IPC_NOWAIT)
			{
				/* Try to lock ... */
				while ((ret = acquire_sem_etc(Address[2 * (sops[i].sem_num) + 1], -sops[i].sem_op, B_RELATIVE_TIMEOUT, 0)) == B_INTERRUPTED);
				if (ret != B_OK)
					return EWOULDBLOCK;
			}
			else
				while (acquire_sem_etc(Address[2 * (sops[i].sem_num) + 1], -sops[i].sem_op, 0, 0) == B_INTERRUPTED);
		}
		if (sops[i].sem_op > 0)
			release_sem_etc(Address[2 * (sops[i].sem_num) + 1], sops[i].sem_op, 0);
	}

	return 0;
}
