#!/usr/bin/sh
#ident	"@(#)lp:model/PS	1.1.4.8"
#ident  "$Header: PS 1.2 91/06/27 $"

###########
##
##  PostScript interface program.
##
###########
umask 0


#####
##
##  Some logs.
##
#####
LOGFILE="/var/tmp/$$.log"
ERRFILE="/var/tmp/$$.err"

/sbin/rm -f $LOGFILE $ERRFILE

#####
#
#  Special for debugging.
#
#####
DEBUGFILE="/var/tmp/PS.debug"
#echo "$LOGFILE" >$DEBUGFILE
#echo "$ERRFILE" >>$DEBUGFILE
#echo "$*" >>$DEBUGFILE
#/usr/bin/env >>$DEBUGFILE

#####
##
##  Some tools.  Options will be added later.
##
#####
LPBIN="/usr/lib/lp/bin"
PSBIN="/usr/lib/lp/postscript"
PATH="/bin:/usr/bin:${LPBIN}"

LPCAT="$LPBIN/lp.cat"
LPTELL="$LPBIN/lp.tell"

#####
# ${DRAIN} is the name of a program that will wait
# long enough for data sent to the printer to print.
#####
if [ -x "${LPBIN}/drain.output" ]
then
	DRAIN="${LPBIN}/drain.output 5"	# wait only five seconds
else
	DRAIN=
fi

POSTPRINT="$PSBIN/postprint"
DOWNLOAD="$PSBIN/download"
POSTREVERSE="$PSBIN/postreverse"
POSTIO="$PSBIN/postio"
POSTIO_B="$LPCAT"

CAT=/usr/bin/cat
MAILX=/usr/bin/mailx

#####
#
# Until we get to the point below where the printer port
# and physical printer are initialized, we can't do much
# except exit if the Spooler/Scheduler cancels us.
#
#####
ExitCode=0

Exit ()
{
	if [ "$debug" = "yes" -a -n "$user_name" ]
	then
		if [ -s $LOGFILE ]
		then
			$MAILX -s "$request_id: LOG FILE" \
				$user_name <$LOGFILE
		fi
		if [ -s $ERRFILE ]
		then
			$MAILX -s "$request_id: ERROR FILE" \
				$user_name <$ERRFILE
		fi
		if [ -s $DEBUGFILE ]
		then
			$MAILX -s "$request_id: DEBUG FILE" \
				$user_name  <$DEBUGFILE
		fi
	fi
	rm -f $ERRFILE $LOGFILE $DEBUGFILE
	if [ -n "$1" ]
	then
		exit $1
	else
		exit $ExitCode
	fi
}

trap 'Exit 0' 15

#####
#
# We can be clever about getting a hangup or interrupt, though, at least
# until the filter runs. Do this early, even though $LPTELL
# isn't defined, so that we're covered.
#
#####
catch_hangup ()
{
	echo \
"The connection to the printer dropped; perhaps the printer went off-line?" \
	>$ERRFILE
	0<$ERRFILE ${LPTELL} ${printer}
	$CAT $ERRFILE >>$LOGFILE
	>$ERRFILE
	return	0
}

catch_interrupt ()
{
	echo \
"Received an interrupt from the printer.  The reason is unknown,
although a common cause is that the baud rate is too high." \
	>$ERRFILE
	0<$ERRFILE ${LPTELL} ${printer}
	$CAT $ERRFILE >>$LOGFILE
	>$ERRFILE
	return	0
}
trap 'catch_hangup; Exit 129' 1
trap 'catch_interrupt; Exit 129' 2 3

#####
#
# Most of the time we don't want the standard error to be captured
# by the Spooler, mainly to avoid "Terminated" messages that the
# shell puts out when we get a SIGTERM. We'll save the standard
# error channel under another number, so we can use it when it
# should be captured.
#
# Open another channel to the printer port, for use when the
# regular standard output won't be directed there, such as in
# command substitution (`cmd`).
#####
exec 5>&2 2>>$ERRFILE 3>&1

#####
# Error message formatter:
#
# Invoke as
#
#	errmsg severity message-number problem help
#
# where severity is "ERROR" or "WARNING", message-number is
# a unique identifier, problem is a short description of the
# problem, and help is a short suggestion for fixing the problem.
#####

LP_ERR_LABEL="UX:lp"

E_IP_ARGS=1
E_IP_OPTS=2
#E_IP_FILTER=3
E_IP_STTY=4
E_IP_UNKNOWN=5
E_IP_BADFILE=6
E_IP_BADCHARSET=7
E_IP_BADCPI=8
E_IP_BADLPI=9
E_IP_BADWIDTH=10
E_IP_BADLENGTH=11
E_IP_ERRORS=12		# (in slow.filter)

errmsg ()
{
	case $1 in
	ERROR )
		sev="  ERROR";
		;;
	WARNING )
		sev="WARNING";
		;;
	esac
	echo "${LP_ERR_LABEL}: ${sev}: $3
        TO FIX: $4" >&5
}

#####
#
# This program is invoked as
#
# ${SPOOLDIR}/.../printer request-id user title copies options files...
#
# The first three arguments are simply reprinted on the banner page,
# the fourth (copies) is used to control the number of copies to print,
# the fifth (options) is a blank separated list (in a single argument)
# of user or Spooler supplied options (without the -o prefix),
# and the last arguments are the files to print.
#####

if [ $# -lt 5 ]
then
	errmsg ERROR ${E_IP_ARGS} \
		"wrong number of arguments to interface program" \
		"consult your system administrator"
	Exit 1
fi

printer=`basename $0`
request_id=$1
user_name=$2
title=$3
copies=$4
option_list=$5

shift 5
files="$*"

nobanner="no"
nofilebreak="no"
debug="no"
stty=

inlist=
for i in ${option_list}
do
	case "${inlist}${i}" in


	nobanner )
		nobanner="yes"
		;;

	nofilebreak )
		nofilebreak="yes"
		;;

	debug )
		debug="yes"
		;;
	copies=* )
		;;
	length=* )
		;;
	pages=* )
		;;

	#####
	#
	# If you want to add options that, like "stty",
	# take a list (e.g. -o lopt='a b c'), identify
	# them here and below (look for LOPT).
	#####
	stty=* | flist=* | lpd=* )
#LOPT	stty=* | flist=* | lpd=* | lopt=* )

		inlist=`expr "${inlist}${i}" : "^\([^=]*=\)"`
		case "${i}" in
		${inlist}\'*\' )
			item=`expr "${i}" : "^[^=]*='*\(.*\)'\$"`
			;;
		${inlist}\' )
			continue
			;;
		${inlist}\'* )
			item=`expr "${i}" : "^[^=]*='*\(.*\)\$"`
			;;
		${inlist}* )
			item=`expr "${i}" : "^[^=]*=\(.*\)\$"`
			;;
		*\' )
			item=`expr "${i}" : "^\(.*\)'\$"`
			;;
		* )
			item="${i}"
			;;
		esac

		#####
		#
		# We don't dare use "eval" because a clever user could
		# put something in an option value that we'd end up
		# exec'ing.
		#####
		case "${inlist}" in
		stty= )
			stty="${stty} ${item}"
			;;
		flist= )
			flist="${flist} ${item}"
			;;
		lpd= )
			lpd="${lpd} ${item}"
			;;
#LOPT		lopt= )
#LOPT			lopt="${lopt} ${item}"
#LOPT			;;
		esac

		case "${i}" in
		${inlist}\'*\' )
			inlist=
			;;
		${inlist}\'* )
			;;
		*\' | ${inlist}* )
			inlist=
			;;
		esac
		;;

	* )
		errmsg WARNING ${E_IP_OPTS} \
			"unrecognized \"-o ${i}\" option" \
			"check the option, resubmit if necessary
		printing continues"
		;;
	esac
done

#####
#
# Additional ``parameters'' are passed via Shell environment
# variables:
#
#	TERM	The printer type (used for Terminfo access)
#	FILTER	The filter to run
#####

if [ -z "$TERM" ]
then
	errmsg ERROR ${E_IP_ARGS} \
		"Missing mandatory environment variable." \
		"Consult your system administrator"
	Exit 1
fi
if [ -z "$FILTER" ]
then
	FILTER="$LPCAT 0"
fi

#####
##
##  Banner filter
##
#####
	#####
	##
	##  Where to find fonts (if appropriate).
	##
	#####
	DOWNLOAD="$DOWNLOAD -p$printer"

	POSTIO="$POSTIO 2>>$ERRFILE"

	#####
	##  Allow infinite delays on write.
	#####
	POSTIO_B="$POSTIO_B 0 2>>$ERRFILE"	

	case "${TERM}" in
	"PS" | "PS-r" | "PSR")
		BFILTER="$DOWNLOAD | $POSTIO"
		;;

	"PS-b" | "PS-br" )
		BFILTER="$DOWNLOAD | $POSTIO_B"
		;;

	*)
		errmsg ERROR ${E_IP_ARGS} \
			"Bad mandatory environment variable." \
			"Consult your system administrator."
		Exit 1
		;;
	esac

EndJob ()
{
	if [ "$TERM" = "PS-b" -o "$TERM" = "PS-br" ]
	then
		echo "\004\c"
	fi
}

###########
##
## Initialize the printer port
##
###########

#####
#
# SERIAL PORTS:
# Initialize everything.
#
# PARALLEL PORTS:
# Don't initialize baud rate.
#
# It's not obvious how to tell if a port is parallel or serial.
# However, by splitting the initialization into two steps and letting
# the serial-only part fail nicely, it'll work.
#
# Another point: The output must be a ``tty'' device. If not, don't
# bother with any of this.
#####
stty1= stty2=
tty 0<&1 1>/dev/null 2>&1 && {

	#####
	#
	# First set the default parameters,
	# then the requested parameters.
	#####

	stty 9600 0<&1 2>/dev/null 1>&2

	stty cs8 -cstopb -parenb -parodd \
		ixon -ixany \
		opost -olcuc onlcr -ocrnl -onocr -onlret -ofill \
		nl0 cr0 tab0 bs0 vt0 ff0 \
			0<&1 2>/dev/null 1>&2

	if [ -n "${stty}" ]
	then
		if stty ${stty} 0<&1 1>/dev/null 2>&5
		then
			:
		else
			errmsg ERROR ${E_IP_STTY} \
				"stty option list failed" \
				"check the \"-o stty\" option you used,
		or consult your system administrator"
			Exit 1
		fi
	fi


	##########
	#
	# Find out if we have to turn off opost before initializing the
	# printer and on after. Likewise, check clocal.
	#
	# Turning OFF opost (output postprocessing) keeps the UNIX system
	# from changing what we try to send to the printer. Turning ON
	# clocal keeps the UNIX system from dropping what we are trying to
	# send if the printer drops DTR. An example of the former is the
	# AT&T 479, which wants to send a linefeed (ASCII 10) when a page
	# width of 10 is set; with opost on, this COULD BE turned into a
	# carriage-return/linefeed pair. An example of the latter is the
	# AT&T 455, which momentarily drops DTR when it gets the
	# initialization string, is2; with clocal off, the UNIX system
	# stops sending the rest of the initialization sequence at that
	# point.
	#
	# THIS CODE MUST FOLLOW THE REST OF THE PORT INITIALIZATION CODE.
	##########
	cur_stty=`stty -a 0<&3`
	expr "${cur_stty}" : '.*-opost' 1>/dev/null 2>&1 \
		|| stty1="${stty1} -opost" stty2="${stty2} opost"
	expr "${cur_stty}" : '.*-clocal' 1>/dev/null 2>&1 \
		&& stty1="${stty1} clocal" stty2="${stty2} -clocal"
	expr "${cur_stty}" : '.* opost.*' 1>/dev/null 2>&1 \
		|| banner_filter=${FIX386BD}

}



#####
#
# Basic initialization. The ``else'' clause is equivalent,
# but covers cases where old Terminal Information Utilities are present.
#####
[ -n "${stty1}" ] && stty ${stty1} 0<&1
[ -n "${stty2}" ] && stty ${stty2} 0<&1


#####
#
# Now that the printer is ready for printing, we're able
# to record on paper a cancellation.
#
#####

cancel_page ()
{
	originator="$user_name"
	if [ -n "$ALIAS_USERNAME" ]
	then
		user="$ALIAS_USERNAME"
	fi
	title="*** Cancelled ***"
	$CAT $PSBIN/banner.ps
	echo "(`LC_ALL=C LC_CTYPE=C LC_TIME=C date '+%a %H:%M %h %d, 19%y'`)"
	echo "($request_id)"
	echo "($title)"
	echo "($originator)"
	echo "($user_name)"
	echo "banner"
}

cancel_job ()
{
	EndJob	#  if needed.
	echo "##"			     >>$LOGFILE
	echo "##  Printing cancel page."     >>$LOGFILE
	echo "##  Banner filter: '$BFILTER'" >>$LOGFILE
	echo "##"			     >>$LOGFILE
	eval "cancel_page | $BFILTER"
	ExitCode=$?
	EndJob
	if [ $ExitCode -ne 0 ]
	then
		$CAT $ERRFILE >>$LOGFILE
		echo "##"			     >>$LOGFILE
		echo "##  Cancel page failed."       >>$LOGFILE
		echo "##  ExitCode: $ExitCode"       >>$LOGFILE
		echo "##"			     >>$LOGFILE
		0<$ERRFILE $LPTELL $printer
		>$ERRFILE
		Exit
	else
		$CAT $ERRFILE >>$LOGFILE
		>$ERRFILE
	fi
}
trap   'cancel_job; Exit 0' 15

###########
#
# Print the banner page
#
###########

#####
#
# You may want to change the following code to get a custom banner.
#
#####

banner_page ()
{
	originator="$user_name"
	if [ -n "$ALIAS_USERNAME" ]
	then
		user="$ALIAS_USERNAME"
	fi
	if [ -z "${title}" ]
	then
		title="<untitled>"
	fi
	$CAT $PSBIN/banner.ps
	echo "(`LC_ALL=C LC_CTYPE=C LC_TIME=C date '+%a %H:%M %h %d, 19%y'`)"
	echo "($request_id)"
	echo "($title)"
	echo "($originator)"
	echo "($user_name)"
	echo "banner"
}


#####
#
#  Generate banner page for non-reversed job.
#
#####
if [ "no" = "${nobanner}" -a \( "${TERM}" = "PS" -o "$TERM" = "PS-b" \) ]
then
	echo "##"			     >>$LOGFILE
	echo "##  Printing banner page."     >>$LOGFILE
	echo "##  Banner filter: '$BFILTER'" >>$LOGFILE
	echo "##"			     >>$LOGFILE
	eval "banner_page | $BFILTER"
	ExitCode=$?
	EndJob
	if [ $ExitCode -ne 0 ]
	then
		$CAT $ERRFILE >>$LOGFILE
		echo "##"			     >>$LOGFILE
		echo "##  Banner page failed."       >>$LOGFILE
		echo "##  ExitCode: $ExitCode"       >>$LOGFILE
		echo "##"			     >>$LOGFILE
		0<$ERRFILE $LPTELL $printer
		>$ERRFILE
		Exit
	else
		$CAT $ERRFILE >>$LOGFILE
		>$ERRFILE
	fi
fi

###########
##
## Print some copies of the file(s)
##
###########

#####
#
# The protocol between the interface program and the Spooler
# is fairly simple:
#
#	All standard error output is assumed to indicate a
#	fault WITH THE REQUEST. The output is mailed to the
#	user who submitted the print request and the print
#	request is finished.
#
#	If the interface program sets a zero exit code,
#	it is assumed that the file printed correctly.
#	If the interface program sets a non-zero exit code
#	less than 128, it is assumed that the file did not
#	print correctly, and the user will be notified.
#	In either case the print request is finished.
#
#	If the interface program sets an exit code greater
#	than 128, it is assumed that the file did not print
#	because of a printer fault. If an alert isn't already
#	active (see below) one will be activated. (Exit code
#	128 should not be used at all. The shell, which executes
#	this program, turns SIGTERM, used to kill this program
#	for a cancellation or disabling, into exit 128. The
#	Spooler thus interpretes 128 as SIGTERM.)
#
#	A message sent to the standard input of the ${LPTELL}
#	program is assumed to describe a fault WITH THE PRINTER.
#	The output is used in an alert (if alerts are defined).
#	If the fault recovery is "wait" or "begin", the printer
#	is disabled (killing the interface program if need be),
#	and the print request is left on the queue.
#	If the fault recovery is "continue", the interface program
#	is allowed to wait for the printer fault to be cleared so
#	it can resume printing.
#
# This interface program relies on filters to detect printer faults.
# In absence of a filter provided by the customer, it uses a simple
# filter (${LPCAT}) to detect the class of faults that cause DCD
# (``carrier'') drop. The protocol between the interface program and
# the filter:
#
#	The filter should exit with zero if printing was
#	successful and non-zero if printing failed because
#	of a printer fault. This interface program turns a
#	non-zero exit of the filter into an "exit 129" from
#	itself, thus telling the Spooler that a printer fault
#	(still) exists.
#
#	The filter should report printer faults via a message
#	to its standard error. This interface program takes all
#	standard error output from the filter and feeds it as
#	standard input to the ${LPTELL} program.
#
#	The filter should wait for a printer fault to clear,
#	and should resume printing when the fault clears.
#	Preferably it should resume at the top of the page
#	that was being printed when the fault occurred.
#	If it waits and finishes printing, it should exit
#	with a 0 exit code. If it can't wait, it should exit
#	with a non-zero exit code.
#
#	The interface program expects that ANY message on the
#	standard error from the filter indicates a printer fault.
#	Therefore, a filter should not put user (input) error
#	messages on the standard error, but on the standard output
#	(where the user can read them when he or she examines
#	the print-out).
#
#####

badfileyet=
i=1
while [ $i -le $copies ]
do
	for file in ${files}
	do

		if [ -r "${file}" ]
		then

			#####
			#
			# Here's where we set up the $LPTELL program to
			# capture fault messages, and...
			#
			# Here's where we print the file.
			#
			# We set up a pipeline to $LPTELL, but play a trick
			# to get the filter's standard ERROR piped instead of
			# its standard OUTPUT: Divert the standard error (#2) to
			# the standard output (#1) IN THE PIPELINE. The shell
			# will have changed #1 to be the pipe, not the
			# printer, so diverting #2 connects it to the pipe.
			# We then change the filter's #1 to a copy of the real
			# standard output (the printer port) made earlier,
			# so that is connected back to the printer again.
			#
			# We do all this inside a parenthesized expression
			# so that we can get the exit code; this is necessary
			# because the exit code of a pipeline is the exit
			# code of the right-most command, which isn't the
			# filter.
			#
			# These two tricks could be avoided by using a named
			# pipe to connect the standard error to $LPTELL. In
			# fact an early prototype of this script did just
			# that; however, the named pipe introduced a timing
			# problem. The processes that open a named pipe hang
			# until both ends of the pipe are opened. Cancelling
			# a request or disabling the printer often killed one
			# of the processes, causing the other process to hang
			# forever waiting for the other end of the pipe to
			# be opened.
			#####

			trap '' 1	# Let the filter handle a hangup
			trap '' 2 3	# and interrupts

			#####
			#  Put the 0<${file} before the "eval" to keep
			#  clever users from giving a file name that
			#  evaluates as something to execute.
			#####
			echo "##"			     >>$LOGFILE
			echo "##  Printing file: '$file'"    >>$LOGFILE
			echo "##"			     >>$LOGFILE
			0<${file} eval ${FILTER} 2>&1 1>&3
			ExitCode=$?
			EndJob

			trap 'catch_hangup; exit 129' 1
			trap 'catch_interrupt; exit 129' 2 3

			if [ "${ExitCode}" -ne 0 ]
			then
				$CAT $ERRFILE >>$LOGFILE
				echo "##"                      >>$LOGFILE
				echo "##  Job failed."         >>$LOGFILE
				echo "##  ExitCode: $ExitCode" >>$LOGFILE
				echo "##"                      >>$LOGFILE
				trap '' 15  # Avoid dying from disable
				0<$ERRFILE $LPTELL $printer
				>$ERRFILE
				sleep 4	    # Give $LPTELL a chance to tell
				Exit 129
			else
				$CAT $ERRFILE >>$LOGFILE
				>$ERRFILE
			fi
		else
			#####
			#
			# Don't complain about not being able to read
			# a file on second and subsequent copies, unless
			# we've not complained yet. This removes repeated
			# messages about the same file yet reduces the
			# chance that the user can remove a file and not
			# know that we had trouble finding it.
			#
			#####
			if [ "${i}" -le 1 -o -z "${badfileyet}" ]
			then
				errmsg WARNING ${E_IP_BADFILE} \
					"cannot read file \"${file}\"" \
					"see if the file still exists and is readable,
		or consult your system administrator;
		printing continues"
				badfileyet=yes
			fi

		fi

	done
	i=`expr $i + 1`

done

#####
#
#  Banner page for reversed job.
#
#####
if [ "no" = "${nobanner}" -a \
	\( "${TERM}" = "PS-r" -o "$TERM" = "PS-br" -o "$TERM" = "PSR" \) ]
then
	echo "##"			     >>$LOGFILE
	echo "##  Printing banner page."     >>$LOGFILE
	echo "##  Banner filter: '$BFILTER'" >>$LOGFILE
	echo "##"			     >>$LOGFILE
	eval "banner_page | $BFILTER"
	ExitCode=$?
	EndJob
	if [ $ExitCode -ne 0 ]
	then
		$CAT $ERRFILE >>$LOGFILE
		echo "##"			     >>$LOGFILE
		echo "##  Banner page failed."       >>$LOGFILE
		echo "##  ExitCode: $ExitCode"       >>$LOGFILE
		echo "##"			     >>$LOGFILE
		0<$ERRFILE $LPTELL $printer
		>$ERRFILE
		Exit
	else
		$CAT $ERRFILE >>$LOGFILE
		>$ERRFILE
	fi
fi


${DRAIN}

Exit ${ExitCode}
