/*
* This file has been modified for the cdrkit suite.
*
* The behaviour and appearence of the program code below can differ to a major
* extent from the version distributed by the original author(s).
*
* For details, see Changelog file distributed with the cdrkit package. If you
* received this file from another source then ask the distributing person for
* a log of modifications.
*
*/
/* @(#)fifo.c 1.49 06/02/08 Copyright 1989,1997-2006 J. Schilling */
/*
* A "fifo" that uses shared memory between two processes
*
* The actual code is a mixture of borrowed code from star's fifo.c
* and a proposal from Finn Arne Gangstad <finnag@guardian.no>
* who had the idea to use a ring buffer to handle average size chunks.
*
* Copyright (c) 1989,1997-2006 J. Schilling
*/
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* this program; see the file COPYING. If not, write to the Free Software
* Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#ifndef DEBUG
#define DEBUG
#endif
/*#define XDEBUG*/
#include <mconfig.h>
/* We always wish FIFO unless it is disabled below */
#ifndef FIFO
#define FIFO
#endif
#if defined(HAVE_OS_H) && \
defined(HAVE_CLONE_AREA) && defined(HAVE_CREATE_AREA) && \
defined(HAVE_DELETE_AREA)
#include <OS.h>
# define HAVE_BEOS_AREAS /* BeOS/Zeta */
#endif
#if !defined(HAVE_SMMAP) && !defined(HAVE_USGSHM) && \
!defined(HAVE_DOSALLOCSHAREDMEM) && !defined(HAVE_BEOS_AREAS)
#undef FIFO /* We cannot have a FIFO on this platform */
#endif
#if !defined(HAVE_FORK)
#undef FIFO /* We cannot have a FIFO on this platform */
#endif
#ifdef FIFO
#if !defined(USE_MMAP) && !defined(USE_USGSHM)
#define USE_MMAP
#endif
#ifndef HAVE_SMMAP
# undef USE_MMAP
# define USE_USGSHM /* now SYSV shared memory is the default*/
#endif
#ifdef USE_MMAP /* Only want to have one implementation */
# undef USE_USGSHM /* mmap() is preferred */
#endif
#ifdef HAVE_DOSALLOCSHAREDMEM /* This is for OS/2 */
# undef USE_MMAP
# undef USE_USGSHM
# define USE_OS2SHM
#endif
#ifdef HAVE_BEOS_AREAS /* This is for BeOS/Zeta */
# undef USE_MMAP
# undef USE_USGSHM
# undef USE_OS2SHM
# define USE_BEOS_AREAS
#endif
#include <stdio.h>
#include <stdxlib.h>
#include <unixstd.h> /* includes <sys/types.h> */
#include <utypes.h>
#include <fctldefs.h>
#if defined(HAVE_SMMAP) && defined(USE_MMAP)
#include <mmapdefs.h>
#endif
#include <waitdefs.h>
#include <standard.h>
#include <errno.h>
#include <signal.h>
#include <libport.h>
#include <schily.h>
#include "wodim.h"
#include "xio.h"
#ifdef DEBUG
#ifdef XDEBUG
FILE *ef;
#define USDEBUG1 if (debug) {if (s == owner_reader) fprintf(ef, "r"); else fprintf(ef, "w"); fflush(ef); }
#define USDEBUG2 if (debug) {if (s == owner_reader) fprintf(ef, "R"); else fprintf(ef, "W"); fflush(ef); }
#else
#define USDEBUG1
#define USDEBUG2
#endif
#define EDEBUG(a) if (debug) schily_error a
#else
#define EDEBUG(a)
#define USDEBUG1
#define USDEBUG2
#endif
#define palign(x, a) (((char *)(x)) + ((a) - 1 - (((UIntptr_t)((x)-1))%(a))))
typedef enum faio_owner {
owner_none, /* Unused in real life */
owner_writer, /* owned by process that writes into FIFO */
owner_faio, /* Intermediate state when buf still in use */
owner_reader /* owned by process that reads from FIFO */
} fowner_t;
char *onames[] = {
"none",
"writer",
"faio",
"reader",
};
typedef struct faio {
int len;
volatile fowner_t owner;
volatile int users;
short fd;
short saved_errno;
char *bufp;
} faio_t;
struct faio_stats {
long puts;
long gets;
long empty;
long full;
long done;
long cont_low;
int users;
} *sp;
#define MIN_BUFFERS 3
#define MSECS 1000
#define SECS (1000*MSECS)
/*
* Note: WRITER_MAXWAIT & READER_MAXWAIT need to be greater than the SCSI
* timeout for commands that write to the media. This is currently 200s
* if we are in SAO mode.
*/
/* microsecond delay between each buffer-ready probe by writing process */
#define WRITER_DELAY (20*MSECS)
#define WRITER_MAXWAIT (240*SECS) /* 240 seconds max wait for data */
/* microsecond delay between each buffer-ready probe by reading process */
#define READER_DELAY (80*MSECS)
#define READER_MAXWAIT (240*SECS) /* 240 seconds max wait for reader */
static char *buf;
static char *bufbase;
static char *bufend;
static long buflen; /* The size of the FIFO buffer */
extern int debug;
extern int lverbose;
void init_fifo(long);
#ifdef USE_MMAP
static char *mkshare(int size);
#endif
#ifdef USE_USGSHM
static char *mkshm(int size);
#endif
#ifdef USE_OS2SHM
static char *mkos2shm(int size);
#endif
#ifdef USE_BEOS_AREAS
static char *mkbeosshm(int size);
static void beosshm_child(void);
#endif
BOOL init_faio(track_t *trackp, int);
BOOL await_faio(void);
void kill_faio(void);
int wait_faio(void);
static void faio_reader(track_t *trackp);
static void faio_read_track(track_t *trackp);
static void faio_wait_on_buffer(faio_t *f, fowner_t s, unsigned long delay,
unsigned long max_wait);
static int faio_read_segment(int fd, faio_t *f, track_t *track, long secno,
int len);
static faio_t *faio_ref(int n);
int faio_read_buf(int f, char *bp, int size);
int faio_get_buf(int f, char **bpp, int size);
void fifo_stats(void);
int fifo_percent(BOOL addone);
void
init_fifo(long fs)
{
int pagesize;
if (fs == 0L)
return;
pagesize = getpagesize();
buflen = roundup(fs, pagesize) + pagesize;
EDEBUG(("fs: %ld buflen: %ld\n", fs, buflen));
#if defined(USE_MMAP)
buf = mkshare(buflen);
#endif
#if defined(USE_USGSHM)
buf = mkshm(buflen);
#endif
#if defined(USE_OS2SHM)
buf = mkos2shm(buflen);
#endif
#if defined(USE_BEOS_AREAS)
buf = mkbeosshm(buflen);
#endif
bufbase = buf;
bufend = buf + buflen;
EDEBUG(("buf: %p bufend: %p, buflen: %ld\n", buf, bufend, buflen));
buf = palign(buf, pagesize);
buflen -= buf - bufbase;
EDEBUG(("buf: %p bufend: %p, buflen: %ld (align %ld)\n", buf, bufend, buflen, (long)(buf - bufbase)));
/*
* Dirty the whole buffer. This can die with various signals if
* we're trying to lock too much memory
*/
fillbytes(buf, buflen, '\0');
#ifdef XDEBUG
if (debug)
ef = fopen("/tmp/ef", "w");
#endif
}
#ifdef USE_MMAP
static char *
mkshare(int size)
{
int f;
char *addr;
#ifdef MAP_ANONYMOUS /* HP/UX */
f = -1;
addr = mmap(0, mmap_sizeparm(size),
PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, f, 0);
#else
if ((f = open("/dev/zero", O_RDWR)) < 0)
comerr("Cannot open '/dev/zero'.\n");
addr = mmap(0, mmap_sizeparm(size),
PROT_READ|PROT_WRITE, MAP_SHARED, f, 0);
#endif
if (addr == (char *)-1)
comerr("Cannot get mmap for %d Bytes on /dev/zero.\n", size);
if (f >= 0)
close(f);
if (debug) errmsgno(EX_BAD, "shared memory segment attached at: %p size %d\n",
(void *)addr, size);
return (addr);
}
#endif
#ifdef USE_USGSHM
#include <sys/ipc.h>
#include <sys/shm.h>
static char *
mkshm(int size)
{
int id;
char *addr;
/*
* Unfortunately, a declaration of shmat() is missing in old
* implementations such as AT&T SVr0 and SunOS.
* We cannot add this definition here because the return-type
* changed on newer systems.
*
* We will get a warning like this:
*
* warning: assignment of pointer from integer lacks a cast
* or
* warning: illegal combination of pointer and integer, op =
*/
/* extern char *shmat();*/
if ((id = shmget(IPC_PRIVATE, size, IPC_CREAT|0600)) == -1)
comerr("shmget failed\n");
if (debug) errmsgno(EX_BAD, "shared memory segment allocated: %d\n", id);
if ((addr = shmat(id, (char *)0, 0600)) == (char *)-1)
comerr("shmat failed\n");
if (debug) errmsgno(EX_BAD, "shared memory segment attached at: %p size %d\n",
(void *)addr, size);
if (shmctl(id, IPC_RMID, 0) < 0)
comerr("shmctl failed to detach shared memory segment\n");
#ifdef SHM_LOCK
/*
* Although SHM_LOCK is standard, it seems that all versions of AIX
* ommit this definition.
*/
if (shmctl(id, SHM_LOCK, 0) < 0)
comerr("shmctl failed to lock shared memory segment\n");
#endif
return (addr);
}
#endif
#ifdef USE_OS2SHM
static char *
mkos2shm(int size)
{
char *addr;
/*
* The OS/2 implementation of shm (using shm.dll) limits the size of one shared
* memory segment to 0x3fa000 (aprox. 4MBytes). Using OS/2 native API we have
* no such restriction so I decided to use it allowing fifos of arbitrary size.
*/
if (DosAllocSharedMem(&addr, NULL, size, 0X100L | 0x1L | 0x2L | 0x10L))
comerr("DosAllocSharedMem() failed\n");
if (debug) errmsgno(EX_BAD, "shared memory allocated attached at: %p size %d\n",
(void *)addr, size);
return (addr);
}
#endif
#ifdef USE_BEOS_AREAS
static area_id faio_aid;
static void *faio_addr;
static char faio_name[32];
static char *
mkbeosshm(int size)
{
snprintf(faio_name, sizeof (faio_name), "cdrecord FIFO %lld",
(Llong)getpid());
faio_aid = create_area(faio_name, &faio_addr,
B_ANY_ADDRESS,
size,
B_NO_LOCK, B_READ_AREA|B_WRITE_AREA);
if (faio_addr == NULL) {
comerrno(faio_aid,
"Cannot get create_area for %d Bytes FIFO.\n", size);
}
if (debug) errmsgno(EX_BAD, "shared memory allocated attached at: %p size %d\n",
(void *)faio_addr, size);
return (faio_addr);
}
static void
beosshm_child()
{
/*
* Delete the area created by fork that is copy-on-write.
*/
delete_area(area_for(faio_addr));
/*
* Clone (share) the original one.
*/
faio_aid = clone_area(faio_name, &faio_addr,
B_ANY_ADDRESS, B_READ_AREA|B_WRITE_AREA,
faio_aid);
if (bufbase != faio_addr) {
errmsgno(EX_BAD, "Panic FIFO addr.\n");
return (FALSE);
}
}
#endif
static int faio_buffers;
static int faio_buf_size;
static int buf_idx = 0; /* Initialize to fix an Amiga bug */
static int buf_idx_reader = 0; /* Separate var to allow vfork() */
/* buf_idx_reader is for the process */
/* that fills the FIFO */
static pid_t faio_pid = -1;
static BOOL faio_didwait;
#ifdef AMIGA
/*
* On Amiga fork will be replaced by the speciall vfork() like call ix_vfork,
* which lets the parent asleep. The child process later wakes up the parent
* process by calling ix_fork_resume().
*/
#define fork() ix_vfork()
#define __vfork_resume() ix_vfork_resume()
#else /* !AMIGA */
#define __vfork_resume()
#endif
/*#define faio_ref(n) (&((faio_t *)buf)[n])*/
BOOL
init_faio(track_t *trackp, int bufsize)
{
int n;
faio_t *f;
int pagesize;
char *base;
if (buflen == 0L)
return (FALSE);
pagesize = getpagesize();
faio_buf_size = bufsize;
f = (faio_t *)buf;
/*
* Compute space for buffer headers.
* Round bufsize up to pagesize to make each FIFO segment
* properly page aligned.
*/
bufsize = roundup(bufsize, pagesize);
faio_buffers = (buflen - sizeof (*sp)) / bufsize;
EDEBUG(("bufsize: %d buffers: %d hdrsize %ld\n", bufsize, faio_buffers, (long)faio_buffers * sizeof (struct faio)));
/*
* Reduce buffer space by header space.
*/
n = sizeof (*sp) + faio_buffers * sizeof (struct faio);
n = roundup(n, pagesize);
faio_buffers = (buflen-n) / bufsize;
EDEBUG(("bufsize: %d buffers: %d hdrsize %ld\n", bufsize, faio_buffers, (long)faio_buffers * sizeof (struct faio)));
if (faio_buffers < MIN_BUFFERS) {
errmsgno(EX_BAD,
"write-buffer too small, minimum is %dk. Disabling.\n",
MIN_BUFFERS*bufsize/1024);
return (FALSE);
}
if (debug)
printf("Using %d buffers of %d bytes.\n", faio_buffers, faio_buf_size);
f = (faio_t *)buf;
base = buf + roundup(sizeof (*sp) + faio_buffers * sizeof (struct faio),
pagesize);
for (n = 0; n < faio_buffers; n++, f++, base += bufsize) {
/* Give all the buffers to the file reader process */
f->owner = owner_writer;
f->users = 0;
f->bufp = base;
f->fd = -1;
}
sp = (struct faio_stats *)f; /* point past headers */
sp->gets = sp->puts = sp->done = 0L;
sp->users = 1;
faio_pid = fork();
if (faio_pid < 0)
comerr("fork(2) failed");
if (faio_pid == 0) {
/*
* child (background) process that fills the FIFO.
*/
raisepri(1); /* almost max priority */
#ifdef USE_OS2SHM
DosGetSharedMem(buf, 3); /* PAG_READ|PAG_WRITE */
#endif
#ifdef USE_BEOS_AREAS
beosshm_child();
#endif
/* Ignoring SIGALRM cures the SCO usleep() bug */
/* signal(SIGALRM, SIG_IGN);*/
__vfork_resume(); /* Needed on some platforms */
faio_reader(trackp);
/* NOTREACHED */
} else {
#ifdef __needed__
Uint t;
#endif
faio_didwait = FALSE;
/*
* XXX We used to close all track files in the foreground
* XXX process. This was not correct before we used "xio"
* XXX and with "xio" it will start to fail because we need
* XXX the fd handles for the faio_get_buf() function.
*/
#ifdef __needed__
/* close all file-descriptors that only the child will use */
for (t = 1; t <= trackp->tracks; t++) {
if (trackp[t].xfp != NULL)
xclose(trackp[t].xfp);
}
#endif
}
return (TRUE);
}
BOOL
await_faio()
{
int n;
int lastfd = -1;
faio_t *f;
/*
* Wait until the reader is active and has filled the buffer.
*/
if (lverbose || debug) {
printf("Waiting for reader process to fill input buffer ... ");
flush();
}
faio_wait_on_buffer(faio_ref(faio_buffers - 1), owner_reader,
500*MSECS, 0);
if (lverbose || debug)
printf("input buffer ready.\n");
sp->empty = sp->full = 0L; /* set correct stat state */
sp->cont_low = faio_buffers; /* set cont to max value */
f = faio_ref(0);
for (n = 0; n < faio_buffers; n++, f++) {
if (f->fd != lastfd &&
f->fd == STDIN_FILENO && f->len == 0) {
errmsgno(EX_BAD, "Premature EOF on stdin.\n");
kill(faio_pid, SIGKILL);
return (FALSE);
}
lastfd = f->fd;
}
return (TRUE);
}
void
kill_faio()
{
if (faio_pid > 0)
kill(faio_pid, SIGKILL);
faio_pid=-1;
}
int
wait_faio()
{
if (faio_pid > 0 && !faio_didwait)
return (wait(0));
faio_didwait = TRUE;
return (0);
}
static void
faio_reader(track_t *trackp)
{
/* This function should not return, but _exit. */
Uint trackno;
if (debug)
printf("\nfaio_reader starting\n");
for (trackno = 1; trackno <= trackp->tracks; trackno++) {
if (debug)
printf("\nfaio_reader reading track %u\n", trackno);
faio_read_track(&trackp[trackno]);
}
sp->done++;
if (debug)
printf("\nfaio_reader all tracks read, exiting\n");
/* Prevent hang if buffer is larger than all the tracks combined */
if (sp->gets == 0)
faio_ref(faio_buffers - 1)->owner = owner_reader;
#ifdef USE_OS2SHM
DosFreeMem(buf);
sleep(30000); /* XXX If calling _exit() here the parent process seems to be blocked */
/* XXX This should be fixed soon */
#endif
if (debug)
fprintf(stderr, "\nfaio_reader _exit(0)\n");
_exit(0);
}
#ifndef faio_ref
static faio_t *
faio_ref(int n)
{
return (&((faio_t *)buf)[n]);
}
#endif
static void
faio_read_track(track_t *trackp)
{
int fd = -1;
int bytespt = trackp->secsize * trackp->secspt;
int secspt = trackp->secspt;
int l;
long secno = trackp->trackstart;
tsize_t tracksize = trackp->tracksize;
tsize_t bytes_read = (tsize_t)0;
long bytes_to_read;
if (trackp->xfp != NULL)
fd = xfileno(trackp->xfp);
if (bytespt > faio_buf_size) {
comerrno(EX_BAD,
"faio_read_track fatal: secsize %d secspt %d, bytespt(%d) > %d !!\n",
trackp->secsize, trackp->secspt, bytespt,
faio_buf_size);
}
do {
bytes_to_read = bytespt;
if (tracksize > 0) {
if ((tracksize - bytes_read) > bytespt) {
bytes_to_read = bytespt;
} else {
bytes_to_read = tracksize - bytes_read;
}
}
l = faio_read_segment(fd, faio_ref(buf_idx_reader), trackp, secno, bytes_to_read);
if (++buf_idx_reader >= faio_buffers)
buf_idx_reader = 0;
if (l <= 0)
break;
bytes_read += l;
secno += secspt;
} while (tracksize < 0 || bytes_read < tracksize);
xclose(trackp->xfp); /* Don't keep files open longer than neccesary */
}
static void
#ifdef PROTOTYPES
faio_wait_on_buffer(faio_t *f, fowner_t s,
unsigned long delay,
unsigned long max_wait)
#else
faio_wait_on_buffer(faio_t *f, fowner_t *s, unsigned long delay, unsigned long max_wait)
#endif
{
unsigned long max_loops;
if (f->owner == s)
return; /* return immediately if the buffer is ours */
if (s == owner_reader)
sp->empty++;
else
sp->full++;
max_loops = max_wait / delay + 1;
while (max_wait == 0 || max_loops--) {
USDEBUG1;
usleep(delay);
USDEBUG2;
if (f->owner == s)
return;
}
if (debug) {
errmsgno(EX_BAD,
"%lu microseconds passed waiting for %d current: %d idx: %ld\n",
max_wait, s, f->owner, (long)(f - faio_ref(0))/sizeof (*f));
}
comerrno(EX_BAD, "faio_wait_on_buffer for %s timed out.\n",
(s > owner_reader || s < owner_none) ? "bad_owner" : onames[s-owner_none]);
}
static int
faio_read_segment(int fd, faio_t *f, track_t *trackp, long secno, int len)
{
int l;
faio_wait_on_buffer(f, owner_writer, WRITER_DELAY, WRITER_MAXWAIT);
f->fd = fd;
l = fill_buf(fd, trackp, secno, f->bufp, len);
f->len = l;
f->saved_errno = geterrno();
f->owner = owner_reader;
f->users = sp->users;
sp->puts++;
return (l);
}
int
faio_read_buf(int fd, char *bp, int size)
{
char *bufp;
int len = faio_get_buf(fd, &bufp, size);
if (len > 0) {
movebytes(bufp, bp, len);
}
return (len);
}
int
faio_get_buf(int fd, char **bpp, int size)
{
faio_t *f;
int len;
again:
f = faio_ref(buf_idx);
if (f->owner == owner_faio) {
f->owner = owner_writer;
if (++buf_idx >= faio_buffers)
buf_idx = 0;
f = faio_ref(buf_idx);
}
if ((sp->puts - sp->gets) < sp->cont_low && sp->done == 0) {
EDEBUG(("gets: %ld puts: %ld cont: %ld low: %ld\n", sp->gets, sp->puts, sp->puts - sp->gets, sp->cont_low));
sp->cont_low = sp->puts - sp->gets;
}
faio_wait_on_buffer(f, owner_reader, READER_DELAY, READER_MAXWAIT);
len = f->len;
if (f->fd != fd) {
if (f->len == 0) {
/*
* If the tracksize for this track was known, and
* the tracksize is 0 mod bytespt, this happens.
*/
goto again;
}
comerrno(EX_BAD,
"faio_get_buf fatal: fd=%d, f->fd=%d, f->len=%d f->errno=%d\n",
fd, f->fd, f->len, f->saved_errno);
}
if (size < len) {
comerrno(EX_BAD,
"unexpected short read-attempt in faio_get_buf. size = %d, len = %d\n",
size, len);
}
if (len < 0)
seterrno(f->saved_errno);
sp->gets++;
*bpp = f->bufp;
if (--f->users <= 0)
f->owner = owner_faio;
return (len);
}
void
fifo_stats()
{
if (sp == NULL) /* We might not use a FIFO */
return;
errmsgno(EX_BAD, "fifo had %ld puts and %ld gets.\n",
sp->puts, sp->gets);
errmsgno(EX_BAD, "fifo was %ld times empty and %ld times full, min fill was %ld%%.\n",
sp->empty, sp->full, (100L*sp->cont_low)/faio_buffers);
}
int
fifo_percent(BOOL addone)
{
int percent;
if (sp == NULL) /* We might not use a FIFO */
return (-1);
if (sp->done)
return (100);
percent = (100*(sp->puts + 1 - sp->gets)/faio_buffers);
if (percent > 100)
return (100);
return (percent);
}
#else /* FIFO */
#include <standard.h>
#include <utypes.h> /* includes sys/types.h */
#include <schily.h>
#include "wodim.h"
void init_fifo(long);
BOOL init_faio(track_t *track, int);
BOOL await_faio(void);
void kill_faio(void);
int wait_faio(void);
int faio_read_buf(int f, char *bp, int size);
int faio_get_buf(int f, char **bpp, int size);
void fifo_stats(void);
int fifo_percent(BOOL addone);
void init_fifo(long fs)
{
errmsgno(EX_BAD, "Fifo not supported.\n");
}
BOOL init_faio(track_t *track,
int bufsize /* The size of a single transfer buffer */)
{
return (FALSE);
}
BOOL await_faio()
{
return (TRUE);
}
void kill_faio()
{
}
int wait_faio()
{
return (0);
}
int faio_read_buf(int fd, char *bp, int size)
{
return (0);
}
int faio_get_buf(int fd, char **bpp, int size)
{
return (0);
}
void fifo_stats()
{
}
int fifo_percent(BOOL addone)
{
return (-1);
}
#endif /* FIFO */