/* Copyright (c)1994-2000 Begemot Computer Associates. All rights reserved.
 * See the file COPYRIGHT for details of redistribution and use. */

/*
 * driver for lp
 *
 * This program is usually piped from p11 trough a command line for
 * the lp driver.
 */
# include <stdio.h>
# include <stdlib.h>
# include <limits.h>
# include <unistd.h>
# include <signal.h>
# include <errno.h>
# include <string.h>
# include <fcntl.h>
# include <sys/types.h>
# include <sys/wait.h>
# include <sys/socket.h>

# ifdef HAVE_GETOPT_H
#  include <getopt.h>
# endif

# include <begemot.h>
# include "cdefs.h"
# include "util.h"

RCSID("$Id: lp_file.c 476 2001-07-09 08:09:10Z hbb $")

static volatile int sig;		/* USR1 catched */
static int	run;		/* run file instead of write */
static u_char	*eofseq;	/* end of file sequence */
static size_t	seqlen;		/* length of sequence */
static u_char	*cmpbuf;	/* buffer to compare */
static size_t	cmplen;		/* number of bytes */
static int	fd = -1;	/* output fd */
static char	*fname;		/* file to write or execute */
static char	**args;		/* argument vector */
static u_int	filgen;		/* file generation number */

static void parse_seq(char *);
static void nextpage(int);
static void onchld(int);
static void usage(void) DEAD_FUNC;
static void check_page(char);
static int pipe_prog(void);
static int next_file(void);

static char usgtxt[] =
"Begemot PDP11 emulator Version %s; Printer-to-file adapter\n"
"Copyright (c) 1994-2000 Begemot Computer Associates. All rights reserved.\n"
"Usage: lp_file [-p pidfile] [-c] [-e seq] file [args ...]\n"
"where:\n"
"	-p file	write pid into file\n"
"	-c	run file and pipe into its stdin\n"
"	-e seq	set a EOF sequence\n";


int
main(int argc, char **argv)
{
	int opt;
	FILE *fp;
	char c;
	ssize_t n;
	fd_set iset, oset;

	while((opt = getopt(argc, argv, "ce:p:h")) != EOF)
		switch(opt) {

		  case 'c':
			run = 1;
			break;

		  case 'e':
			parse_seq(optarg);
			break;

		  case 'p':
			if((fp = fopen(optarg, "w")) != NULL) {
				fprintf(fp, "%d", (int)getpid());
				fclose(fp);
			}
			break;

		  case 'h':
			usage();
		}

	argc -= optind;
	argv += optind;

	catch_signal(SIGUSR1, nextpage);
	catch_signal(SIGCHLD, onchld);

	fname = argv[0];
	args = &argv[0];

	if(run)
		fd = pipe_prog();
	else
		fd = next_file();

	sig = 0;
	FD_ZERO(&iset);
	FD_SET(0, &iset);
	for(;;) {
		if(!sig) {
			oset = iset;
			n = select(1, &oset, NULL, NULL, NULL);
			if(n == -1) {
				if(errno != EINTR)
					panic("select: %s", strerror(errno));
			} else if(n == 0) {
				panic("select returns 0");
			} else if(FD_ISSET(0, &oset)) {
				n = read(0, &c, 1);
				if(n == -1) {
					if(errno != EINTR)
						panic("read: %s", strerror(errno));
				} else if(n == 0)
					exit(0);
				else
					check_page(c);
			}
		}
		if(sig) {
			if(run)
				fd = pipe_prog();
			else
				fd = next_file();

			sig = 0;
		}

	}

	return 0;
}

static void
usage(void)
{
	fprintf(stderr, usgtxt, VERSION);
	exit(0);
}


static void
nextpage(int s UNUSED)
{
	sig++;
}


static void
parse_seq(char *arg)
{
	char *end;
	u_long val;

	while(*arg != '\0') {
		val = strtoul(arg, &end, 0);
		if(end == arg || (*end != '\0' && *end != ':'))
			panic("bad sequence at: '%s'", arg);
		if(val > 0xff)
			panic("sequence element too big 0x%lx", val);
		seqlen++;
		eofseq = xrealloc(eofseq, seqlen);
		eofseq[seqlen-1] = val;
		if(*end == ':')
			arg = end+1;
		else
			arg = end;
	}
	if(cmpbuf != NULL)
		free(cmpbuf);
	cmpbuf = xalloc(seqlen);
}

static void
check_page(char c)
{
	if(seqlen > 0) {
		if(cmplen == seqlen) {
			if(seqlen > 1)
				(void)memcpy(cmpbuf, cmpbuf+1, seqlen-1);
			cmplen--;
		}
		cmpbuf[seqlen-1] = (u_char)c;
		cmplen++;
		if(cmplen == seqlen && memcmp(eofseq, cmpbuf, seqlen) == 0) {
			sig++;
			cmplen = 0;
		}
	}
	(void)write(fd, &c, 1);
}

static void
onchld(int s UNUSED)
{
	pid_t pid;
	int status;

	while((int)(pid = wait(&status)) == -1)
		if(errno == ECHILD)
			break;
		else if(errno == EINTR)
			;
		else
			panic("wait: %s", strerror(errno));
}

static int
next_file()
{
	char *buf;

	if(fd >= 0)
		(void)close(fd);

	buf = xalloc(strlen(fname) + 20);
	(void)sprintf(buf, "%s%03u", fname, filgen++);
	if((fd = open(buf, O_WRONLY|O_CREAT|O_TRUNC, 0666)) < 0)
		panic("%s: %s", buf, strerror(errno));
	free(buf);

	return fd;
}

static int
pipe_prog()
{
	int sd[2];
	pid_t child;

	if(fd >= 0) {
		/*
		 * Close the pipe. A good child should exit.
		 */
		(void)close(fd);
	}

	if(socketpair(PF_UNIX, SOCK_STREAM, 0, sd) < 0)
		panic("socketpair: %s", strerror(errno));

	switch(child = fork()) {

	  case -1:
		/* failure */
		panic("fork: %s", strerror(errno));

	  case 0:
		/* child */
		if(sd[0] != 0) {
			if(dup2(sd[0], 0) == -1)
				abort();
			(void)close(sd[0]);
		}

		/*
		 * don't close fds 0 (it's the pipe), 2 (stderr).
		 */
		for(fd = getdtablesize() - 1; fd > 0; fd--)
			if(fd != 2)
				(void)close(fd);
		execv(fname, args);
		panic("exec: %s - %s", fname, strerror(errno));
	}

	/* parent */
	close(sd[0]);

	return sd[1];
}
