OpenSolaris_b135/cmd/lp/model/lp.cat.c

Compare this file to the similar file:
Show the results in this format:

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
/*	  All Rights Reserved  	*/

#include <stdio.h>
#include <stdlib.h>
#include <termio.h>
#include <sys/types.h>
#include <errno.h>
#include <signal.h>
#include <sys/times.h>
#include <string.h>
#include <limits.h>
#include <sys/prnio.h>

#include "lp.h"

#include <locale.h>

/*
 *	Begin Sun Additions for Parallel ports
 */

#include <string.h>
#include <stdarg.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ioccom.h>
#include <sys/ioctl.h>

#include <sys/bpp_io.h>
#include <sys/ecppsys.h>
#include <stropts.h>

/*
 * the parameter structure for the parallel port
 */
struct ppc_params_t {
	int		flags;		/* same as above */
	int		state;		/* status of the printer interface */
	int		strobe_w;	/* strobe width, in uS */
	int		data_setup;	/* data setup time, in uS */
	int		ack_timeout;	/* ACK timeout, in secs */
	int		error_timeout;	/* PAPER OUT, etc... timeout, in secs */
	int		busy_timeout;	/* BUSY timeout, in seconds */
};



static void printer_info(char *fmt, ...);

/*	These are the routines avaliable to others for use 	*/
int is_a_parallel_bpp(int);
int bpp_state(int);
int parallel_comm(int, int());
int get_ecpp_status(int fd);
int is_a_prnio(int);
int prnio_state(int);

#define	PRINTER_ERROR_PAPER_OUT		1
#define	PRINTER_ERROR_OFFLINE		2
#define	PRINTER_ERROR_BUSY		3
#define	PRINTER_ERROR_ERROR		4
#define	PRINTER_ERROR_CABLE_POWER	5
#define	PRINTER_ERROR_UNKNOWN		6
#define	PRINTER_ERROR_TIMEOUT		7
#define	PRINTER_IO_ERROR		129


/*
 *	for BPP PARALLEL interfaces
 */

int
is_a_parallel_bpp(int fd)
{
	if (ioctl(fd, BPPIOC_TESTIO) == 0 || errno == EIO)
		return (1);
	return (0);
}


#if defined(DEBUG) && defined(NOTDEF)
char *
BppState(int state)
{
	static char buf[BUFSIZ];

	memset(buf, 0, sizeof (buf));
	sprintf(buf, "State (0x%.4x) - (%s%s%s%s)\n", state,
	    ((state & BPP_SLCT_ERR) ?  "offline " : ""),
	    ((state & BPP_BUSY_ERR) ?  "busy " : ""),
	    ((state & BPP_PE_ERR) ?  "paper " : ""),
	    ((state & BPP_ERR_ERR) ?  "error " : ""));

	return (buf);
}
#endif

int
bpp_state(int fd)
{
	if (ioctl(fd, BPPIOC_TESTIO)) {
		struct bpp_error_status  bpp_stat;
		int state;

		if (ioctl(fd, BPPIOC_GETERR, &bpp_stat) < 0)
			exit(PRINTER_IO_ERROR);
		state = bpp_stat.pin_status;

#if defined(DEBUG) && defined(NOTDEF)
		logit("%s", BppState(state));
#endif

		if (state == (BPP_PE_ERR | BPP_ERR_ERR | BPP_SLCT_ERR)) {
			/* paper is out */
			return (PRINTER_ERROR_PAPER_OUT);
		} else if (state & BPP_BUSY_ERR) {
			/* printer is busy */
			return (PRINTER_ERROR_BUSY);
		} else if (state & BPP_SLCT_ERR) {
			/* printer is offline */
			return (PRINTER_ERROR_OFFLINE);
		} else if (state & BPP_ERR_ERR) {
			/* printer is errored */
			return (PRINTER_ERROR_ERROR);
		} else if (state == BPP_PE_ERR) {
			/* printer is off/unplugged */
			return (PRINTER_ERROR_CABLE_POWER);
		} else if (state) {
			return (PRINTER_ERROR_UNKNOWN);
		} else
			return (0);
	}
	return (0);
}

/*
 * For ecpp parallel port
 */

int
get_ecpp_status(int fd)
{
	int state;
	struct ecpp_transfer_parms transfer_parms;


	if (ioctl(fd, ECPPIOC_GETPARMS, &transfer_parms) == -1) {
		return (-1);
	}

	state = transfer_parms.mode;
	/*
	 * We don't know what all printers will return in
	 * nibble mode, therefore if we support nibble mode we will
	 * force the printer to be in CENTRONICS mode.
	 */
	if (state != ECPP_CENTRONICS) {
		transfer_parms.mode = ECPP_CENTRONICS;
		if (ioctl(fd, ECPPIOC_SETPARMS, &transfer_parms) == -1) {
			return (-1);
		} else {
			state = ECPP_CENTRONICS;
		}
	}


	return (state);
}

/*
 * For prnio(7I) - generic printer interface
 */
int
is_a_prnio(int fd)
{
	uint_t	cap;

	/* check if device supports prnio */
	if (ioctl(fd, PRNIOC_GET_IFCAP, &cap) == -1) {
		return (0);
	}
	/* we will use 1284 status if available */
	if ((cap & PRN_1284_STATUS) == 0) {
		/* some devices may only support 1284 status in unidir. mode */
		if (cap & PRN_BIDI) {
			cap &= ~PRN_BIDI;
			(void) ioctl(fd, PRNIOC_SET_IFCAP, &cap);
		}
	}
	return (1);
}

int
prnio_state(int fd)
{
	uint_t	status;
	uchar_t	pins;

	if ((ioctl(fd, PRNIOC_GET_STATUS, &status) == 0) &&
	    (status & PRN_READY)) {
		return (0);
	}

	if (ioctl(fd, PRNIOC_GET_1284_STATUS, &pins) != 0) {
		return (PRINTER_ERROR_UNKNOWN);
	}

	if ((pins & ~PRN_1284_BUSY) == PRN_1284_PE) {
		/* paper is out */
		return (PRINTER_ERROR_PAPER_OUT);
	} else if (pins == (PRN_1284_PE | PRN_1284_SELECT |
	    PRN_1284_NOFAULT | PRN_1284_BUSY)) {
		/* printer is off/unplugged */
		return (PRINTER_ERROR_CABLE_POWER);
	} else if ((pins & PRN_1284_SELECT) == 0) {
		/* printer is offline */
		return (PRINTER_ERROR_OFFLINE);
	} else if ((pins & PRN_1284_NOFAULT) == 0) {
		/* printer is errored */
		return (PRINTER_ERROR_ERROR);
	} else if (pins & PRN_1284_PE) {
		/* paper is out */
		return (PRINTER_ERROR_PAPER_OUT);
	} else if (pins ^ (PRN_1284_SELECT | PRN_1284_NOFAULT)) {
		return (PRINTER_ERROR_UNKNOWN);
	}

	return (0);
}

/*
 *	Common routines
 */

/*ARGSUSED0*/
static void
ByeByeParallel(int sig)
{
	/* try to shove out the EOT */
	(void) write(1, "\004", 1);
	exit(0);
}


/*ARGSUSED0*/
static void
printer_info(char *fmt, ...)
{
	char mesg[BUFSIZ];
	va_list ap;

	va_start(ap, fmt);
	vsprintf(mesg, fmt, ap);
	va_end(ap);
/*
 *	fprintf(stderr,
 *		"%%%%[ PrinterError: %s; source: parallel ]%%%%\n",
 *		mesg);
 */
	fprintf(stderr, "%s\n", mesg);
	fflush(stderr);
	fsync(2);

}

static void
printer_error(int error)
{
	switch (error) {
	case -1:
		printer_info("ioctl(): %s", strerror(errno));
		break;
	case PRINTER_ERROR_PAPER_OUT:
		printer_info("out of paper");
		break;
	case PRINTER_ERROR_OFFLINE:
		printer_info("offline");
		break;
	case PRINTER_ERROR_BUSY:
		printer_info("busy");
		break;
	case PRINTER_ERROR_ERROR:
		printer_info("printer error");
		break;
	case PRINTER_ERROR_CABLE_POWER:
		printer_info("printer powered off or disconnected");
		break;
	case PRINTER_ERROR_UNKNOWN:
		printer_info("unknown error");
		break;
	case PRINTER_ERROR_TIMEOUT:
		printer_info("communications timeout");
		break;
	default:
		printer_info("get_status() failed");
	}
}


static void
wait_state(int fd, int get_state())
{
	int state;
	int was_faulted = 0;

	while (state = get_state(fd)) {
		was_faulted = 1;
		printer_error(state);
		sleep(15);
	}

	if (was_faulted) {
		fprintf(stderr, "printer ok\n");
		fflush(stderr);
		fsync(2);
	}
}

/*
 *  end of Sun Additions for parallel port
 */
#define	IDENTICAL(A, B)	(A.st_dev == B.st_dev && A.st_ino == B.st_ino)
#define	ISBLK(A)	((A.st_mode & S_IFMT) == S_IFBLK)
#define	ISCHR(A)	((A.st_mode & S_IFMT) == S_IFCHR)

#define	E_SUCCESS	0
#define	E_BAD_INPUT	1
#define	E_BAD_OUTPUT	2
#define	E_BAD_TERM	3
#define	E_IDENTICAL	4
#define	E_WRITE_FAILED	5
#define	E_TIMEOUT	6
#define	E_HANGUP	7
#define	E_INTERRUPT	8

#define	SAFETY_FACTOR	2.0
#define	R(F)		(int)((F) + .5)
#define	DELAY(N, D)	R(SAFETY_FACTOR * ((N) / (double)(D)))

char			buffer[BUFSIZ];

void			sighup(),
			sigint(),
			sigquit(),
			sigpipe(),
			sigalrm(),
			sigterm();

#if	defined(baudrate)
#undef	baudrate
#endif

int baudrate();


int
nop(int fd)
{
	return (0);
}

int bpp_state(int);


/*
 * main()
 */

int
main(int argc, char *argv[])
{
	int	nin, nout, effective_rate, max_delay = 0, n;
	int	report_rate;
	short	print_rate;
	struct stat	in, out;
	struct tms	tms;
	long	epoch_start, epoch_end;
	char	*TERM;
	int	(*func)(int fd);

	/*
	 * The Spooler can hit us with SIGTERM for three reasons:
	 *
	 *	- the user's job has been canceled
	 *	- the printer has been disabled while we were printing
	 *	- the Spooler heard that the printer has a fault,
	 *	  and the fault recovery is wait or beginning
	 *
	 * We should exit cleanly for the first two cases,
	 * but we have to be careful with the last. If it was THIS
	 * PROGRAM that told the Spooler about the fault, we must
	 * exit consistently.
	 *
	 * The method of avoiding any problem is to turn off the
	 * trapping of SIGTERM before telling the Spooler about
	 * the fault.
	 *
	 * Faults that we can detect:
	 *	- hangup (drop of carrier)
	 *	- interrupt (printer sent a break or quit character)
	 *	- SIGPIPE (output port is a FIFO, and was closed early)
	 *	- failed or incomplete write()
	 *	- excess delay in write() (handled with SIGALRM later)
	 *
	 * Pseudo-faults (errors in use):
	 *	- No input/output, or strange input/output
	 *	- Input/output identical
	 *	- No TERM defined or trouble reading Terminfo database
	 */
	signal(SIGTERM, sigterm);
	signal(SIGHUP, sighup);
	signal(SIGINT, sigint);
	signal(SIGQUIT, sigint);
	signal(SIGPIPE, sigpipe);


	if (argc > 1 && STREQU(argv[1], "-r")) {
		report_rate = 1;
		argc--;
		argv++;
	} else
		report_rate = 0;

	(void) setlocale(LC_ALL, "");
#if !defined(TEXT_DOMAIN)
#define	TEXT_DOMAIN "SYS_TEST"
#endif
	(void) textdomain(TEXT_DOMAIN);

	/*
	 * Stat the standard output to be sure it is defined.
	 */
	if (fstat(1, &out) < 0) {
		signal(SIGTERM, SIG_IGN);
		fprintf(stderr, gettext("Can't stat output "
		    "(%s);\nincorrect use of lp.cat!\n"), PERROR);
		exit(E_BAD_OUTPUT);
	}

	/*
	 * Stat the standard input to be sure it is defined.
	 */
	if (fstat(0, &in) < 0) {
		signal(SIGTERM, SIG_IGN);
		fprintf(stderr, gettext("Can't stat input "
		    "(%s);\nincorrect use of lp.cat!\n"), PERROR);
		exit(E_BAD_INPUT);
	}

	/*
	 * If the standard output is not a character special file or a
	 * block special file, make sure it is not identical to the
	 * standard input.
	 *
	 * If we are an ecpp parallel port in centronics mode treat
	 * ourselves as a bpp compatible device.
	 */

	if (is_a_prnio(1)) {
		func = prnio_state;
	} else if (is_a_parallel_bpp(1) ||
	    (get_ecpp_status(1) == ECPP_CENTRONICS)) {
		func = bpp_state;
	} else if (isatty(1)) {
		/* serial connection (probably) - continue as usual */
		func = nop;
	} else {
		func = nop;
	}

	if (!ISCHR(out) && !ISBLK(out) && IDENTICAL(out, in)) {
		signal(SIGTERM, SIG_IGN);
		fprintf(stderr, gettext("Input and output are identical; "
		    "incorrect use of lp.cat!\n"));
		exit(E_IDENTICAL);
	}

	/*
	 * The effective data transfer rate is the lesser
	 * of the transmission rate and print rate. If an
	 * argument was passed to us, it should be a data
	 * rate and it may be lower still.
	 * Based on the effective data transfer rate,
	 * we can predict the maximum delay we should experience.
	 * But there are other factors that could introduce
	 * delay, so let's be generous; after all, we'd rather
	 * err in favor of waiting too long to detect a fault
	 * than err too often on false alarms.
	 */

	if (!(TERM = getenv("TERM")) || !*TERM) {
		signal(SIGTERM, SIG_IGN);
		fprintf(stderr, gettext("No TERM variable defined! "
		    "Trouble with the Spooler!\n"));
		exit(E_BAD_TERM);
	}
	if (!STREQU(TERM, NAME_UNKNOWN) &&
	    tidbit(TERM, "cps", &print_rate) == -1) {
		signal(SIGTERM, SIG_IGN);
		fprintf(stderr, gettext("Trouble identifying printer "
		    "type \"%s\"; check the Terminfo database.\n"), TERM);
		exit(E_BAD_TERM);
	}
	if (STREQU(TERM, NAME_UNKNOWN))
		print_rate = -1;

	effective_rate = baudrate() / 10; /* okay for most bauds */
	if (print_rate != -1 && print_rate < effective_rate)
		effective_rate = print_rate;
	if (argc > 1 && (n = atoi(argv[1])) >= 0 && n < effective_rate)
		effective_rate = n;	  /* 0 means infinite delay */
	if (effective_rate)
		max_delay = DELAY(BUFSIZ, effective_rate);

	/*
	 * We'll use the "alarm()" system call to keep us from
	 * waiting too long to write to a printer in trouble.
	 */
	if (max_delay)
		signal(SIGALRM, sigalrm);

	/*
	 * While not end of standard input, copy blocks to
	 * standard output.
	 */
	while ((nin = read(0, buffer, BUFSIZ)) > 0) {
		char *ptr = buffer;

		/*
		 * We should be safe from incomplete writes to a full
		 * pipe, as long as the size of the buffer we write is
		 * a even divisor of the pipe buffer limit. As long as
		 * we read from files or pipes (not communication devices)
		 * this should be true for all but the last buffer. The
		 * last will be smaller, and won't straddle the pipe max
		 * limit (think about it).
		 */
#if	PIPE_BUF < BUFSIZ || (PIPE_MAX % BUFSIZ)
		this_wont_compile;
#endif
		if (report_rate)
			epoch_start = times(&tms);
		do {
			wait_state(1, func);

			if (max_delay)
				alarm(max_delay);
			nout = write(1, ptr, nin);
			alarm(0);
			if (nout < 0) {
				fprintf(stderr, gettext("Write failed "
				    "(%s);\nperhaps the printer has gone "
				    "off-line.\n"), PERROR);
				fflush(stderr);
				if (errno != EINTR)
				/* I/O error on device, get lpcshed to retry */
					exit(PRINTER_IO_ERROR);
				else /* wait for printer to come back online */
					sleep(15);
			} else {
				nin -= nout;
				ptr += nout;
			}
		} while (nin > 0);

		if (max_delay)
			alarm(0);
		else if (report_rate) {
			epoch_end = times(&tms);
			if (epoch_end - epoch_start > 0)
				fprintf(stderr, "%d CPS\n",
				    R((100 * BUFSIZ) /
				    (double)(epoch_end - epoch_start)));
		}

	}

	return (E_SUCCESS);
}

/*
 * sighup() - CATCH A HANGUP (LOSS OF CARRIER)
 */
void
sighup()
{
	signal(SIGTERM, SIG_IGN);
	signal(SIGHUP, SIG_IGN);
	fprintf(stderr, gettext(HANGUP_FAULT_LPCAT));
	exit(E_HANGUP);
}

/*
 * sigint() - CATCH AN INTERRUPT
 */
void
sigint()
{
	signal(SIGTERM, SIG_IGN);
	signal(SIGINT, SIG_IGN);
	fprintf(stderr, gettext(INTERRUPT_FAULT));
	exit(E_INTERRUPT);
}

/*
 * sigpipe() - CATCH EARLY CLOSE OF PIPE
 */
void
sigpipe()
{
	signal(SIGTERM, SIG_IGN);
	signal(SIGPIPE, SIG_IGN);
	fprintf(stderr, gettext(PIPE_FAULT));
	exit(E_INTERRUPT);
}

/*
 * sigalrm() - CATCH AN ALARM
 */
void
sigalrm()
{
	signal(SIGTERM, SIG_IGN);
	fprintf(stderr, gettext("Excessive write delay; "
	    "perhaps the printer has gone off-line.\n"));
	exit(E_TIMEOUT);
}

/*
 * sigterm() - CATCH A TERMINATION SIGNAL
 */
void
sigterm()
{
	signal(SIGTERM, SIG_IGN);
	/*
	 * try to flush the output queue in the case of ecpp port.
	 * ignore the return code as this may not be the ecpp.
	 */
	ioctl(1, I_FLUSH, FLUSHW);
	exit(E_SUCCESS);
}

/*
 * baudrate() - RETURN BAUD RATE OF OUTPUT LINE
 */

static int		baud_convert[] =
{
	0, 50, 75, 110, 135, 150, 200, 300, 600, 1200,
	1800, 2400, 4800, 9600, 19200, 38400, 57600,
	76800, 115200, 153600, 230400, 307200, 460800, 921600
};

int
baudrate()
{
	struct termio		tm;
	struct termios		tms;
	int			speed;

	if (ioctl(1, TCGETS, &tms) < 0) {
		if (ioctl(1, TCGETA, &tm) < 0)
			return (1200);
		else
			speed = tm.c_cflag&CBAUD;
	} else
		speed = cfgetospeed(&tms);

	return (speed ? baud_convert[speed] : 1200);
}