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