/**********************************************************************
 *	ex:se ts=4 sw=4 ai:
 *
 *	Shared Memory Communication Library.
 *
 *	Author: Gene H. Olson
 *	        Smartware Consulting
 *
 *	This is FREE software in the PUBLIC DOMAIN.
 *
 *	"Everything FREE contains no guarantee."
 *
 ***********************************************************************
 *
 *	This library is designed to be a re-usable shared-library
 *	package for high-speed communication between cooperating
 *	tasks using System V shared memory.
 *
 *	Unfortunately the interface to system V shared libraries
 *	is sufficiently awkward that it is difficult to create
 *	a simple interface.  This was the best I could think of.
 *
 *	In this interface, a parent program creates a shared
 *	memory segment for use with its children using the
 *	share_create() function.  This function returns 4 pipe
 *	file descriptors to be selectively passed to its children.
 *
 *	Two of the file descriptors (pfd[2]) are passed to the
 *	producer process and two are passed to the consumer
 *	process.  The share_open() function is then called
 *	with the appropriate file descriptors, and shared
 *	memory is mapped into processor space.
 *
 *	In passing file descriptors, the producer task should
 *	always be passed pfd[0] and pfd[1], and cfd[1] must
 *	be closed.  If SIGPIPE interrupts are desired when
 *	sending a buffer to a terminated consumer process,
 *	cfd[0] should also be closed;  otherwise it should be
 *	left open and never accessed.  The consumer task
 *	should receive cfd[0], cfd[1] and close pfd[1].
 *	Usually pfd[0] is left open in the consumer task so
 *	it won't get SIGPIPE interrupts when returning buffers
 *	after the producer process terminates.
 *
 *	Both producer and consumer processes obtain a memory
 *	buffer to pass to the other by calling share_get().
 *	They pass the buffer to the opposite task by calling
 *	share_put().  share_get() blocks as necessary to wait
 *	for a buffer.   The only real difference between the
 *	producer and consumer processes is that the entire
 *	buffer list is initially queued to the producer.
 *
 *	When communication is complete, both producer and
 *	consumer can either terminate, or call share_close()
 *	to deallocate resources.  However at least one of
 *	them must always call share_destroy() to deallocate
 *	the shared memory segments.  Failure to do this will
 *	leave the segments hanging out there chewing up
 *	swap space.
 *
 *	Note that the SHARE object malloc()ed throughout these
 *	operations is not generally free()ed.  This wastes a
 *	bit of memory but simplifies caller programming.
 *
 *	In keeping with the current craze for object-oriented
 *	programming and information-hiding, the SHARE object
 *	which controls all of this is opaque to the calling
 *	routines, and should be declared as type (char *).
 *	How you reconcile this with lint is unresolved,
 *	but at least this module lints clean.
 *
 *	The module smtest.c provides a simple example of how
 *	to use the library.
 **********************************************************************/

#ifndef lint
static char sccs[] = "@(#)share.c	1.10 11/3/90" ;
#endif

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

#include <assert.h>
#include <stdio.h>
#include <errno.h>

extern char *shmat() ;
extern char *memset() ;
extern void exit() ;
extern int	errno ;

/*
 *	Patch for Xenix which tests valid memory for reads & writes
 *	off by 1 byte.
 */

#ifdef M_XENIX
#	define MKLUDGE	2
#else
#	define MKLUDGE	0
#endif

/*
 *	Shared memory and pipe item descriptors.
 */

typedef struct sh_struct SHARE ;

#define SHARE_MAXID		16			/* Max shared memory ID's */

struct sh_struct
{
	int*	sh_rbuf ;				/* Pipe receive buffer */
	int		sh_fd[2] ;				/* Pipe read file descriptors */
	int		sh_nid ;				/* Number of allocated IDs */
	int		sh_nbuf ;				/* Number of buffers allocated */
	int		sh_nmap ;				/* Number of segments mapped */
	int		sh_in ;					/* Circular buffer in pointer */
	int		sh_out ;				/* Circular buffer in pointer */
	int		sh_bufsize ;			/* Buffer size */
	int		sh_rin ;				/* Pipe buffer IN index */
	int		sh_rout ;				/* Pipe buffer OUT index */
	int		sh_id[SHARE_MAXID] ;	/* Shared memory ID */
	char*	sh_base[SHARE_MAXID] ;	/* Shared memory base */
	int		sh_len[SHARE_MAXID];	/* Shared segment length */
} ;

#if lint
#	define	malloc(x)	0
#endif


/**************************************************************
 *	share_create - Create a shared memory entity in the parent
 *	               of two tasks to share memory.
 **************************************************************/

SHARE *
share_create(pfd, cfd, bufsize, nbuf)
int *pfd ;						/* Producer file descriptors */
int *cfd ;						/* Consumer file descriptors */
int bufsize ;					/* Buffer size */
int nbuf ;						/* Number of buffers */
{
	register int id ;
	register int i ;
	register int n ;
	register int s ;
	register SHARE* sh ;
	int size ;
	int fd[4] ;

	if (nbuf > 500) return(0) ;

	sh = (SHARE *) malloc(sizeof(SHARE)) ;
	if (sh == 0) return(0) ;

	(void) memset((char *)sh, 0, sizeof(SHARE)) ;

	sh->sh_fd[0] = sh->sh_fd[1] = -1 ;

	sh->sh_bufsize = bufsize ;

	/*
	 *	Allocate pipes for bi-directional communication
	 *	between the producer and consumer processes.
	 */

	if (pipe(fd) == -1) goto fail ;

	if (pipe(fd + 2) == -1)
	{
		(void) close(fd[0]) ;
		(void) close(fd[1]) ;
		goto fail ;
	}

	pfd[0] = fd[0] ;
	pfd[1] = fd[3] ;

	cfd[0] = fd[2] ;
	cfd[1] = fd[1] ;

	/*
	 *	Allocate the required number of buffers in as many
	 *	segments as needed.
	 *
	 *	When the system will not provide us with a single
	 *	segment of the requested size, repeatedly request
	 *	a segment of the next lower power-of-two size.
	 */

	for (n = nbuf ; n ; n -= s)
	{
		s = n ;

		while (s > 0)
		{
			if ((id = shmget(IPC_PRIVATE, s * bufsize + MKLUDGE, 0600)) >= 0)
			{
				sh->sh_id[sh->sh_nid] = id ;
				sh->sh_len[sh->sh_nid] = s ;
				sh->sh_nid++ ;
				break ;
			}

			i = s * bufsize + MKLUDGE - 1 ;
			while (i & (i-1)) i = i & (i-1) ;
			s = (i - MKLUDGE) / bufsize ;
		}

		if (s <= 0)
		{
			if (sh->sh_nid == 0) goto fail ;
			break ;
		}
		
		sh->sh_nbuf += s ;

		if (sh->sh_nid == SHARE_MAXID) break ;
	}

	/*
	 *	Write out the shared memory descriptor
	 *	to both pipes.
	 */

	if (write(cfd[1], (char *)sh, sizeof(SHARE)) == -1)
	{
		errno = EIO ;
		goto fail ;
	}

	if (write(pfd[1], (char *)sh, sizeof(SHARE)) == -1)
	{
		errno = EIO ;
		goto fail ;
	}

	/*
	 *	Allocate all buffers to the producer process.
	 */

	size = 0 ;

	for (i = 0 ; i < sh->sh_nbuf ; i++)
	{
		if (write(cfd[1], (char *)&size, sizeof(int)) == -1) goto fail ;
	}

	return(sh) ;

fail:
	share_destroy(sh) ;
	free((char*)sh) ;

	return(0) ;
}



/*******************************************************
 *	share_close - Close a SHARE descriptor.
 *******************************************************/

share_close(sh)
register SHARE *sh ;
{
	register int i ;

	if (sh->sh_rbuf)
	{
		free((char *) sh->sh_rbuf) ;
		sh->sh_rbuf = 0 ;
	}

	for (i = 0 ; i < sh->sh_nmap ; i++)
	{
		(void) shmdt(sh->sh_base[i]) ;
	}

	sh->sh_nmap = 0 ;

	for (i = 0 ; i < 2 ; i++)
	{
		if (sh->sh_fd[i] != -1)
		{
			(void) close(sh->sh_fd[i]) ;
			sh->sh_fd[i] = -1 ;
		}
	}
}



/*******************************************************
 *	share_destroy - Remove share descritors.
 *******************************************************/

share_destroy(sh)
register SHARE *sh ;				/* Shared memory descriptor */
{
	int i ;

	share_close(sh) ;

	for (i = 0 ; i < sh->sh_nid ; i++)
	{
		(void) shmctl(sh->sh_id[i], IPC_RMID, (struct shmid_ds *)0) ;
	}

	sh->sh_nid = 0 ;
}



/*****************************************************
 *	share_open - Open shared memory descriptor.
 *****************************************************/

SHARE *
share_open(fd)
int fd[2] ;
{
	register SHARE *sh ;
	register char *p ;
	register int i ;

	/*
	 *	Read in the Share structure from the read file
	 *	descriptor.
	 */

	sh = (SHARE *)malloc(sizeof(SHARE)) ;

	if (sh == 0) return(0) ;

	if (read(fd[0], (char *)sh, sizeof(SHARE)) != sizeof(SHARE))
	{
		free((char *)sh) ;
		return(0) ;
	}

	/*
	 *	Initialize the structure.
	 */

	sh->sh_fd[0] = fd[0] ;
	sh->sh_fd[1] = fd[1] ;

	/*
	 *	Map in the shared memory segment.
	 */

	for (i = 0 ; i < sh->sh_nid ; i++)
	{
		p = shmat(sh->sh_id[i], (char *)0, 0) ;

		if (p == 0)
		{
			share_destroy(sh) ;
			return(0) ;
		}

		sh->sh_nmap++ ;
		sh->sh_base[i] = p ;

#if defined(sun) && 0
		(void) mlock(sh->sh_base[i], (unsigned)(sh->sh_bufsize*sh->sh_len[i])) ;
#endif
	}

	return(sh) ;
}



/*******************************************************
 *	share_get - Fetch next block from shared memory.
 *******************************************************/

int
share_get(sh, buf, n)
register SHARE *sh ;
char **buf ;
int *n ;
{
	register int i ;
	register int r ;
	register int index ;

	/*
	 *	For efficiency (probably misguided) allocate
	 *	a buffer of 64 items.
	 */

	if (sh->sh_rbuf == 0)
	{
		sh->sh_rbuf = (int *)malloc(64 * sizeof(int)) ;
		sh->sh_rin = 0 ;
		sh->sh_rout = 0 ;
	}

	/*
	 *	When the buffer is dry, read in another block
	 *	of data sizes.
	 */

	if (sh->sh_rin == sh->sh_rout)
	{
		r = read(sh->sh_fd[0], (char *) sh->sh_rbuf, 64 * sizeof(int)) ;

		if (r <= 0) return(r) ;

		assert((r % sizeof(int)) == 0) ;

		sh->sh_rin = 0 ;
		sh->sh_rout = r / sizeof(int) ;
	}

	/*
	 *	Get the next buffer pointer & count.
	 */

	*n = sh->sh_rbuf[sh->sh_rin++] ;

#if DEBUG >= 2
	if (1)
	{
		char b[100] ;
		sprintf(b, "GET      %5d   %6d\n", sh->sh_in, *n) ;
		write(2, b, strlen(b)) ;
	}
#endif

	index = sh->sh_out ;

	for (i = 0 ;; i++)
	{
		assert(i < sh->sh_nid) ;
		
		if (index < sh->sh_len[i])
		{
			*buf = sh->sh_base[i] + sh->sh_bufsize * index ;
			break ;
		}

		index -= sh->sh_len[i] ;
	}

	if (++sh->sh_out == sh->sh_nbuf) sh->sh_out = 0 ;

	return(1) ;
}



/***************************************************************
 *	share_put - Put a data buffer on the queue to the remote.
 ***************************************************************/

int
share_put(sh, buf, n)
SHARE *sh ;							/* Share pointer */
char *buf ;							/* Buffer pointer */
int n ;								/* Number of bytes */
{
	register int i ;
	register int r ;
	register int index ;

	index = sh->sh_in ;

	for (i = 0 ;; i++)
	{
		assert(i < sh->sh_nid) ;
		
		if (index < sh->sh_len[i])
		{
			assert(buf == sh->sh_base[i] + sh->sh_bufsize * index) ;
			break ;
		}

		index -= sh->sh_len[i] ;
	}

#if DEBUG >= 2
	if (1)
	{
		char b[100] ;
		sprintf(b, "PUT %5d        %6d\n", sh->sh_in, n) ;
		write(2, b, strlen(b)) ;
	}
#endif

	if (++sh->sh_in == sh->sh_nbuf) sh->sh_in = 0 ;

	r = write(sh->sh_fd[1], (char *)&n, sizeof(n)) ;

	return(r <= 0 ? r : 1) ;
}
