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

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

#ifndef OS_UNIX_LINUX
#define SEMCTL_RMID(semid)	semctl(semid, 0, IPC_RMID)
#else
#define SEMCTL_RMID(semid)	semctl(semid, 0, IPC_RMID, NULL)
#endif

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

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

VoidPtr LIBCALL
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 LIBCALL
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	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_RMID(semid);
		(void) fclose(fp);
		return NULL;
	}
	if (lockfile == TRUE) {
		if (shmctl(shmid, SHM_LOCK, NULL) != 0 && verbose) {
			perror("shmctl: locking segment in physical memory");
		}
	}

	/* 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_RMID(semid);
		fclose(fp);
		return NULL;
	}

    cp = (char *)shmat(shmid, NULL, 0);
    shmd.shmaddr = (void *)cp;
	if (cp == NULL) {
		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_RMID(semid);
		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_RMID(semid);
		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");
	}

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

	shmdp = (shm_dat *)Nlm_MemDup(&shmd, sizeof(shmd));
	shmdp->filename = Nlm_StrSave(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 LIBCALL
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
	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) {
		if (verbose)
			perror("shm_attfile");
		return NULL;
	}
	shmdp = (shm_dat *)Nlm_MemDup(&shmd, sizeof(shmd));
	shmdp->filename = Nlm_StrSave(filename);
	shmdp->filesize = sbuf.st_size;
	return shmdp;
#else
	return NULL;
#endif
}


/*
	shm_detach() -- detach the shared memory segment pointed to by 'sp'
*/
int LIBCALL
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 LIBCALL
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_RMID(semid) | shmctl(shmid, IPC_RMID, NULL);
	if (msqid != -1)
		i |= msgctl(msqid, IPC_RMID, NULL);
	return i;
#else
	return 1;
#endif
}


int LIBCALL
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 LIBCALL
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 LIBCALL
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
}
