/*************************************************************************
*                                                                        *
*                             COPYRIGHT NOTICE                           *
*                                                                        *
* This software/database is "United States Government Work" under the	 *
* terms of the United States Copyright Act.  It was written as part of	 *
* the author's official duties as a Government employee and thus cannot	 *
* be copyrighted.  This software/database is freely available to the	 *
* public for use without a copyright notice.  Restrictions cannot be	 *
* placed on its present or future use.                                   *
*                                                                        *
* Although all reasonable efforts have been taken to insure the accuracy *
* and reliability of the software and data, the National Library of      *
* Medicine (NLM) and the U.S. Government does not and cannot warrant the *
* performance or results that may be obtained by using this software or  *
* data.  The NLM and the U.S. Government disclaims all warranties as to  *
* performance, merchantability or fitness for any particular purpose.    *
*                                                                        *
* In any work or product derived from this material, proper attribution  *
* of the author as the source of the software or data would be           *
* appreciated.                                                           *
*                                                                        *
*************************************************************************/

#include <ncbi.h>
#include <gish.h>
#include <gishlib.h>

#ifdef SYSV_IPC_AVAIL
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/msg.h>
#endif

#define PROJECTID	5	/* the project ID used in calls to ftok() */

key_t _cdecl
shm_mkkey(filename)
	CharPtr	filename;
{
#ifdef SYSV_IPC_AVAIL
	key_t	key;

	key = ftok(filename, PROJECTID);
	return key;
#else
	return 0;
#endif
}

VoidPtr _cdecl
shm_addr(shmdp)
	shm_dat	PNTR shmdp;
{
#ifdef SYSV_IPC_AVAIL
	if (shmdp != NULL)
		return shmdp->shmaddr;
#endif
	return NULL;
}


/*
	Create a new shared memory segment and read the entire file into it.
	Returns pointer to the shared memory segment on success; NULL on failure.
*/
shm_dat PNTR _cdecl
shm_loadfile(filename, mmode, smode, qmode, lockfile, verbose)
	CharPtr	filename;
	int		mmode, /* Access mode for shared memory segment */
			smode, /* Access mode for semaphore */
			qmode; /* Access mode for message queue */
	int	lockfile, verbose;
{
#ifdef SYSV_IPC_AVAIL
	key_t	key;
	int		shmid, semid, msqid;
    struct shmid_ds shmds;
	shm_dat	shmd, *shmdp;
	union foo {
		int	val;
		struct semid_ds	*buf;
		unsigned short	*array;
	}	sem;
	int		val;
	int	err;
	register char	*cp;
	long	filesize;
	FILE	*fp;

	if (filename == NULL || *filename == NULLB)
		return NULL;

	mmode &= BITMASK(9);
	smode &= BITMASK(9);
	qmode &= BITMASK(9);

	/* Open the file first thing, so we have a pseudo-lock on it */
	fp = fopen(filename, "r");
	if (fp == NULL) {
		if (verbose)
			perror(filename);
		return NULL;
	}

	shmd.key = key = shm_mkkey(filename);
	if (key == -1) {
		if (verbose)
			perror(filename);
		fclose(fp);
		return NULL;
	}

	fseek(fp, 0, SEEK_END);
	shmd.filesize = filesize = ftell(fp);
	fseek(fp, 0, SEEK_SET);

	/* Create a semaphore corresponding to "key" */
	shmd.semid = semid = semget(key, 1, IPC_EXCL|IPC_CREAT|smode);
	if (semid == -1) {
		if (verbose)
			perror(filename);
		fclose(fp);
		return NULL;
	}

	/* Does a shared memory segment corresponding to "key" already exist? */
	shmd.shmid = shmid = shmget(key, filesize, IPC_EXCL|IPC_CREAT|0600);
	if (shmid == -1) {
		if (verbose) {
			err = errno;
			perror(filename);
			if (err == EINVAL)
				fprintf(stderr, "File size is invalid--perhaps greater than the kernel-imposed maximum size for a shared memory segment.\n");
		}
		/* Destroy the semaphore just created */
		semctl(semid, 0, IPC_RMID);
		(void) fclose(fp);
		return NULL;
	}

	/* Does a message queue corresponding to "key" already exist? */
	shmd.msqid = msqid = msgget(key, IPC_EXCL|IPC_CREAT|qmode);
	if (msqid == -1) {
		if (verbose)
			perror(filename);
		/* Destroy the shared memory segment just created */
		shmctl(shmid, IPC_RMID, NULL);
		/* Destroy the semaphore just created */
		semctl(semid, 0, IPC_RMID);
		fclose(fp);
		return NULL;
	}

    cp = (char *)shmat(shmid, NULL, 0);
    shmd.shmaddr = (void *)cp;
	if (cp == NULL || cp == (char *)-1) {
		if (verbose)
			perror(filename);
		/* Destroy the shared memory segment just created */
		shmctl(shmid, IPC_RMID, NULL);
		/* Destroy the message queue */
		msgctl(msqid, IPC_RMID, NULL);
		/* Destroy the semaphore just created */
		semctl(semid, 0, IPC_RMID);
		fclose(fp);
		return NULL;
	}

	if (fread(cp, filesize, 1, fp) != 1) {
		if (verbose)
			perror(filename);
		/* Destroy the shared memory segment just created */
		shmctl(shmid, IPC_RMID, NULL);
		/* Destroy the message queue */
		msgctl(msqid, IPC_RMID, NULL);
		/* Destroy the semaphore just created */
		semctl(semid, 0, IPC_RMID);
		fclose(fp);
		return NULL;
	}
	if (mmode != 0600) {
		if (shmctl(shmid, IPC_STAT, &shmds) != 0 && verbose)
			perror("shmctl1");
		shmds.shm_perm.mode &= ~BITMASK(9);
		shmds.shm_perm.mode |= mmode;
		if (shmctl(shmid, IPC_SET, &shmds) != 0 && verbose)
			perror("shmctl2");
	}

	if (lockfile == TRUE) {
		if (shmctl(shmid, SHM_LOCK, NULL) != 0)
			perror("SHM_LOCK");
	}

	/* Make the semaphore available to 32767 other processes */
	sem.val = 32767;
	if (semctl(semid, 0, SETVAL, sem) == -1 && verbose)
		perror(filename);

	shmdp = (shm_dat *)mem_dup(&shmd, sizeof(shmd));
	shmdp->filename = str_dup(filename);
	fclose(fp);
	return shmdp;
#else /* !SYSV_IPC_AVAIL */
	if (verbose)
		fprintf(stderr, "\nshm_loadfile:  SYSV_IPC is unavailable\n");
	return NULL;
#endif /* !SYSV_IPC_AVAIL */
}


/*
	Attach to an existing shared memory segment that contains
	a copy of the specified file.

	Segment is attached RDONLY if mode == "r"
	Segment is attached RDWR if mode == "w"
*/
shm_dat PNTR _cdecl
shm_attfile(filename, mode, verbose)
	CharPtr	filename;
	CharPtr	mode; /* "r" or "w" */
	int		verbose; /* non-zero means issue verbal complaints upon error */
{
#ifdef SYSV_IPC_AVAIL
	static char	*module = "shm_attfile";
	shm_dat  shmd, *shmdp;
	key_t	key;
	struct sembuf	sops;
	struct stat	sbuf;
	int		semid, shmid, shmflg;
	char	*cp;

	shmd.fp = NULL;
	shmd.key = key = shm_mkkey(filename);
	if (key == -1) {
		if (verbose)
			perror(filename);
		return NULL;
	}

	shmd.semid = semid = semget(key, 1, 0);
	if (semid == -1) {
		if (verbose)
			perror(filename);
		return NULL;
	}
	sops.sem_num = 0;
	sops.sem_op = -1;
	sops.sem_flg = SEM_UNDO;
	if (semop(semid, &sops, 1) == -1) {
		if (verbose)
			perror(filename);
		return NULL;
	}

	if (stat(filename, &sbuf) == -1) {
		if (verbose)
			perror(filename);
		return NULL;
	}

	shmd.shmid = shmid = shmget(key, sbuf.st_size, 0);
	if (shmid == -1) {
		if (verbose)
			perror(filename);
		return NULL;
	}

	if (mode[0] == 'r')
		shmflg = SHM_RDONLY;
	else /* mode[0] == 'w' */
		shmflg = 0;
	shmd.shmaddr = cp = (char *)shmat(shmid, NULL, shmflg);
	if (cp == NULL || cp == (char *)-1) {
		if (verbose)
			perror(module);
		return NULL;
	}
	shmdp = (shm_dat *)mem_dup(&shmd, sizeof(shmd));
	shmdp->filename = str_dup(filename);
	shmdp->filesize = sbuf.st_size;
	return shmdp;
#else
	return NULL;
#endif
}


/*
	shm_detach() -- detach the shared memory segment pointed to by 'sp'
*/
int _cdecl
shm_detach(sp)
	CharPtr	sp;
{
#ifdef SYSV_IPC_AVAIL
	return shmdt(sp);
#else
	return 1;
#endif
}


/*
	shm_dropfile -- remove any existing shared memory segment that keys
	to the specified file.  Warning:  all processes including the local
	process must be assured of being detached from the segment before
	this function is called, or they will crash upon their next attempt
	to reference storage within the segment.
*/
int _cdecl
shm_dropfile(shmdp)
	shm_dat	PNTR shmdp;
{
#ifdef SYSV_IPC_AVAIL
	int		semid, shmid, msqid;
	struct sembuf	sops;
	int		i;

	if (shmdp == NULL)
		return -1;

	semid = shmdp->semid, shmid = shmdp->shmid, msqid = shmdp->msqid;

	/* Grab the semaphore as soon as all other users have released it */
	sops.sem_num = 0;
	sops.sem_op = -16000;
	sops.sem_flg = 0;
	i = semop(semid, &sops, 1);

	/* Detach the shared memory segment */
	shmdt(shmdp->shmaddr);
	if (shmdp->filename != NULL)
		Nlm_Free(shmdp->filename);
	shmdp->filename = NULL;
	Nlm_Free(shmdp);

	i |= semctl(semid, 0, IPC_RMID) | shmctl(shmid, IPC_RMID, NULL);
	if (msqid != -1)
		i |= msgctl(msqid, IPC_RMID, NULL);
	return i;
#else
	return 1;
#endif
}


int _cdecl
shm_sendto(filename, sig)
	CharPtr	filename;
	int		sig;
{
#ifdef SYSV_IPC_AVAIL
	key_t	key;
	int		msqid;
	struct msgbuf msg;

	key = shm_mkkey(filename);
	if (key == -1)
		return -1;

	msqid = msgget(key, 0);
	if (msqid == -1)
		return -1;

	msg.mtype = 1;
	msg.mtext[0] = sig;
	return msgsnd(msqid, &msg, sizeof(msg.mtext[0]), 0);
#else
	return 1;
#endif
}

int _cdecl
shm_sendtokey(key, sig)
	key_t	key;
	int		sig;
{
#ifdef SYSV_IPC_AVAIL
	int		msqid;
	struct msgbuf msg;

	if (key == -1)
		return -1;

	msqid = msgget(key, 0);
	if (msqid == -1)
		return -1;

	msg.mtype = 1;
	msg.mtext[0] = sig;
	return msgsnd(msqid, &msg, sizeof(msg.mtext[0]), 0);
#else
	return 1;
#endif
}

long _cdecl
shm_waitmsg(shmdp)
	shm_dat	PNTR shmdp;
{
#ifdef SYSV_IPC_AVAIL
	int	msqid;
	struct msgbuf msg;

	msqid = shmdp->msqid;
	while (msgrcv(msqid, &msg, sizeof(msg.mtext[0]), 0, MSG_NOERROR) == -1) {
		switch (errno) {
		case EINTR:
			continue;
		default:
			perror("shm_waitmsg");
			shm_dropfile(shmdp);
			exit(1);
			/*NOTREACHED*/
		}
	}
	return msg.mtext[0];
#else
	return 0;
#endif
}
