/*
 * Copyright (c) 2000-2001, 2005 Sendmail, Inc. and its suppliers.
 *      All rights reserved.
 *
 * By using this file, you agree to the terms and conditions set
 * forth in the LICENSE file which can be found at the top level of
 * the sendmail distribution.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <sm/gen.h>
SM_RCSID("@(#)$Id: sem.c,v 1.13 2005/08/12 20:39:59 ca Exp $")

#if SM_CONF_SEM
# include <stdlib.h>
# include <unistd.h>
# include <sm/sem.h>
# include <sm/heap.h>
# include <errno.h>

/*
**  SM_SEM_START -- initialize semaphores
**
**	Parameters:
**		key -- key for semaphores.
**		nsem -- number of semaphores.
**		semflg -- flag for semget(), if 0, use a default.
**		owner -- create semaphores.
**
**	Returns:
**		id for semaphores.
**		< 0 on failure.
*/

int
sm_sem_start(key, nsem, semflg, owner)
	key_t key;
	int nsem;
	int semflg;
	bool owner;
{
	int semid, i, err;
	unsigned short *semvals;

	semvals = NULL;
	if (semflg == 0)
		semflg = (SEM_A|SEM_R)|((SEM_A|SEM_R) >> 3);
	if (owner)
		semflg |= IPC_CREAT|IPC_EXCL;
	semid = semget(key, nsem, semflg);
	if (semid < 0)
		goto error;

	if (owner)
	{
		union semun semarg;

		semvals = (unsigned short *) sm_malloc(nsem * sizeof semvals);
		if (semvals == NULL)
			goto error;
		semarg.array = semvals;

		/* initialize semaphore values to be available */
		for (i = 0; i < nsem; i++)
			semvals[i] = 1;
		if (semctl(semid, 0, SETALL, semarg) < 0)
			goto error;
	}
	return semid;

error:
	err = errno;
	if (semvals != NULL)
		sm_free(semvals);
	if (semid >= 0)
		sm_sem_stop(semid);
	return (err > 0) ? (0 - err) : -1;
}

/*
**  SM_SEM_STOP -- stop using semaphores.
**
**	Parameters:
**		semid -- id for semaphores.
**
**	Returns:
**		0 on success.
**		< 0 on failure.
*/

int
sm_sem_stop(semid)
	int semid;
{
	return semctl(semid, 0, IPC_RMID, NULL);
}

/*
**  SM_SEM_ACQ -- acquire semaphore.
**
**	Parameters:
**		semid -- id for semaphores.
**		semnum -- number of semaphore.
**		timeout -- how long to wait for operation to succeed.
**
**	Returns:
**		0 on success.
**		< 0 on failure.
*/

int
sm_sem_acq(semid, semnum, timeout)
	int semid;
	int semnum;
	int timeout;
{
	int r;
	struct sembuf semops[1];

	semops[0].sem_num = semnum;
	semops[0].sem_op = -1;
	semops[0].sem_flg = SEM_UNDO |
			    (timeout != SM_TIME_FOREVER ? 0 : IPC_NOWAIT);
	if (timeout == SM_TIME_IMMEDIATE || timeout == SM_TIME_FOREVER)
		return semop(semid, semops, 1);
	do
	{
		r = semop(semid, semops, 1);
		if (r == 0)
			return r;
		sleep(1);
		--timeout;
	} while (timeout > 0);
	return r;
}

/*
**  SM_SEM_REL -- release semaphore.
**
**	Parameters:
**		semid -- id for semaphores.
**		semnum -- number of semaphore.
**		timeout -- how long to wait for operation to succeed.
**
**	Returns:
**		0 on success.
**		< 0 on failure.
*/

int
sm_sem_rel(semid, semnum, timeout)
	int semid;
	int semnum;
	int timeout;
{
	int r;
	struct sembuf semops[1];

#if PARANOID
	/* XXX should we check whether the value is already 0 ? */
	SM_REQUIRE(sm_get_sem(semid, semnum) > 0);
#endif /* PARANOID */

	semops[0].sem_num = semnum;
	semops[0].sem_op = 1;
	semops[0].sem_flg = SEM_UNDO |
			    (timeout != SM_TIME_FOREVER ? 0 : IPC_NOWAIT);
	if (timeout == SM_TIME_IMMEDIATE || timeout == SM_TIME_FOREVER)
		return semop(semid, semops, 1);
	do
	{
		r = semop(semid, semops, 1);
		if (r == 0)
			return r;
		sleep(1);
		--timeout;
	} while (timeout > 0);
	return r;
}

/*
**  SM_SEM_GET -- get semaphore value.
**
**	Parameters:
**		semid -- id for semaphores.
**		semnum -- number of semaphore.
**
**	Returns:
**		value of semaphore on success.
**		< 0 on failure.
*/

int
sm_sem_get(semid, semnum)
	int semid;
	int semnum;
{
	int semval;

	if ((semval = semctl(semid, semnum, GETVAL, NULL)) < 0)
		return -1;
	return semval;
}
#endif /* SM_CONF_SEM */