xref: /illumos-gate/usr/src/cmd/lp/model/standard (revision 7c478bd9)
1#
2# Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
3# Use is subject to license terms.
4#
5# CDDL HEADER START
6#
7# The contents of this file are subject to the terms of the
8# Common Development and Distribution License, Version 1.0 only
9# (the "License").  You may not use this file except in compliance
10# with the License.
11#
12# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
13# or http://www.opensolaris.org/os/licensing.
14# See the License for the specific language governing permissions
15# and limitations under the License.
16#
17# When distributing Covered Code, include this CDDL HEADER in each
18# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
19# If applicable, add the following below this CDDL HEADER, with the
20# fields enclosed by brackets "[]" replaced with your own identifying
21# information: Portions Copyright [yyyy] [name of copyright owner]
22#
23# CDDL HEADER END
24#
25#ident	"%Z%%M%	%I%	%E% SMI"	/* SVr4.0 1.26	*/
26
27###########
28##
29## Standard printer interface program.
30###########
31
32#####
33#
34# Until we get to the point below where the printer port
35# and physical printer are initialized, we can't do much
36# except exit if the Spooler/Scheduler cancels us.
37#####
38trap 'exit' 15
39
40#####
41#
42# We can be clever about getting a hangup or interrupt, though, at least
43# until the filter runs. Do this early, even though $LPTELL
44# isn't defined, so that we're covered.
45#####
46catch_hangup () {
47	if [ -n "${LPTELL}" ]
48	then
49		echo \
50"The connection to the printer dropped; perhaps the printer went off-line?" \
51		| ${LPTELL} ${printer}
52	fi
53	return 0
54}
55catch_interrupt () {
56	if [ -n "${LPTELL}" ]
57	then
58		echo \
59"Received an interrupt from the printer.  The reason is unknown,
60although a common cause is that the baud rate is too high." \
61		| ${LPTELL} ${printer}
62	fi
63	return 0
64}
65trap 'catch_hangup; exit_code=129 exit 129' 1
66trap 'catch_interrupt; exit_code=129 exit 129' 2 3
67
68#####
69#
70# Most of the time we don't want the standard error to be captured
71# by the Spooler, mainly to avoid "Terminated" messages that the
72# shell puts out when we get a SIGTERM. We'll save the standard
73# error channel under another number, so we can use it when it
74# should be captured.
75#
76# Open another channel to the printer port, for use when the
77# regular standard output won't be directed there, such as in
78# command substitution (`cmd`).
79#####
80exec 5>&2 2>/dev/null 3>&1
81
82#####
83#
84# Set some globally used variables and functions.
85#####
86
87: ${TMPDIR:=/tmp}
88: ${SPOOLDIR:=/usr/spool/lp}
89: ${TERMINFO:=/usr/lib/terminfo}
90: ${CHARSETDIR:=/usr/lib/charsets}
91
92: ${LOCALPATH:=${SPOOLDIR}/bin}
93PATH="/bin:/usr/bin:${LOCALPATH}"
94
95MAX_COLS_SMALL_BANNER=40
96
97#####
98#
99# On the 3.2 release of the 386unix product, the parallel port does
100# not support any ioctl calls.  As a result, we cannot set the opost
101# and onlcr attributes to have <NL>'s expanded to <CR><NL>.  This
102# "filter" gets the job done for us.
103#####
104: ${FIX386BD:=${LOCALPATH}/386parallel}
105if [ -n "${FIX386BD}" -a -x "${FIX386BD}" ]
106then
107	FIX386BD="| ${FIX386BD}"
108else
109	FIX386BD=""
110fi
111
112#####
113# Use ${TMPPREFIX} as the prefix for all temporary files, so
114# that cleanup is easy. The prefix may be up to 13 characters
115# long, so you only have space for one more character to make
116# a file name. If necessary, make a directory using this prefix
117# for better management of unique temporary file names.
118#####
119TMPPREFIX=${TMPDIR}/`uname -n`$$
120
121#####
122# Before exiting, set ${exit_code} to the value with which to exit.
123# Otherwise, the exit from this script will be 0.
124#####
125trap 'rm -fr ${TMPPREFIX}*; exit ${exit_code}' 0
126
127#####
128# ${LPTELL} is the name of a program that will send its
129# standard input to the Spooler. It is used to forward
130# the description of a printer fault to the Spooler,
131# which uses it in an alert to the administrator.
132#####
133if [ ! -x "${LPTELL:=${LOCALPATH}/lp.tell}" ]
134then
135	fake_lptell () {
136		header="no"
137		while read line
138		do
139			if [ "no" = "${header}" ]
140			then
141				errmsg ERROR ${E_IP_UNKNOWN} \
142		"unknown printer/interface failure" \
143		"consult your system administrator;
144		reasons for failure (if any) follow:"
145				header=yes
146			fi
147			echo "${line}" >&2
148		done
149		return 1
150	}
151	LPTELL=fake_lptell
152fi
153
154#####
155# ${DRAIN} is the name of a program that will wait
156# long enough for data sent to the printer to print.
157#####
158if [ -x "${LOCALPATH}/drain.output" ]
159then
160	DRAIN="${LOCALPATH}/drain.output 5"	# wait only five seconds
161else
162	DRAIN=
163fi
164
165#####
166# ${LPCAT} is the name of a program to use as a default
167# filter. Minimally it should copy its standard input to
168# the standard output, but it should also trap printer
169# faults. The current LPCAT traps hangups (DCD dropping, SIGHUP),
170# interrupts (SIGINT, SIGQUIT), broken pipe (SIGPIPE), and
171# excess delays in sending data to the printer, interpreting all
172# as printer faults.
173#####
174if [ ! -x "${LPCAT:=${LOCALPATH}/lp.cat}" ]
175then
176	LPCAT="cat"
177fi
178
179#####
180# ${LPSET} is the name of a program that will set the
181# character pitch, line pitch, page width, page length,
182# and character set. It helps to have this in a single
183# binary program so that (1) it's faster than calls
184# to "tput"; and (2) it can access the new Terminfo
185# capabilities for printers (on pre SVR3.2 machines, tput can't).
186#####
187if [ ! -x "${LPSET:=${LOCALPATH}/lp.set}" ]
188then
189	fake_lpset () {
190		echo H V W L S >&2
191		false
192	}
193	LPSET=fake_lpset
194fi
195
196internal_lpset () {
197	#####
198	#
199	# The funny business with the "2>&1 1>&3" is to let us capture
200	# the standard ERROR, not the standard OUTPUT as is the usual case
201	# with foo=`cmd`. The standard output will go to the printer.
202	#####
203	[ -n "${stty1}" ] && stty ${stty1} 0<&1
204	chk=`${LPSET} "$1" "$2" "$3" "$4" "$5" 2>&1 1>&3`
205	[ -n "${stty2}" ] && stty ${stty2} 0<&1
206
207	#####
208	#
209	# The standard error of the delivered ${LPSET} program
210	# is a string of letters, H, V, W, L, S, which correspond
211	# to cpi, lpi, width, length, and character set. A letter
212	# is present only if the corresponding attribute could not
213	# be set.
214	#####
215	for err in ${chk}
216	do
217		case ${err} in
218		H )
219			errmsg WARNING ${E_IP_BADCPI} \
220		"can't select the character pitch \"${cpi}\"" \
221		"check the valid pitches for the printer,
222		or consult your system administrator;
223		printing continues"
224			;;
225		V )
226			errmsg WARNING ${E_IP_BADLPI} \
227		"can't select the line pitch \"${lpi}\"" \
228		"check the valid pitches for the printer,
229		or consult your system administrator;
230		printing continues"
231			;;
232		W )
233			width=${cols}
234			errmsg WARNING ${E_IP_BADWIDTH} \
235		"can't select the page width \"${width}\"" \
236		"check the valid widths for the printer,
237		or consult your system administrator;
238		printing continues"
239			;;
240		L )
241			length=${lines}
242			errmsg WARNING ${E_IP_BADLENGTH} \
243		"can't select the page length \"${length}\"" \
244		"check the valid lengths for the printer,
245		or consult your system administrator;
246		printing continues"
247			;;
248		S )
249			errmsg WARNING ${E_IP_BADCHARSET} \
250		"can't select the character set \"${CHARSET}\"" \
251		"check the name given in the -S option,
252		or consult your system administrator;
253		printing continues"
254			;;
255		esac
256	done
257}
258
259
260#####
261# ${TPUT} is "tput" IF it works. We'll disable it if we get an
262# ugly error message the first time we use it. See the TERM variable
263# later in the script.
264#
265# NOTE: The check we use to see if "tput" works is to use an OLD
266# Terminfo capability, like "lines". If it works with that it may
267# still fail with some of the newer capabilities like "init" (SVR3.0)
268# or "swidm" (SVR3.2), because the version of "tput" we have on your
269# machine is older. Thus, on some of the code where ${TPUT} is used
270# you'll see "2>/dev/null" being used to avoid ugly error messages.
271#####
272TPUT=tput
273
274#####
275# Error message formatter:
276#
277# Invoke as
278#
279#	errmsg severity message-number problem help
280#
281# where severity is "ERROR" or "WARNING", message-number is
282# a unique identifier, problem is a short description of the
283# problem, and help is a short suggestion for fixing the problem.
284#####
285
286LP_ERR_LABEL="UX:lp"
287
288E_IP_ARGS=1
289E_IP_OPTS=2
290#E_IP_FILTER=3
291E_IP_STTY=4
292E_IP_UNKNOWN=5
293E_IP_BADFILE=6
294E_IP_BADCHARSET=7
295E_IP_BADCPI=8
296E_IP_BADLPI=9
297E_IP_BADWIDTH=10
298E_IP_BADLENGTH=11
299E_IP_ERRORS=12		# (in slow.filter)
300
301errmsg () {
302	case $1 in
303	ERROR )
304		sev="  ERROR";
305		;;
306	WARNING )
307		sev="WARNING";
308		;;
309	esac
310#	tag=`expr "${LP_ERR_LABEL}" : "\(.*\):"``expr "${LP_ERR_LABEL}" : ".*:\(.*\)"`
311	echo "${LP_ERR_LABEL}: ${sev}: $3
312        TO FIX: $4" >&5
313}
314
315
316###########
317##
318## Check arguments
319###########
320
321parse () {
322	echo "`expr \"$1\" : \"^[^=]*=\(.*\)\"`"
323}
324
325#####
326#
327# This program is invoked as
328#
329# ${SPOOLDIR}/.../printer request-id user title copies options files...
330#
331# The first three arguments are simply reprinted on the banner page,
332# the fourth (copies) is used to control the number of copies to print,
333# the fifth (options) is a blank separated list (in a single argument)
334# of user or Spooler supplied options (without the -o prefix),
335# and the last arguments are the files to print.
336#####
337
338if [ $# -lt 5 ]
339then
340	errmsg ERROR ${E_IP_ARGS} \
341		"wrong number of arguments to interface program" \
342		"consult your system administrator"
343	exit 1
344fi
345
346printer=`basename $0`
347request_id=$1
348user_name=$2
349title=$3
350copies=$4
351option_list=$5
352
353shift 5
354files="$*"
355
356nobanner="no"
357nofilebreak="no"
358stty=
359
360inlist=
361for i in ${option_list}
362do
363	case "${inlist}${i}" in
364
365
366	nobanner )
367		nobanner="yes"
368		;;
369
370	nofilebreak )
371		nofilebreak="yes"
372		;;
373
374	#####
375	#
376	# If you want to add simple options (e.g. -o simple)
377	# identify them here.
378	#####
379#	simple )
380#		simple="yes"
381#		;;
382
383
384	cpi=pica )
385		cpi=10
386		;;
387	cpi=elite )
388		cpi=12
389		;;
390	cpi=* )
391		cpi=`parse ${i}`
392		;;
393
394	lpi=* )
395		lpi=`parse ${i}`
396		;;
397
398	length=* )
399		length=`parse ${i}`
400		;;
401
402	width=* )
403		width=`parse ${i}`
404		;;
405
406	#####
407	#
408	# If you want to add simple-value options (e.g. -o value=a)
409	# identify them here.
410	#####
411#	value=* )
412#		value=`parse ${i}`
413#		;;
414
415
416	#####
417	#
418	# If you want to add options that, like "stty",
419	# take a list (e.g. -o lopt='a b c'), identify
420	# them here and below (look for LOPT).
421	#####
422	stty=* | flist=* | lpd=* )
423#LOPT	stty=* | flist=* | lpd=* | lopt=* )
424
425		inlist=`expr "${inlist}${i}" : "^\([^=]*=\)"`
426		case "${i}" in
427		${inlist}\'*\' )
428			item=`expr "${i}" : "^[^=]*='*\(.*\)'\$"`
429			;;
430		${inlist}\' )
431			continue
432			;;
433		${inlist}\'* )
434			item=`expr "${i}" : "^[^=]*='*\(.*\)\$"`
435			;;
436		${inlist}* )
437			item=`expr "${i}" : "^[^=]*=\(.*\)\$"`
438			;;
439		*\' )
440			item=`expr "${i}" : "^\(.*\)'\$"`
441			;;
442		* )
443			item="${i}"
444			;;
445		esac
446
447		#####
448		#
449		# We don't dare use "eval" because a clever user could
450		# put something in an option value that we'd end up
451		# exec'ing.
452		#####
453		case "${inlist}" in
454		stty= )
455			stty="${stty} ${item}"
456			;;
457		flist= )
458			flist="${flist} ${item}"
459			;;
460		lpd= )
461			lpd="${lpd} ${item}"
462			;;
463#LOPT		lopt= )
464#LOPT			lopt="${lopt} ${item}"
465#LOPT			;;
466		esac
467
468		case "${i}" in
469		${inlist}\'*\' )
470			inlist=
471			;;
472		${inlist}\'* )
473			;;
474		*\' | ${inlist}* )
475			inlist=
476			;;
477		esac
478		;;
479
480	* )
481		errmsg WARNING ${E_IP_OPTS} \
482			"unrecognized \"-o ${i}\" option" \
483			"check the option, resubmit if necessary
484		printing continues"
485		;;
486	esac
487done
488
489#####
490#
491# Additional ``parameters'' are passed via Shell environment
492# variables:
493#
494#	TERM	The printer type (used for Terminfo access)
495#	CHARSET	The character set to choose
496#	FILTER	The filter to run
497#####
498
499#####
500# Set defaults for unset variables.
501#####
502
503: ${TERM:=unknown}
504tput lines 1>/dev/null 2>&1 || TPUT=:
505
506: ${CHARSET:=cs0}
507
508if [ -z "${FILTER}" ]
509then
510	#####
511	#
512	# If no filter is being used, we have a little routine that
513	# will push the data to the printer. It traps hangups (loss
514	# of carrier) and checks for excessive delays in sending the
515	# data to the printer. The lesser of the print rate of the printer
516	# (obtained from Terminfo) or the baud rate is used to compute
517	# the expected delay. If neither of these is correct, you
518	# may be experiencing false alarms. If so, give the correct
519	# rate, in characters per second, as a single argument.
520	# An argument of 0 means don't check for delays.
521	# Give an -r option to get a printout of actual delays.
522	# (QUOTES ARE IMPORTANT!)
523	#####
524	case "$TERM" in
525		PS )
526			# make the "postscript" printers use postio to
527			# talk to the printer and periodically get a
528			# status from them
529			FILTER="/usr/lib/lp/postscript/postio"
530		;;
531		PSR )
532			# make the "reverse postscript" printers reverse the
533			# output and the use postio to talk to the printer
534			FILTER="/usr/lib/lp/postscript/postreverse | \
535				/usr/lib/lp/postscript/postio"
536		;;
537		* )
538			# we don't know the type, so just assume that the
539			# input and output are the same
540			if [ `basename "${LPCAT}"` = "lp.cat" ] ; then
541				FILTER="${LPCAT} 0"	# infinite delays
542				# FILTER="${LPCAT} 120"	# e.g. 120 CPS
543				# FILTER="${LPCAT} -r 0 2>/tmp/delays"
544				# FILTER=${LPCAT}
545			fi
546		;;
547	esac
548fi
549
550###########
551##
552## Initialize the printer port
553###########
554
555#####
556#
557# SERIAL PORTS:
558# Initialize everything.
559#
560# PARALLEL PORTS:
561# Don't initialize baud rate.
562#
563# It's not obvious how to tell if a port is parallel or serial.
564# However, by splitting the initialization into two steps and letting
565# the serial-only part fail nicely, it'll work.
566#
567# Another point: The output must be a ``tty'' device. If not, don't
568# bother with any of this.
569#####
570stty1= stty2=
571tty 0<&1 1>/dev/null 2>&1 && {
572
573	#####
574	#
575	# First set the default parameters,
576	# then the requested parameters.
577	#####
578
579	stty \
580		9600 \
581			0<&1 2>/dev/null 1>&2
582	stty \
583		cs8 -cstopb -parenb -parodd \
584		ixon -ixany \
585		opost -olcuc onlcr -ocrnl -onocr -onlret -ofill \
586		nl0 cr0 tab0 bs0 vt0 ff0 \
587			0<&1 2>/dev/null 1>&2
588
589	if [ -n "${stty}" ]
590	then
591		if stty ${stty} 0<&1 1>/dev/null 2>&5
592		then
593			:
594		else
595			errmsg ERROR ${E_IP_STTY} \
596				"stty option list failed" \
597				"check the \"-o stty\" option you used,
598		or consult your system administrator"
599			exit 1
600		fi
601	fi
602
603	#####
604	#
605	# Here you may want to add other port initialization code.
606	# Some examples:
607	#
608	# estty	# for printer needing hardware flow control (3B2/EPORTS)
609	# fctty	# for printer needing hardware flow control (3B15,3B20)
610	#####
611	#estty 0<&1
612	#fctty 0<&1
613
614
615	##########
616	#
617	# Find out if we have to turn off opost before initializing the
618	# printer and on after. Likewise, check clocal.
619	#
620	# Turning OFF opost (output postprocessing) keeps the UNIX system
621	# from changing what we try to send to the printer. Turning ON
622	# clocal keeps the UNIX system from dropping what we are trying to
623	# send if the printer drops DTR. An example of the former is the
624	# AT&T 479, which wants to send a linefeed (ASCII 10) when a page
625	# width of 10 is set; with opost on, this COULD BE turned into a
626	# carriage-return/linefeed pair. An example of the latter is the
627	# AT&T 455, which momentarily drops DTR when it gets the
628	# initialization string, is2; with clocal off, the UNIX system
629	# stops sending the rest of the initialization sequence at that
630	# point.
631	#
632	# THIS CODE MUST FOLLOW THE REST OF THE PORT INITIALIZATION CODE.
633	##########
634	cur_stty=`stty -a 0<&3`
635	expr "${cur_stty}" : '.*-opost' 1>/dev/null 2>&1 \
636		|| stty1="${stty1} -opost" stty2="${stty2} opost"
637	expr "${cur_stty}" : '.*-clocal' 1>/dev/null 2>&1 \
638		&& stty1="${stty1} clocal" stty2="${stty2} -clocal"
639	expr "${cur_stty}" : '.* opost.*' 1>/dev/null 2>&1 \
640		|| banner_filter=${FIX386BD}
641
642}
643
644
645###########
646##
647## Initialize the physical printer (Part I).
648## Here we bring the printer to a sane state and set the page size.
649###########
650
651##########
652#
653# WARNING! The "echo" command will catch backslashes (\) and
654# try to interpret the characters following it. Thus, using
655# "echo" to print string values obtained from "tput" is dangerous.
656##########
657
658#####
659# We're confident that most printers don't have backslashes
660# in the control sequences for carriage return and form-feed.
661# We're also confident that these don't contain newlines.
662# We're also confident that most printers have a linefeed
663# in the control sequence for doing a newline (move to beginning
664# of next line), but we can't capture it like we do the
665# carriage return or form-feed. Thus we set it unconditionally.
666# We don't set form-feed if it isn't defined, however, because
667# maybe the printer doesn't have a formfeed. If not set, we're
668# out of luck.
669#####
670
671CR=`${TPUT} cr`
672[ -z "${CR}" ] && CR="\r"
673
674FF=`${TPUT} ff`
675
676NL="${CR}\n"
677
678lines=`${TPUT} lines`
679[ -z "${lines}" -o 0 -ge "${lines}" ] && lines=66
680
681cols=`${TPUT} cols`
682[ -z "${cols}" -o 0 -ge "${cols}" ] && cols=132
683
684#####
685#
686# Basic initialization. The ``else'' clause is equivalent,
687# but covers cases where old Terminal Information Utilities are present.
688#####
689[ -n "${stty1}" ] && stty ${stty1} 0<&1
690
691#
692# "tput init" will return an "^M" in many cases to "stdout", i.e., printer!
693# This creates problems for some PS printers
694#
695if [ "${TERM}" = "PS" -o "${TERM}" = "PSR" ]
696then
697	:
698elif ${TPUT} init 2>/dev/null
699then
700	:
701else
702	pgm=`${TPUT} iprog`
703	if [ -x "${pgm}" ]
704	then
705		eval ${pgm}
706	fi
707
708	${TPUT} is1
709	${TPUT} is2
710
711	tabset=
712	if [ "8" != "`${TPUT} it`" ]
713	then
714		stty tab3 0<&1 1>/dev/null 2>&1
715
716	elif `${TPUT} ht >/dev/null`
717	then
718		tabset="/usr/lib/tabset/${TERM}"
719		if [ -r ${tabset} ]
720		then
721			cat -s ${tabset}
722		fi
723		stty tab3 0<&1 1>/dev/null 2>&1
724	fi
725
726	file=`${TPUT} if`
727	if [ "${tabset}" != "${file}" -a -r "${file}" ]
728	then
729		cat -s "${file}"
730	fi
731
732	${TPUT} is3
733	echo "${CR}\c"
734fi
735[ -n "${stty2}" ] && stty ${stty2} 0<&1
736
737#####
738#
739# Set the page size and print spacing, but not the character set.
740# We will be doing the character set later (after the header).
741#####
742internal_lpset "${cpi}" "${lpi}" "${width}" "${length}" ""
743
744#####
745#
746# The banner page (and cancellation page) will
747# use double width characters if they're available.
748#####
749WIDE_CS=`${TPUT} swidm 2>/dev/null` && NORM_CS=`${TPUT} rwidm 2>/dev/null`
750PAD="#####${NL}"
751
752#####
753#
754# Some printers need to have the banner page filtered.
755#####
756case "${TERM}" in
757
758PS | PSR )
759	banner_filter="/usr/lib/lp/postscript/postprint | /usr/lib/lp/postscript/postio"
760	LPTELL_OPTS="-l"
761	;;
762
763esac
764if [ -n "${banner_filter}" ]
765then
766	banner_filter="| ${banner_filter}"
767fi
768
769#####
770#
771# Now that the printer is ready for printing, we're able
772# to record on paper a cancellation.
773#####
774
775cancel_banner () {
776	echo "${PAD}${PAD}\c"
777	echo "#####${WIDE_CS} Job ${request_id}${NORM_CS}${NL}\c"
778	echo "#####${WIDE_CS} suspended or canceled${NORM_CS}${NL}\c"
779	echo "${PAD}${PAD}\c"
780}
781
782canceled () {
783	${TPUT} scs 0 2>/dev/null
784	echo "${CR}\c"
785	if [ "${width:-${cols}}" -lt "${MAX_COLS_SMALL_BANNER}" ]
786	then
787		WIDE_CS= NORM_CS=
788	fi
789	cancel_banner
790	if [ -n "${FF}" ]
791	then
792		echo "${CR}${FF}\c"
793	fi
794}
795
796trap 'eval canceled ${banner_filter}; exit_code=0 exit' 15
797
798
799###########
800##
801## Print the banner page
802###########
803
804#####
805#
806# You may want to change the following code to get a custom banner.
807#####
808
809regular_banner () {
810	echo "${CR}\c"
811	echo "${PAD}${PAD}${PAD}${PAD}${PAD}\c"
812	echo "#####${WIDE_CS}       User: ${user_name}${NORM_CS}${NL}\c"
813	if [ -n "$ALIAS_USERNAME" ]
814	then
815		echo "${PAD}\c"
816		echo "#####${WIDE_CS}      Alias: ${ALIAS_USERNAME}${NORM_CS}${NL}\c"
817	fi
818	if [ -n "${title}" ]
819	then
820		echo "${PAD}\c"
821		echo "#####${WIDE_CS}      Title: ${title}${NORM_CS}${NL}\c"
822	fi
823	echo "${PAD}\c"
824	echo "#####${WIDE_CS}    Printed: `LANG=C date '+%a %H:%M %h %d, %Y'`${NORM_CS}${NL}\c"
825	echo "${PAD}\c"
826	echo "#####${WIDE_CS} Job number: ${request_id}${NORM_CS}${NL}\c"
827	echo "${PAD}${PAD}${PAD}${PAD}${PAD}\c"
828	if [ -n "${FF}" ]
829	then
830		echo "${CR}${FF}\c"
831	fi
832}
833
834small_banner () {
835	echo "${CR}\c"
836	echo "${PAD}\c"
837	echo "#####  User: ${user_name}${NL}\c"
838	if [ -n "${title}" ]
839	then
840		echo "##### Title: ${title}${NL}\c"
841	fi
842	echo "#####  Date: `LANG=C date '+%a %H:%M %h %d, %Y'`${NL}\c"
843	echo "#####   Job: ${request_id}${NL}\c"
844	echo "${PAD}\c"
845	if [ -n "${FF}" ]
846	then
847		echo "${CR}${FF}\c"
848	fi
849}
850
851if [ "${width:-${cols}}" -lt "${MAX_COLS_SMALL_BANNER}" ]
852then
853	banner=small_banner
854else
855	banner=regular_banner
856fi
857
858if [ "no" = "${nobanner}" -a "${TERM}" != "PSR" ]
859then
860	( eval "${banner} ${banner_filter}" 2>&1 1>&3 ) \
861		| ${LPTELL} ${LPTELL_OPTS} ${printer}
862fi
863
864
865###########
866##
867## Initialize the physical printer (Part II)
868## Here we select the character set.
869## One could argue that this should be done before the banner is printed,
870## but we don't, to keep the banner page looking consistent for the
871## operator. You can move this code before the banner code if you
872## disagree. If you do, combine it with the other call to "internal_lpset"
873## to do everything in one shot.
874###########
875internal_lpset "" "" "" "" "${CHARSET}"
876
877###########
878##
879## Print some copies of the file(s)
880###########
881
882#####
883#
884# The protocol between the interface program and the Spooler
885# is fairly simple:
886#
887#	All standard error output is assumed to indicate a
888#	fault WITH THE REQUEST. The output is mailed to the
889#	user who submitted the print request and the print
890#	request is finished.
891#
892#	If the interface program sets a zero exit code,
893#	it is assumed that the file printed correctly.
894#	If the interface program sets a non-zero exit code
895#	less than 128, it is assumed that the file did not
896#	print correctly, and the user will be notified.
897#	In either case the print request is finished.
898#
899#	If the interface program sets an exit code greater
900#	than 128, it is assumed that the file did not print
901#	because of a printer fault. If an alert isn't already
902#	active (see below) one will be activated. (Exit code
903#	128 should not be used at all. The shell, which executes
904#	this program, turns SIGTERM, used to kill this program
905#	for a cancellation or disabling, into exit 128. The
906#	Spooler thus interpretes 128 as SIGTERM.)
907#
908#	A message sent to the standard input of the ${LPTELL}
909#	program is assumed to describe a fault WITH THE PRINTER.
910#	The output is used in an alert (if alerts are defined).
911#	If the fault recovery is "wait" or "begin", the printer
912#	is disabled (killing the interface program if need be),
913#	and the print request is left on the queue.
914#	If the fault recovery is "continue", the interface program
915#	is allowed to wait for the printer fault to be cleared so
916#	it can resume printing.
917#
918# This interface program relies on filters to detect printer faults.
919# In absence of a filter provided by the customer, it uses a simple
920# filter (${LPCAT}) to detect the class of faults that cause DCD
921# (``carrier'') drop. The protocol between the interface program and
922# the filter:
923#
924#	The filter should exit with zero if printing was
925#	successful and non-zero if printing failed because
926#	of a printer fault. This interface program turns a
927#	non-zero exit of the filter into an "exit 129" from
928#	itself, thus telling the Spooler that a printer fault
929#	(still) exists.
930#
931#	The filter should report printer faults via a message
932#	to its standard error. This interface program takes all
933#	standard error output from the filter and feeds it as
934#	standard input to the ${LPTELL} program.
935#
936#	The filter should wait for a printer fault to clear,
937#	and should resume printing when the fault clears.
938#	Preferably it should resume at the top of the page
939#	that was being printed when the fault occurred.
940#	If it waits and finishes printing, it should exit
941#	with a 0 exit code. If it can't wait, it should exit
942#	with a non-zero exit code.
943#
944#	The interface program expects that ANY message on the
945#	standard error from the filter indicates a printer fault.
946#	Therefore, a filter should not put user (input) error
947#	messages on the standard error, but on the standard output
948#	(where the user can read them when he or she examines
949#	the print-out).
950#
951#####
952
953badfileyet=
954i=1
955while [ $i -le $copies ]
956do
957	for file in ${files}
958	do
959		if [ -r "${file}" ]
960		then
961			#####
962			#
963			# Here's where we set up the $LPTELL program to
964			# capture fault messages, and...
965			#
966			# Here's where we print the file.
967			#
968			# We set up a pipeline to $LPTELL, but play a trick
969			# to get the filter's standard ERROR piped instead of
970			# its standard OUTPUT: Divert the standard error (#2) to
971			# the standard output (#1) IN THE PIPELINE. The shell
972			# will have changed #1 to be the pipe, not the
973			# printer, so diverting #2 connects it to the pipe.
974			# We then change the filter's #1 to a copy of the real
975			# standard output (the printer port) made earlier,
976			# so that is connected back to the printer again.
977			#
978			# We do all this inside a parenthesized expression
979			# so that we can get the exit code; this is necessary
980			# because the exit code of a pipeline is the exit
981			# code of the right-most command, which isn't the
982			# filter.
983			#
984			# These two tricks could be avoided by using a named
985			# pipe to connect the standard error to $LPTELL. In
986			# fact an early prototype of this script did just
987			# that; however, the named pipe introduced a timing
988			# problem. The processes that open a named pipe hang
989			# until both ends of the pipe are opened. Cancelling
990			# a request or disabling the printer often killed one
991			# of the processes, causing the other process to hang
992			# forever waiting for the other end of the pipe to
993			# be opened.
994			#####
995			EXIT_CODE=${TMPPREFIX}e
996			trap '' 1	# Let the filter handle a hangup
997			trap '' 2 3	# and interrupts
998			(
999				#####
1000				# Put the 0<${file} before the "eval" to keep
1001				# clever users from giving a file name that
1002				# evaluates as something to execute.
1003				#####
1004				0<${file} eval ${FILTER} 2>&1 1>&3
1005				echo $? >${EXIT_CODE}
1006			) | ${LPTELL} ${LPTELL_OPTS} ${printer}
1007			trap 'catch_hangup; exit_code=129 exit 129' 1
1008			trap 'catch_interrupt; exit_code=129 exit 129' 2 3
1009			exit_code=`cat ${EXIT_CODE}`
1010
1011			if [ -n "${exit_code}" -a 0 -ne "${exit_code}" ]
1012			then
1013				trap '' 15  # Avoid dying from disable
1014				sleep 4	    # Give $LPTELL a chance to tell
1015				exit ${exit_code}
1016			fi
1017
1018			if [ -n "${FF}" -a "no" = "${nofilebreak}" ]
1019			then
1020				echo "${CR}${FF}\c"
1021			fi
1022
1023		else
1024
1025			#####
1026			#
1027			# Don't complain about not being able to read
1028			# a file on second and subsequent copies, unless
1029			# we've not complained yet. This removes repeated
1030			# messages about the same file yet reduces the
1031			# chance that the user can remove a file and not
1032			# know that we had trouble finding it.
1033			#####
1034			if [ "${i}" -le 1 -o -z "${badfileyet}" ]
1035			then
1036				errmsg WARNING ${E_IP_BADFILE} \
1037					"cannot read file \"${file}\"" \
1038					"see if the file still exists and is readable,
1039		or consult your system administrator;
1040		printing continues"
1041				badfileyet=yes
1042			fi
1043
1044		fi
1045
1046	done
1047	i=`expr $i + 1`
1048
1049done
1050
1051if [ "no" = "${nobanner}" -a "${TERM}" = "PSR" ]
1052then
1053	( eval "${banner} ${banner_filter}" 2>&1 1>&3 ) \
1054		| ${LPTELL} ${LPTELL_OPTS} ${printer}
1055fi
1056
1057if [ -n "${exit_code}" -a 0 -ne "${exit_code}" ]
1058then
1059	exit ${exit_code}
1060fi
1061
1062#####
1063#
1064# Always ensure the complete job ends with a ``formfeed'', to
1065# let the next job start on a new page. (If someone wants to
1066# concatenate files, they can give them in one job.)
1067# So, if we haven't been putting out a ``formfeed'' between files,
1068# it means we haven't followed the last file with a formfeed,
1069# so we do it here.
1070#####
1071if [ -n "${FF}" -a "yes" = "${nofilebreak}" ]
1072then
1073	echo "${CR}${FF}\c"
1074fi
1075
1076${DRAIN}
1077
1078exit_code=0 exit 0
1079