Blame src/libout123/xfermem.c

Packit c32a2d
/*
Packit c32a2d
	xfermem: unidirectional fast pipe
Packit c32a2d
Packit c32a2d
	copyright ?-2015 by the mpg123 project - free software under the terms of the LGPL 2.1
Packit c32a2d
	see COPYING and AUTHORS files in distribution or http://mpg123.org
Packit c32a2d
	initially written by Oliver Fromme
Packit c32a2d
	old timestamp: Sun Apr  6 02:26:26 MET DST 1997
Packit c32a2d
Packit c32a2d
	See xfermem.h for documentation/description.
Packit c32a2d
*/
Packit c32a2d
Packit c32a2d
#include "config.h"
Packit c32a2d
#include "compat.h"
Packit c32a2d
#include "xfermem.h"
Packit c32a2d
#include <string.h>
Packit c32a2d
#include <errno.h>
Packit c32a2d
#include <sys/uio.h>
Packit c32a2d
#include <sys/mman.h>
Packit c32a2d
#include <sys/socket.h>
Packit c32a2d
#include <fcntl.h>
Packit c32a2d
Packit c32a2d
#ifndef HAVE_MMAP
Packit c32a2d
#include <sys/ipc.h>
Packit c32a2d
#include <sys/shm.h>
Packit c32a2d
#endif
Packit c32a2d
Packit c32a2d
#include "debug.h"
Packit c32a2d
Packit c32a2d
#if defined (HAVE_MMAP) && defined(MAP_ANONYMOUS) && !defined(MAP_ANON)
Packit c32a2d
#define MAP_ANON MAP_ANONYMOUS
Packit c32a2d
#endif
Packit c32a2d
Packit c32a2d
void xfermem_init (txfermem **xf, size_t bufsize, size_t msize, size_t skipbuf)
Packit c32a2d
{
Packit c32a2d
	size_t regsize = bufsize + msize + skipbuf + sizeof(txfermem);
Packit c32a2d
Packit c32a2d
#ifdef HAVE_MMAP
Packit c32a2d
#  ifdef MAP_ANON
Packit c32a2d
	if ((*xf = (txfermem *) mmap(0, regsize, PROT_READ | PROT_WRITE,
Packit c32a2d
			MAP_ANON | MAP_SHARED, -1, 0)) == (txfermem *) -1) {
Packit c32a2d
		perror ("mmap()");
Packit c32a2d
		exit (1);
Packit c32a2d
	}
Packit c32a2d
#  else
Packit c32a2d
	int devzero;
Packit c32a2d
	if ((devzero = open("/dev/zero", O_RDWR, 0)) == -1) {
Packit c32a2d
		perror ("open(/dev/zero)");
Packit c32a2d
		exit (1);
Packit c32a2d
	}
Packit c32a2d
	if ((*xf = (txfermem *) mmap(0, regsize, PROT_READ | PROT_WRITE,
Packit c32a2d
			MAP_SHARED, devzero, 0)) == (txfermem *) -1) {
Packit c32a2d
		perror ("mmap()");
Packit c32a2d
		exit (1);
Packit c32a2d
	}
Packit c32a2d
	close (devzero);
Packit c32a2d
#  endif
Packit c32a2d
#else
Packit c32a2d
	struct shmid_ds shmemds;
Packit c32a2d
	int shmemid;
Packit c32a2d
	if ((shmemid = shmget(IPC_PRIVATE, regsize, IPC_CREAT | 0600)) == -1) {
Packit c32a2d
		perror ("shmget()");
Packit c32a2d
		exit (1);
Packit c32a2d
	}
Packit c32a2d
	if ((*xf = (txfermem *) shmat(shmemid, 0, 0)) == (txfermem *) -1) {
Packit c32a2d
		perror ("shmat()");
Packit c32a2d
		shmctl (shmemid, IPC_RMID, &shmemds);
Packit c32a2d
		exit (1);
Packit c32a2d
	}
Packit c32a2d
	if (shmctl(shmemid, IPC_RMID, &shmemds) == -1) {
Packit c32a2d
		perror ("shmctl()");
Packit c32a2d
		xfermem_done (*xf);
Packit c32a2d
		exit (1);
Packit c32a2d
	}
Packit c32a2d
#endif
Packit c32a2d
	if (socketpair(AF_UNIX, SOCK_STREAM, 0, (*xf)->fd) < 0) {
Packit c32a2d
		perror ("socketpair()");
Packit c32a2d
		xfermem_done (*xf);
Packit c32a2d
		exit (1);
Packit c32a2d
	}
Packit c32a2d
	(*xf)->freeindex = (*xf)->readindex = 0;
Packit c32a2d
	(*xf)->data = ((char *) *xf) + sizeof(txfermem) + msize;
Packit c32a2d
	(*xf)->metadata = ((char *) *xf) + sizeof(txfermem);
Packit c32a2d
	(*xf)->size = bufsize;
Packit c32a2d
	(*xf)->metasize = msize + skipbuf;
Packit c32a2d
}
Packit c32a2d
Packit c32a2d
void xfermem_done (txfermem *xf)
Packit c32a2d
{
Packit c32a2d
	if(!xf)
Packit c32a2d
		return;
Packit c32a2d
#ifdef HAVE_MMAP
Packit c32a2d
	/* Here was a cast to (caddr_t) ... why? Was this needed for SunOS?
Packit c32a2d
	   Casting to (void*) should silence compilers in case of funny
Packit c32a2d
	   prototype for munmap(). */
Packit c32a2d
	munmap ( (void*)xf, xf->size + xf->metasize + sizeof(txfermem));
Packit c32a2d
#else
Packit c32a2d
	if (shmdt((void *) xf) == -1) {
Packit c32a2d
		perror ("shmdt()");
Packit c32a2d
		exit (1);
Packit c32a2d
	}
Packit c32a2d
#endif
Packit c32a2d
}
Packit c32a2d
Packit c32a2d
void xfermem_init_writer (txfermem *xf)
Packit c32a2d
{
Packit c32a2d
	if(xf)
Packit c32a2d
		close (xf->fd[XF_READER]);
Packit c32a2d
	debug1("xfermem writer fd=%i", xf->fd[XF_WRITER]);
Packit c32a2d
}
Packit c32a2d
Packit c32a2d
void xfermem_init_reader (txfermem *xf)
Packit c32a2d
{
Packit c32a2d
	if(xf)
Packit c32a2d
		close (xf->fd[XF_WRITER]);
Packit c32a2d
	debug1("xfermem reader fd=%i", xf->fd[XF_READER]);
Packit c32a2d
}
Packit c32a2d
Packit c32a2d
size_t xfermem_get_freespace (txfermem *xf)
Packit c32a2d
{
Packit c32a2d
	size_t freeindex, readindex;
Packit c32a2d
Packit c32a2d
	if(!xf)
Packit c32a2d
		return 0;
Packit c32a2d
Packit c32a2d
	if ((freeindex = xf->freeindex) < 0
Packit c32a2d
			|| (readindex = xf->readindex) < 0)
Packit c32a2d
		return (0);
Packit c32a2d
	if (readindex > freeindex)
Packit c32a2d
		return ((readindex - freeindex) - 1);
Packit c32a2d
	else
Packit c32a2d
		return ((xf->size - (freeindex - readindex)) - 1);
Packit c32a2d
}
Packit c32a2d
Packit c32a2d
size_t xfermem_get_usedspace (txfermem *xf)
Packit c32a2d
{
Packit c32a2d
	size_t freeindex, readindex;
Packit c32a2d
Packit c32a2d
	if(!xf)
Packit c32a2d
		return 0;
Packit c32a2d
Packit c32a2d
	if ((freeindex = xf->freeindex) < 0
Packit c32a2d
			|| (readindex = xf->readindex) < 0)
Packit c32a2d
		return (0);
Packit c32a2d
	if (freeindex >= readindex)
Packit c32a2d
		return (freeindex - readindex);
Packit c32a2d
	else
Packit c32a2d
		return (xf->size - (readindex - freeindex));
Packit c32a2d
}
Packit c32a2d
Packit c32a2d
static int xfermem_getcmd_raw (int fd, int block, byte *cmds, int count)
Packit c32a2d
{
Packit c32a2d
	fd_set selfds;
Packit c32a2d
	int ret;
Packit c32a2d
Packit c32a2d
	for (;;) {
Packit c32a2d
		struct timeval selto = {0, 0};
Packit c32a2d
Packit c32a2d
		FD_ZERO (&selfds);
Packit c32a2d
		FD_SET (fd, &selfds);
Packit c32a2d
#ifdef HPUX
Packit c32a2d
		switch (select(FD_SETSIZE, (int *) &selfds, NULL, NULL, block ? NULL : &selto))
Packit c32a2d
#else
Packit c32a2d
		switch (select(FD_SETSIZE, &selfds, NULL, NULL, block ? NULL : &selto))
Packit c32a2d
#endif
Packit c32a2d
		{
Packit c32a2d
			case 0:
Packit c32a2d
				if (!block)
Packit c32a2d
					return (0);
Packit c32a2d
				continue;
Packit c32a2d
			case -1:
Packit c32a2d
				if (errno == EINTR)
Packit c32a2d
					continue;
Packit c32a2d
				return (-2);
Packit c32a2d
			case 1:
Packit c32a2d
				if (FD_ISSET(fd, &selfds))
Packit c32a2d
					switch((ret=read(fd, cmds, count)))
Packit c32a2d
					{
Packit c32a2d
						case 0: /* EOF */
Packit c32a2d
							return (-1);
Packit c32a2d
						case -1:
Packit c32a2d
							if (errno == EINTR)
Packit c32a2d
								continue;
Packit c32a2d
							return (-3);
Packit c32a2d
						default:
Packit c32a2d
							return ret;
Packit c32a2d
					}
Packit c32a2d
				else /* ?!? */
Packit c32a2d
					return (-5);
Packit c32a2d
			default: /* ?!? */
Packit c32a2d
				return (-6);
Packit c32a2d
		}
Packit c32a2d
	}
Packit c32a2d
}
Packit c32a2d
Packit c32a2d
/* Verbose variant for debugging communication. */
Packit c32a2d
int xfermem_getcmd(int fd, int block)
Packit c32a2d
{
Packit c32a2d
	byte cmd;
Packit c32a2d
	int res = xfermem_getcmd_raw(fd, block, &cmd, 1);
Packit c32a2d
	debug3("xfermem_getcmd(%i, %i) = %i", fd, block, res == 1 ? cmd : res);
Packit c32a2d
	return res == 1 ? cmd : res;
Packit c32a2d
}
Packit c32a2d
Packit c32a2d
int xfermem_getcmds(int fd, int block, byte *cmds, int count)
Packit c32a2d
{
Packit c32a2d
	int res = xfermem_getcmd_raw(fd, block, cmds, count);
Packit c32a2d
	debug5("xfermem_getcmds(%i, %i, %p, %i) = %i"
Packit c32a2d
	,	fd, block, (void*)cmds, count
Packit c32a2d
	,	res);
Packit c32a2d
	return res;
Packit c32a2d
}
Packit c32a2d
Packit c32a2d
Packit c32a2d
int xfermem_putcmd (int fd, byte cmd)
Packit c32a2d
{
Packit c32a2d
	for (;;) {
Packit c32a2d
		switch (write(fd, &cmd, 1)) {
Packit c32a2d
			case 1:
Packit c32a2d
				debug2("xfermem_putcmd(%i, %i) = 1", fd, cmd);
Packit c32a2d
				return (1);
Packit c32a2d
			case -1:
Packit c32a2d
				if (errno != EINTR)
Packit c32a2d
				{
Packit c32a2d
					debug3("xfermem_putcmd(%i, %i) = -1 (%s)"
Packit c32a2d
					,	fd, cmd, strerror(errno));
Packit c32a2d
					return (-1);
Packit c32a2d
				}
Packit c32a2d
		}
Packit c32a2d
	}
Packit c32a2d
}
Packit c32a2d
Packit c32a2d
/*
Packit c32a2d
	There is a basic assumetry between reader and writer:
Packit c32a2d
	The reader does work in periodic pieces and can be relied upon to
Packit c32a2d
	eventually answer a call. It is important that it does not block
Packit c32a2d
	for a significant duration unless it has really nothing to do.
Packit c32a2d
Packit c32a2d
	The writer is more undefined in its behaviour, it is controlled by
Packit c32a2d
	external agents. You cannot rely on it answering synchronization
Packit c32a2d
	requests in a timely manner. But on the other hand, it can be left
Packit c32a2d
	hanging for a while. The critical side is that of the reader.
Packit c32a2d
Packit c32a2d
	Because of that, it is only sensible to provide a voluntary
Packit c32a2d
	xfermem_writer_block() here. The reader does not need such a function.
Packit c32a2d
	Only if it has nothing else to do, it will simply block on
Packit c32a2d
	xfermem_getcmd(), and the writer promises to xfermem_putcmd() when
Packit c32a2d
	something happens.
Packit c32a2d
Packit c32a2d
	The writer always sends a wakeup command to the reader since the latter
Packit c32a2d
	could be in the process of putting itself to sleep right now, without
Packit c32a2d
	a flag indicating so being set yet.
Packit c32a2d
Packit c32a2d
	The reader periodically reads from its file descriptor so that it does
Packit c32a2d
	not get clogged up with pending messages. It will only (and always) send
Packit c32a2d
	a wakeup call in response to a received command.
Packit c32a2d
*/
Packit c32a2d
Packit c32a2d
/* Wait a bit to get a sign of life from the reader.
Packit c32a2d
   Returns -1 if even that did not work. */
Packit c32a2d
int xfermem_writer_block(txfermem *xf)
Packit c32a2d
{
Packit c32a2d
	int myfd = xf->fd[XF_WRITER];
Packit c32a2d
	int result;
Packit c32a2d
Packit c32a2d
	xfermem_putcmd(myfd, XF_CMD_PING);
Packit c32a2d
	result = xfermem_getcmd(myfd, TRUE);
Packit c32a2d
	/* Only a pong to my ping is the expected good answer.
Packit c32a2d
	   Everything else is a problem to be communicated. */
Packit c32a2d
	return (result == XF_CMD_PONG) ? 0 : result;
Packit c32a2d
}
Packit c32a2d
Packit c32a2d
/* Return: 0 on success, -1 on communication error, > 0 for
Packit c32a2d
   error on buffer side, some special return code from buffer to be
Packit c32a2d
   evaluated. */
Packit c32a2d
int xfermem_write(txfermem *xf, void *buffer, size_t bytes)
Packit c32a2d
{
Packit c32a2d
	if(buffer == NULL || bytes < 1) return 0;
Packit c32a2d
Packit c32a2d
	/* You weren't so braindead not allocating enough space at all, right? */
Packit c32a2d
	while (xfermem_get_freespace(xf) < bytes)
Packit c32a2d
	{
Packit c32a2d
		int cmd = xfermem_writer_block(xf);
Packit c32a2d
		if(cmd) /* Non-successful wait. */
Packit c32a2d
			return cmd;
Packit c32a2d
	}
Packit c32a2d
	/* Now we have enough space. copy the memory, possibly with the wrap. */
Packit c32a2d
	if(xf->size - xf->freeindex >= bytes)
Packit c32a2d
	{	/* one block of free memory */
Packit c32a2d
		memcpy(xf->data+xf->freeindex, buffer, bytes);
Packit c32a2d
	}
Packit c32a2d
	else
Packit c32a2d
	{ /* two blocks */
Packit c32a2d
		size_t endblock = xf->size - xf->freeindex;
Packit c32a2d
		memcpy(xf->data+xf->freeindex, buffer, endblock);
Packit c32a2d
		memcpy(xf->data, (char*)buffer + endblock, bytes-endblock);
Packit c32a2d
	}
Packit c32a2d
	/* Advance the free space pointer, including the wrap. */
Packit c32a2d
	xf->freeindex = (xf->freeindex + bytes) % xf->size;
Packit c32a2d
	/* Always notify the buffer process. */
Packit c32a2d
	debug("write waking");
Packit c32a2d
	return xfermem_putcmd(xf->fd[XF_WRITER], XF_CMD_DATA) < 0
Packit c32a2d
	?	-1
Packit c32a2d
	:	0;
Packit c32a2d
}