/**********************************************************************
fifo, for use with mkfifo and auduplex (Linux, Solaris, IRIX)

$Id: fifo.c,v 1.16 2003/01/19 23:17:09 purnhage Exp $

Source file: fifo.c

(c) Heiko Purnhagen 2000-2003
e-mail: purnhage@tnt.uni-hannover.de
WWW:    http://www.tnt.uni-hannover.de/~purnhage/

This program is available under the terms of the GNU General Public
License. See file COPYING for further details. All usual disclaimers
apply!

Authors:
HP    Heiko Purnhagen, Uni Hannover <purnhage@tnt.uni-hannover.de>

Changes:
06-Jun-2000       1st version
04-Sep-2000       added option w (similar to /usr/bin/buffer -p)
05-Sep-2000       added option r, n0 uses select()
06-Sep-2000       fixed hang-up, now using fifofill
10-Sep-2000       added minfill, maxfill
27-Jul-2001 HP    fixed SGI CLK_TCK
20-Mar-2002 HP    included string.h for FD_ZERO()
*********************************************************************/

#include <stdlib.h>
#include <stdio.h>
#ifdef MSVC
#include <io.h> /* open(), close(), write(), read() */
#include <winsock.h>
#else
#include <unistd.h>
#endif
#include <fcntl.h>
#include <errno.h>
#include <string.h>

#ifdef Linux
#include <sys/time.h>
#endif

#ifdef IRIX
#include <limits.h>
#endif

#ifdef Cygwin
#include <sys/select.h>
#endif


/* ---------- declarations ---------- */

#define PROGVER "fifo V0.5 20-Mar-2002"
#define CVSID "$Id: fifo.c,v 1.16 2003/01/19 23:17:09 purnhage Exp $"
#ifndef DISTVER         /* set in Makefile */
#define DISTVER "<unknown>"
#endif

#ifndef MSVC
#define min(a,b) ((a) < (b) ? (a) : (b))
#define max(a,b) ((a) > (b) ? (a) : (b))
#endif

/* Nap() */
/* nap for ms milliseconds */

void Nap (
  int ms)			/* in: time to nap [ms] */
{
#ifdef Linux
  struct timeval tv;

  tv.tv_sec = 0;
  tv.tv_usec = min(999999,max(1,ms)*1000);
  select(0,NULL,NULL,NULL,&tv);
#endif  

#ifdef Solaris
  struct timeval tv;

  tv.tv_sec = 0;
  tv.tv_usec = min(999999,max(1,ms)*1000);
  select(0,NULL,NULL,NULL,&tv);
#endif  

#ifdef IRIX
  sginap(max(1,(ms*CLK_TCK)/1000));	/* CLK_TCK = 100 ticks/sec */
#endif
}


int main (int argc, char *argv[])
{
  long fifosize = 0x100000;
  long blocksize = 0x10000;
  long writedelay = 0x0;
  long naptime = 0;
  long readidx = 0;
  long writeidx = 0;
  long fifofill = 0;
  long minfill;
  long maxfill = 0;
  long readoff = 0;
  long writeoff = 0;
  long debug = 0;
  long debugrate = 1;
  long debugcnt = 0;
  char *buf;
  long rs,r,ws,w;
  int i;
  fd_set rdfs,wrfs;

  for (i=1; i<argc; i++)
    switch (*argv[i]) {
    case 'f':
      if(sscanf(argv[i]+1,"%li",&fifosize)!=1) {
	fprintf(stderr,"fifo: fxxx format error\n");
	exit(-1);
      }
      break;
    case 'b':
      if(sscanf(argv[i]+1,"%li",&blocksize)!=1) {
	fprintf(stderr,"fifo: bxxx format error\n");
	exit(-1);
      }
      break;
    case 'w':
      if(sscanf(argv[i]+1,"%li",&writedelay)!=1) {
	fprintf(stderr,"fifo: wxxx format error\n");
	exit(-1);
      }
      break;
    case 'n':
      if(sscanf(argv[i]+1,"%li",&naptime)!=1) {
	fprintf(stderr,"fifo: nxxx format error\n");
	exit(-1);
      }
      break;
    case 'd':
      if(sscanf(argv[i]+1,"%li",&debug)!=1) {
	fprintf(stderr,"fifo: dxxx format error\n");
	exit(-1);
      }
      break;
    case 'r':
      if(sscanf(argv[i]+1,"%li",&debugrate)!=1) {
	fprintf(stderr,"fifo: rxxx format error\n");
	exit(-1);
      }
      break;
    default:
    fprintf(stderr,
	    "%s\n"
	    "%s\n"
	    "usage: fifo [options] <in >out\n"
	    "options: fxxx   fifo size xxx bytes (dflt: %ld)\n"
	    "         bxxx   block size xxx bytes (dflt: %ld)\n"
	    "         wxxx   write delay (start after xxx bytes) (dflt: %ld)\n"
	    "         nxxx   nap time [ms] (0 = wait for event) (dflt: %ld)\n"
	    "         dxxx   debug level xxx (dflt: %ld)\n"
	    "         rxxx   debug update rate xxx (dflt: %ld)\n"
	    "\n",
	    PROGVER "   " DISTVER,CVSID,
	    fifosize,blocksize,writedelay,naptime,debug,debugrate);
    exit(-1);
    }

  if (blocksize>fifosize || blocksize<=0 || fifosize<=0 ||
      writedelay<0 || writedelay>fifosize ||
      naptime<0 || debugrate<0) {
    fprintf(stderr,
	    "fifo: illegal option value error\n");
    exit(-1);
  }
  if (!(buf=(char*)malloc(fifosize))) {
    fprintf(stderr,"fifo: malloc error\n");
    exit(-1);
  }
  minfill = fifosize;
    
  if (debug) {
    fprintf(stderr,"fifo: size=%ld block size=%ld debug level=%ld\n",
	    fifosize,blocksize,debug);
    fflush(stderr);
  }

  fcntl(0,F_SETFL,fcntl(0,F_GETFL,0)|O_NONBLOCK);
  fcntl(1,F_SETFL,fcntl(1,F_GETFL,0)|O_NONBLOCK);

  do {
    rs = fifosize-fifofill;
    if (rs>fifosize-readidx)
      rs = fifosize-readidx;
    if (rs>blocksize)
      rs = blocksize;

    if (writedelay==-1 || readidx+readoff>=writedelay) {
      writedelay = -1;
      ws = fifofill;
      if (ws>fifosize-writeidx)
	ws = fifosize-writeidx;
      if (ws>blocksize)
	ws = blocksize;
    }
    else
      ws = 0;

    if (!naptime) {
      FD_ZERO(&rdfs);
      FD_ZERO(&wrfs);
      if (rs)
	FD_SET(0,&rdfs);
      if (ws)
	FD_SET(1,&wrfs);
      i = select(2,&rdfs,&wrfs,NULL,NULL);
      if (i<=0) {
	fprintf(stderr,"fifo: select error %d\n",(i<0)?errno:0);
	exit(-1);
      }
      if (!FD_ISSET(0,&rdfs))
	rs = 0;
      if (!FD_ISSET(1,&wrfs))
	ws = 0;
      if (!rs && !ws) {
	fprintf(stderr,"fifo: select error\n");
	exit(-1);
      }
    }

    if (rs>0) {
      r = read(0,buf+readidx,rs);
      if (r>0) {
	readidx += r;
	fifofill += r;
	if (readidx>=fifosize) {
	  readoff += fifosize;
	  readidx -= fifosize;
	}
      }
      else if (r!=0 && errno!=EAGAIN) {
	fprintf(stderr,"fifo: read error %d\n",errno);
	exit(-1);
      }
    }
    else
      r = -1;

    if (ws>0) {
      w = write(1,buf+writeidx,ws);
      if (w>0) {
	writeidx += w;
	fifofill -= w;
	if (writeidx>=fifosize) {
	  writeoff += fifosize;
	  writeidx -= fifosize;
	}
      }
      else if (w!=0 && errno!=EAGAIN) {
	fprintf(stderr,"fifo: write error %d\n",errno);
	exit(-1);
      }
    }
    
    if (writedelay==-1) {
      if (minfill > fifofill)
	minfill = fifofill;
      if (maxfill < fifofill)
	maxfill = fifofill;
    }
    if (debug>1 && --debugcnt<=0) {
      debugcnt = debugrate;
      fprintf(stderr,
	      (debug==2)?
	      "\rr=%10ld w=%10ld b=%10ld f=%10ld/%10ld ":
	      "r=%10ld w=%10ld b=%10ld f=%10ld/%10ld\n",
	      readidx+readoff,writeidx+writeoff,
	      fifofill,minfill,maxfill);
      fflush(stderr);
    }

    if (naptime)
      Nap(naptime);

  } while (r!=0 || fifofill);
  
  if (debug==2)
    fprintf(stderr," \n");
  if (debug)
    fprintf(stderr,"bytes read=%ld / written=%ld (fifo min=%ld max=%ld)\n",
	    readidx+readoff,writeidx+writeoff,minfill,maxfill);
  return(0);
}
