/* svsem - POSIX-like semaphores implemented using SysV semaphores
 * Copyright (c) 2003 Michael B. Allen <mba2000 ioplex.com>
 *
 * The MIT License
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <errno.h>

#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/mman.h>
#include <sys/wait.h>

#include "mba/svsem.h"
#include "mba/pool.h"
#include "mba/varray.h"
#include "mba/misc.h"
#include "mba/msgno.h"

#if defined(_SEM_SEMUN_UNDEFINED) || \
		(defined(__digital__) && defined(__unix__)) || \
		defined(_HPUX_SOURCE)
union semun {
	int val;                  /* value for SETVAL */
	struct semid_ds *buf;     /* buffer for IPC_STAT, IPC_SET */
	unsigned short *array;    /* array for GETALL, SETALL */
	/* Linux specific part: */
	struct seminfo *__buf;    /* buffer for IPC_INFO */
};
#endif

#define MAGIC 0xAD800000
#define MAX_TRIES 10
#define MAX_SEMNUM 255
#define SEM_ISVALID(s) ((s) && ((s)->flags & 0xFFF00000) == MAGIC)
#define IS_UNDO(s)     ((s) && ((s)->flags & 0x00080000) != 0)

/* Open or create a semaphore initializing it as necessary.
 */
static int
semid_get(const char *name, int nsems, int oflags, mode_t mode, int value)
{
	key_t key;
	int max;

	if (nsems > MAX_SEMNUM) {
		PMNO(errno = ERANGE);
		return -1;
	}
	if ((key = ftok((char *)name, 1)) == (key_t)-1) {
		PMNO(errno);
		return -1;
	}

	/* This following loop ensures that we know if the semaphore was created
	 * as opposed to just opened so that it can be initialized properly. We
	 * do this by alternating between oflags 0 and IPC_CREATE|IPC_EXCL until
	 * one succeeds.
	 */ 
	for (max = MAX_TRIES; max; max--) {
		int semid;
		union semun arg;

		if ((oflags & O_EXCL) == 0) {
			if ((semid = semget(key, nsems, 0)) != -1) {
				struct semid_ds buf;

				/* This inner try-loop ensures that the semaphore is initialized before
				 * we return even if the semaphore has been created with semget but not
				 * yet initialized with semctl. See Stevens' UNPv2 p274.
				 */
				arg.buf = &buf;
				for (max = MAX_TRIES; max; max--) {
					if (semctl(semid, 0, IPC_STAT, arg) == -1) {
						PMNO(errno);
						return -1;
					}
					if (buf.sem_otime != 0) {
						return semid;
					}
					sleep(1);
				}

				PMNO(errno = ETIMEDOUT);
				return -1;
			} else if (errno != ENOENT) {
				PMNO(errno);
				return -1;
			}
		}
		if ((semid = semget(key, nsems, IPC_CREAT | IPC_EXCL | (mode & 0777))) != -1) {
			struct sembuf initop;

			if (nsems > 1) {
				unsigned short array[MAX_SEMNUM * sizeof(unsigned short)];
				int i;

				arg.array = array;
				arg.array[0] = 0; /* leave the first one 0 to be set with semop */
				for (i = 1; i < nsems; i++) {
					arg.array[i] = value;
				}
				if (semctl(semid, 0, SETALL, arg) == -1) {
					PMNO(errno);
					semctl(semid, 0, IPC_RMID);
					return -1;
				}
			} else {
				arg.val = 0;
				if (semctl(semid, 0, SETVAL, arg) == -1) {
					PMNO(errno);
					semctl(semid, 0, IPC_RMID);
					return -1;
				}
			}
                                 /* increment by value to set sem_otime nonzero */
			initop.sem_num = 0;
			initop.sem_op = value;
			initop.sem_flg = 0;
			if (semop(semid, &initop, 1) == -1) {
				PMNO(errno);
				semctl(semid, 0, IPC_RMID);
				return -1;
			}

			return semid;
		} else if ((oflags & O_EXCL) || errno != EEXIST) {
			PMNO(errno);
			return -1;
		}
	}

	PMNO(errno = ETIMEDOUT);
	return -1;
}
static void *
_svs_new(void *context, size_t size, int flags)
{
	struct _svs_data *sd = context;
	svsem_t *sem;

				/* size is really semaphore number */
	if ((sem = varray_get(&sd->sems, size)) == NULL) {
		AMSG("");
		return NULL;
	}
	sem->id = sd->id;
	sem->num = size;
	sem->flags = MAGIC | (flags & 0x000FFFFFF);

                             /* is this necessary? */
	if (svsem_setvalue(sem, sd->val) == -1) {
		AMSG("");
		sem->flags = 0;
		return NULL;
	}

	return sem;
}
static int
_svs_del(void *context, void *object)
{
	svsem_t *sem = object;

	sem->id = 0;
	sem->flags = 0;

	(void)context;
	return 0;
}
static int
_svs_rst(void *context, void *object)
{
	struct _svs_data *sd = context;
	svsem_t *sem = object;

	if (svsem_setvalue(sem, sd->val) == -1) {
		AMSG("");
		return -1;
	}

	return 0;
}
int
svsem_pool_destroy(struct pool *p)
{
	int ret = 0;

	if (p) {
		struct _svs_data *sd = p->context;
		ret += pool_destroy(p);
		ret += varray_deinit(&sd->sems);
		ret += semctl(sd->id, 0, IPC_RMID);
		unlink(sd->name);
		ret += allocator_free(p->al, sd);
	}

	return ret;
}
int
svsem_pool_create(struct pool *p,
		unsigned int max_size,
		unsigned int value,
		int undo,
		struct allocator *al)
{
	struct _svs_data *sd;
	int fd = 0, nsems = max_size;

	memset(p, 0, sizeof *p);

	if ((sd = allocator_alloc(al, sizeof *sd, 0)) == NULL) {
		AMSG("");
		return -1;
	}

	strcpy(sd->name, "/tmp/svsemXXXXXX");
	if ((fd = mkstemp(sd->name)) == -1) {
		PMNO(errno);
		allocator_free(al, sd);
		return -1;
	}

	if ((sd->id = semid_get(sd->name, nsems, O_CREAT, 0666, value)) == -1) {
		AMSG("");
		allocator_free(al, sd);
		unlink(sd->name);
		return -1;
	}
	unlink(sd->name);
	if (fd) close(fd);

	sd->val = value;
	if (varray_init(&sd->sems, offsetof(svsem_t, name), al) == -1) {
		AMSG("");
		return -1;
	}
	if (pool_create(p, max_size, _svs_new, _svs_del, _svs_rst, sd, (size_t)-1, undo ? O_UNDO : 0, al) == -1) {
		AMSG("");
		semctl(sd->id, 0, IPC_RMID);
		allocator_free(al, sd);
		return -1;
	}

	return 0;
}
int
svsem_open(svsem_t *sem, const char *path, int oflag, ...)
{
	mode_t mode = 0;
	int val = 0, fd = 0;

	if ((oflag & O_CREAT)) {
		va_list ap;

		va_start(ap, oflag);
		mode = va_arg(ap, int);
		val = va_arg(ap, unsigned int);
		va_end(ap);

		if ((fd = open(path, O_CREAT, mode)) == -1) {
			PMNF(errno, ": %s", path);
			return -1;
		}
	}

	if ((sem->id = semid_get(path, 1, oflag, mode, val)) == -1) {
		AMSG("");
		return -1;
	}
	sem->num = 0;
	sem->flags = MAGIC | (oflag & 0x000FFFFFF);
                         /* how do I know this mask is ok? */
	if (fd) close(fd);

	return 0;
}
int
svsem_close(svsem_t *sem)
{
	if (!SEM_ISVALID(sem)) {
		PMNO(errno = EINVAL);
		return -1;
	}

	sem->id = sem->flags = 0;

	return 0;
}
int
svsem_remove(svsem_t *sem)
{
	if (!SEM_ISVALID(sem)) {
		PMNO(errno = EINVAL);
		return -1;
	}

	if (semctl(sem->id, 0, IPC_RMID) == -1) {
		PMNO(errno);
		return -1;
	}
	sem->id = sem->flags = 0;

	return 0;
}
int
svsem_create(svsem_t *sem, int value, int undo)
{
	int fd = 0;
			/* do not change this path without also adjusting
			 * the size if svsem_t's name member */
	strcpy(sem->name, "/tmp/svsemXXXXXX");
	if ((fd = mkstemp(sem->name)) == -1) {
		PMNO(errno);
		return -1;
	}

	if ((sem->id = semid_get(sem->name, 1, O_CREAT | O_EXCL, 0600, value)) == -1) {
		AMSG("");
		return -1;
	}
	sem->num = 0;
	sem->flags = MAGIC | (undo ? O_UNDO : 0);
	if (fd) close(fd);

	return 0;
}
int
svsem_destroy(svsem_t *sem)
{
	if (!SEM_ISVALID(sem)) {
		PMNO(errno = EINVAL);
		return -1;
	}

	if (semctl(sem->id, 0, IPC_RMID) == -1 && errno != EINVAL) {
		PMNO(errno);
		return -1;
	}
	sem->id = sem->flags = 0;
	unlink(sem->name);

	return 0;
}
int
svsem_wait(svsem_t *sem)
{
	struct sembuf wait;

	if (!SEM_ISVALID(sem)) {
		PMNO(errno = EINVAL);
		return -1;
	}

	wait.sem_num = sem->num;
	wait.sem_op = -1;
	wait.sem_flg = IS_UNDO(sem) ? SEM_UNDO : 0;

	if (semop(sem->id, &wait, 1) == -1) {
		PMNF(errno, ": %d:%d", sem->id, sem->num);
		return -1;
	}

	return 0;
}
int
svsem_trywait(svsem_t *sem)
{
	struct sembuf wait;

	if (!SEM_ISVALID(sem)) {
		PMNO(errno = EINVAL);
		return -1;
	}

	wait.sem_num = sem->num;
	wait.sem_op = -1;
	wait.sem_flg = IPC_NOWAIT | (IS_UNDO(sem) ? SEM_UNDO : 0);

	if (semop(sem->id, &wait, 1) == -1) {
		PMNF(errno, ": %d:%d", sem->id, sem->num);
		return -1;
	}

	return 0;
}
int
svsem_post(svsem_t *sem)
{
	struct sembuf post;

	if (!SEM_ISVALID(sem)) {
		PMNO(errno = EINVAL);
		return -1;
	}

	post.sem_num = sem->num;
	post.sem_op = 1;
	post.sem_flg = IS_UNDO(sem) ? SEM_UNDO : 0;

	if (semop(sem->id, &post, 1) == -1) {
		PMNF(errno, ": %d:%d", sem->id, sem->num);
		return -1;
	}

	return 0;
}
int
svsem_post_multiple(svsem_t *sem, int count)
{
	int ret = 0;
	struct sembuf post;

	if (!SEM_ISVALID(sem)) {
		PMNO(errno = EINVAL);
		return -1;
	}

	post.sem_num = sem->num;
	post.sem_op = 1;
	post.sem_flg = IS_UNDO(sem) ? SEM_UNDO : 0;

	while (count--) {
		ret += semop(sem->id, &post, 1);
	}

	if (ret) {
		PMNF(errno, ": %d:%d", sem->id, sem->num);
		return -1;
	}

	return 0;
}
int
svsem_getvalue(svsem_t *sem, int *value)
{
	int v;

	if (!SEM_ISVALID(sem)) {
		PMNO(errno = EINVAL);
		return -1;
	}

	if ((v = semctl(sem->id, sem->num, GETVAL, 0)) == -1) {
		PMNO(errno);
		return -1;
	}
	*value = v;

	return 0;
}
int
svsem_setvalue(svsem_t *sem, int value)
{
	union semun arg;

	if (!SEM_ISVALID(sem)) {
		PMNO(errno = EINVAL);
		return -1;
	}

	arg.val = value;
	if (semctl(sem->id, sem->num, SETVAL, arg) == -1) {
		PMNO(errno);
		return -1;
	}

	return 0;
}



syntax highlighted by Code2HTML, v. 0.9.1