/*
*   tee [options] file...
*
* The turkeys that bring us the Unix libraries don't seem  to  understand  how
* this trivial program should work.  The usual version has the problem that it
* reads large buffers, and refuses to pass on the data to any  of  its  output
* files until it has gotten a full buffer.  So you can wait a loooong time for
* it to release data that it is holding.
*
* This version does non-blocking I/O and uses select() to decide when to  read
* more data or exit; it doesn't induce long delays in the pipe.
*
* The exit status is the errno code from the last read.
*
* Options are copied after those of Unix library versions of tee.  The options
* must be in arg 1, which may start with '-' or '+', followed by  any  of  the
* following option letters:
*
* -a  Append (default is truncate).
*
* -i  Ignore EINTR interrupts (default is to exit).
*
* They don't make it easy ...
*/
#include "V.h"
#include "sys_fcntl.h"

#ifndef OPEN_MAX
#define OPEN_MAX 20
#endif

char  buf[BUFSIZ];

char* fn[OPEN_MAX] = {"STDOUT"};
int   fd[OPEN_MAX] = {1};
int   files = 1;

int   fullsleep = 10;	/* How long to wait when file system full */
int   names = 0;

Flag  appendfl = 0;
Flag  intrfl = 0;
int   mode = O_WRONLY | O_CREAT;

main(ac,av)
	int    ac;
	char** av;
{	int    a, f, i, n;
	int    r;	/* Number of bytes gotten in last read */
	int    w;	/* Number of bytes written in last write */
	int    s=0;
	char*  p;
	int    emask, rmask;

	ac = Vinit(ac,av);
	switch (av[a=1][0]) {
	case '-': case '+':
		for (p = av[a] + 1; *p; p++) {
			switch (*p) {
			case 'A': case 'a':
				appendfl = 1;
				V5 "Append." D;
				break;
			case 'I': case 'i':
				intrfl  = 1;
				break;
			}
		}
		a = 2;
	}
	mode |= appendfl ? O_APPEND : O_TRUNC;
	while (a < ac) {
		if (files < OPEN_MAX) {
			fn[files] = av[a];
			++names;	/* Number of files attempted */
			V5 "File %d: \"%s\"",files,fn[files] D;
			errno = 0;
			if ((fd[files] = Open(fn[files],mode,0666)) >= 0) {
				V3 "Opened \"%s\"",fn[files] D;
				++files;
			} else {
				V1 "Can't write \"%s\" [Err %d=%s=%s]",fn[files],Errlist D;
			}
		} else {
			V1 "Too many files; limited to %d.",OPEN_MAX D;
		}
		++a;
	}
	V3 "There are %d output files.",files D;
	Fcntl(0,F_SETFL,O_NDELAY);	/* Make stdin non-blocking */

loop:
	rmask = emask = 1;	/* Select masks */
	errno = 0;
	i = Select(1,&rmask,0,&emask,0);
	s = errno;
	if (i < 0) {
		V2 "Stdin has problem [Err %d=%s=%s]",Errlist D;
		Fail;
	}
	if (i == 0) {
		V5 "Select returned zero [\"Can't happen\" :-]" D;
		Fail;
	}
	V5 "Stdin has input ..." D;
	errno = 0;
	if ((r = Read(0,buf,BUFSIZ)) > 0) {
		s = errno;
		V5 "Got %d bytes.",r D;
		for (f=0; f<files; f++) {
			V5 "Write to file %d: fd=%d \"%s\"",f,fd[f],fn[f] D;
			if (fd[f] > 0) {
				errno = 0;
				if ((w = writebuf(f,buf,r)) <= 0) {
					V1 "Failed while writing to \"%s\" [Err %d=%s=%s]",
						fn[f],Errlist D;
					s = errno;
					Fail;
				}
			}
		}
		V5 "Wrote %d bytes to %d files.",r,f D;
		Loop;
	}
	if (r < 0) {
		s = errno;
		V1 "Stdin read error %d=%s=%s.",Errlist D;
		Fail;
	}
	if (r == 0) {
		if (errno) {
			V1 "Input error %d=%s=%s.",Errlist D;
		} else {
			V3 "EOF on stdin." D;
		}
		Done;
	}
	Loop;

done:
fail:
	Exit(s);
}

FCT writebuf(f,b,n)
	char* b;
{	int r=0;
	int i=0;
	Fenter("writebuf");
	V7 "Called for file %d=\"%s\" n=%d.",f,fn[f],n D;
	while (r < n) {
		errno = 0;
		if ((i = Write(fd[f],b+r,n-r)) > 0) {
			r += i;
		} else {	/* Some error occurred */
			switch (errno) {
			case ENOSPC:
				V1 "Out of space; can't write to \"%s\"",fn[f] D;
				Sleep(fullsleep);
				continue;
			case EINTR:
				if (intrfl) {
					continue;
				} else {
					V1 "Interrupt while writing \"%s\"",fn[f] D;
					Fail;
				}
			default:
				V1 "Can't write %d bytes to \"%s\" [Err %d=%s=%s]",
					n-r,fn[f],Errlist D;
				r = -r;
				Fail;
			}
		}
	}
fail:
	FExit;
	return r;
}
