/*	Copyright (c) 1990 UNIX System Laboratories, Inc.	*/
/*	Copyright (c) 1984, 1986, 1987, 1988, 1989, 1990 AT&T	*/
/*	  All Rights Reserved  	*/

/*	THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF     	*/
/*	UNIX System Laboratories, Inc.                     	*/
/*	The copyright notice above does not evidence any   	*/
/*	actual or intended publication of such source code.	*/

#ident	"@(#)lp:cmd/lpadmin/do_align.c	1.9.2.1"

#include "stdio.h"
#include "errno.h"
#include "sys/types.h"
#include "signal.h"
#include "setjmp.h"
#include "string.h"
#include "stdlib.h"

#include "lp.h"
#include "msgs.h"
#include "printers.h"
#include "requests.h"
#include "form.h"

#define	WHO_AM_I	I_AM_LPADMIN
#include "oam.h"

#include "lpadmin.h"


extern void		mount_unmount();

extern short		printer_status;

extern char		*cur_pwheel,
			*disable_reason,
			*reject_reason;

extern FORM		formbuf;

static int		again();

static void		disable(),
			enable(),
			accept(),
			reject(),
			cancel(),
			sigpipe(),
			sigother();

static jmp_buf		cleanup_env,
			pipe_env;

/**
 ** do_align() - SET UP PRINTER TO PRINT ALIGNMENT PATTERNS
 **/

int			do_align (printer, form, pwheel)
	char			*printer,
				*form,
				*pwheel;
{
	short			status;

	char			*req_id		= 0,
				*file_prefix,
				*rfile,
				*fifo,
				buffer[MSGMAX];

	long			printer_chk;

	int			try;

	FILE			*align_fp,
				*fifo_fp;

	REQUEST			req;

	void			(*old_sighup)(),
				(*old_sigint)(),
				(*old_sigquit)(),
				(*old_sigterm)();


	/*
	 * Having reached this point means we've already fetched
	 * the form definition. Now get the alignment pattern.
	 */
	if (getform(form, (FORM *)0, (FALERT *)0, &align_fp) == -1) {
		LP_ERRMSG2 (ERROR, E_LP_GETFORM, form, PERROR);
		done (1);
	}
	if (!align_fp) {
		LP_ERRMSG1 (WARNING, E_ADM_NOALIGN, form);
		return (0);
	}

	/*
	 * Having reached this far also means we've already obtained
	 * the printer status from the Spooler. We'll be changing the
	 * status of the printer and queue and will have to restore
	 * the disable/reject reasons.
	 * NOTE: We can't restore the dates!
	 */


	/*
	 * Construct a request to print a ``file'' for copy. The
	 * equivalent "lp" command (with a filename) would be:
	 *
	 * lp -p printer -H immediate -f form -T type -S charset -c -P 1-N
	 *
	 * "type", "charset", and "N" are derived from the form def'n.
	 * This command would make us next to print ONCE THE FORM IS
	 * MOUNTED.
	 *
	 * NOTE: Don't bother with the -S charset if it isn't mandatory,
	 * so we won't get a rejection. Also, we use either the print
	 * wheel given in the -S option or, lacking that, the currently
	 * mounted print wheel. (The former WILL be mounted.) This also
	 * avoids a rejection by the Spooler.
	 */
	req.copies	= 1;
	req.destination	= printer;
/*	req.file_list	= 0;	This is done later. */
	req.form	= form;
	req.actions	= ACT_IMMEDIATE | ACT_FAST;
	req.alert	= 0;
	req.options	= "nobanner";
	req.priority	= 20;	/* it doesn't matter */
	sprintf ((req.pages = "1-999999")+2, "%d", formbuf.np);
	req.charset	= NAME_ANY;	/* Don't restrict the request */
	req.modes	= 0;
	req.title	= "Aligning Form";
	req.input_type	= formbuf.conttype;
	req.user	= getname();


	/*
	 * The following code is sensitive to interrupts: We must
	 * catch interrupts so to restore the printer to its original
	 * state, but if we get interrupted while receiving a message
	 * from the Spooler, we can't issue additional messages because
	 * the old responses still in the response queue will confuse us.
	 * Thus while sending/receiving a message we ignore signals.
	 */
	if (setjmp(cleanup_env) != 0)
		done (1);
	trap_signals (); /* make sure we've done this once */
	old_sighup = signal(SIGHUP, sigother);
	old_sigint = signal(SIGINT, sigother);
	old_sigquit = signal(SIGQUIT, sigother);
	old_sigterm = signal(SIGTERM, sigother);

	/*
	 * We'll try the following twice, first with the page list
	 * set as above. If the request gets refused because there's
	 * no filter to convert the content, we'll try again without
	 * the page list. I don't think the number-of-pages-in-a-form
	 * feature is likely to be used much, so why hassle the
	 * administrator?
#if	defined(WARN_OF_TOO_MANY_LINES)
	 * However, do warn him or her.
#endif
	 */

	try = 0;
Again:	try++;

	/*
	 * Have the Spooler allocate a request file and another file
	 * for our use. We'll delete the other file and recreate it
	 * as a FIFO. We can do this because "lpadmin" can only be run
	 * (successfully) by an administrator. This is the key to what
	 * we're doing! We are submitting a named pipe (FIFO) for
	 * printing, which gives us a connection to the printer
	 * through any filters needed!
	 */

	BEGIN_CRITICAL
		send_message (S_ALLOC_FILES, 2);
		if (mrecv(buffer, MSGMAX) != R_ALLOC_FILES) {
			LP_ERRMSG (ERROR, E_LP_MRECV);
			done (1);
		}
	END_CRITICAL
	(void)getmessage (buffer, R_ALLOC_FILES, &status, &file_prefix);

	switch (status) {
	case MOK:
		break;

	case MNOMEM:
		LP_ERRMSG (ERROR, E_LP_MNOMEM);
		done (1);
	}

	if (!(rfile = malloc((unsigned int)strlen(file_prefix) + 2 + 1))) {
		LP_ERRMSG (ERROR, E_LP_MALLOC);
		done (1);
	}

	sprintf (rfile, "%s-1", file_prefix);

	if (!(fifo = makepath(Lp_Temp, rfile, (char *)0))) {
		LP_ERRMSG (ERROR, E_LP_MALLOC);
		done (1);
	}
	req.file_list = 0;
	addlist (&req.file_list, fifo);

	if (
		Unlink(fifo) == -1
	     || Mknod(fifo, S_IFIFO | 0600, 0) == -1
	) {
		LP_ERRMSG1 (ERROR, E_ADM_NFIFO, PERROR);
		done (1);
	}

	/*
	 * In quick succession,
	 *
	 *	- mount the form,
	 *	- disable the printer,
	 *	- make the Spooler accept requests (if need be),
	 *	- submit the request,
	 *	- make the Spooler reject requests (if need be).
	 *
	 * We want to minimize the window when another request can
	 * be submitted ahead of ours. Though this window is small,
	 * it is a flaw in our design. Disabling the printer will
	 * help, because it will stop any request that is printing
	 * (if the form is already mounted) and will prevent any other
	 * request from printing. (We disable the printer AFTER trying
	 * to mount the form, because we don't disable a printer for a
	 * regular mount, and we'd like to make this mount APPEAR to
	 * be as similar as possible.)
	 */

	if (try == 1) {

		mount_unmount (S_MOUNT, printer, NB(form), NB(pwheel));
		/* This will die if the mount fails, leaving */
		/* the Spooler to clean up our files.        */

		if (!(printer_status & PS_DISABLED))
			disable (printer, CUZ_MOUNTING, 0);

		if (printer_status & PS_REJECTED)
			accept (printer);

		if (setjmp(cleanup_env) != 0) {
			if (printer_status & PS_DISABLED)
				disable (printer, disable_reason, 1);
			if (printer_status & PS_REJECTED)
				reject (printer, reject_reason);
			if (req_id && *req_id)
				cancel (req_id);
			done (1);
		}
	}

	sprintf (rfile, "%s-0", file_prefix);
	if (putrequest(rfile, &req) == -1) {
		LP_ERRMSG1 (ERROR, E_LP_PUTREQUEST, PERROR);
		goto Done;
	}
	BEGIN_CRITICAL
		send_message (S_PRINT_REQUEST, rfile);
		if (mrecv(buffer, MSGMAX) != R_PRINT_REQUEST) {
			LP_ERRMSG (ERROR, E_LP_MRECV);
			done (1);
		}
	END_CRITICAL
	(void)getmessage (buffer, R_PRINT_REQUEST, &status, &req_id, &printer_chk);

	switch (status) {

	case MNOFILTER:
		if (try == 1) {
			req.pages = 0;
			goto Again;
		}
		LP_ERRMSG (ERROR, E_ADM_NFILTER);
		goto Done;

	case MOK:
#if	defined(WARN_OF_TOO_MANY_LINES)
		if (!req.pages)
			LP_ERRMSG1 (WARNING, E_ADM_NPAGES, formbuf.np);
#endif
		break;

	case MERRDEST:
		accept (printer); /* someone snuck a reject in! */
		goto Again;

	case MNOMEM:
		LP_ERRMSG (ERROR, E_LP_MNOMEM);
		goto Done;

	case MNODEST:
		LP_ERRMSG1 (ERROR, E_LP_PGONE, printer);
		goto Done;

	case MNOOPEN:	/* not quite, but close */
		LP_ERRMSG (ERROR, E_ADM_ERRDEST);
		goto Done;

	case MDENYDEST:
		if (printer_chk) {
			char			reason[1024],
						*cp	= reason;

			if (printer_chk & PCK_TYPE)
				cp += sprintf(cp, "printer type, ");
			if (printer_chk & PCK_CHARSET)
				cp += sprintf(cp, "character set, ");
			if (printer_chk & PCK_CPI)
				cp += sprintf(cp, "character pitch, ");
			if (printer_chk & PCK_LPI)
				cp += sprintf(cp, "line pitch, ");
			if (printer_chk & PCK_WIDTH)
				cp += sprintf(cp, "page width, ");
			if (printer_chk & PCK_LENGTH)
				cp += sprintf(cp, "page length, ");
			if (printer_chk & PCK_BANNER)
				cp += sprintf(cp, "nobanner, ");
			cp[-2] = 0;
			LP_ERRMSG1 (ERROR, E_LP_PTRCHK, reason);
			goto Done;
		}
		/*fall through*/

	case MUNKNOWN:
	case MNOMEDIA:
	case MDENYMEDIA:
	case MNOMOUNT:
	case MNOSPACE:
	case MNOPERM:
		LP_ERRMSG1 (ERROR, E_LP_BADSTATUS, status);

Done:		if (!(printer_status & PS_DISABLED))
			enable (printer);
		if (printer_status & PS_REJECTED)
			reject (printer, reject_reason);
		done (1);
		/*NOTREACHED*/
	}

	if (printer_status & PS_REJECTED)
		reject (printer, reject_reason);

	/*
	 * Enable printing, to start the interface program going.
	 * Because of our precautions above, our request SHOULD be
	 * the one that prints!
 	 */
	enable (printer);

	/*
	 * Open the FIFO. One problem: This will hang until the
	 * interface program opens the other end!!
	 */
	if (!(fifo_fp = fopen(fifo, "w"))) {
		LP_ERRMSG1 (ERROR, E_ADM_NFIFO, PERROR);
		done (1);
	}

	/*
	 * Loop, dumping the ENTIRE alignment pattern to the FIFO
	 * each time. SIGPIPE probably means the printer faulted.
	 */
	if (setjmp(pipe_env) == 0) {
		/*
		 * Don't send a form feed after the last copy, since
		 * the interface program does that. To implement this,
		 * we send the form feed BEFORE the alignment pattern;
		 * this way we can simply not send it the first time.
		 */
		char *			ff		= 0;
		char *			ff_before	= 0;

		/*
		 * If we'll be inserting page breaks between alignment
		 * patterns, look up the control sequence for this.
		 *
		 * MORE: We currently don't have the smarts to figure out
		 * WHICH printer type the Spooler will pick; we would need
		 * to steal some of its code for that (see pickfilter.c)
		 * The best we do so far is use the alignment pattern's
		 * content type, if known.
		 */
		if (filebreak) {
			if (
				formbuf.conttype
			     && searchlist_with_terminfo(
					formbuf.conttype,
					T  /* having "filebreak" => OK */
				)
			)
				tidbit (formbuf.conttype, "ff", &ff);
			else
				tidbit (*T, "ff", &ff);
		}

		signal (SIGPIPE, sigpipe);
		do {
			register int		n;
			char			buf[BUFSIZ];

			if (ff_before && *ff_before)
				fputs (ff_before, fifo_fp);
			ff_before = ff;

			rewind (align_fp);
			while ((n = fread(buf, 1, BUFSIZ, align_fp)) > 0)
				fwrite (buf, 1, n, fifo_fp);

			fflush (fifo_fp);

		} while (again());
		fclose (align_fp);
		signal (SIGPIPE, SIG_DFL);

	} else {
		cancel (req_id);

#define P(X)	printf (X)

P("We were interrupted while printing the alignment pattern;\n");
P("check the printer. The form is mounted, so you will have to\n");
P("unmount it if you need to print more alignment patterns later.\n");
	}

	/*
	 * Disable the printer, if needed, and close the FIFO.
	 * Use the wait version of the disable, so our request isn't
	 * stopped, and do it before closing the FIFO, so another request
	 * can't start printing if it isn't supposed to.
	 */
	if (printer_status & PS_DISABLED)
		disable (printer, disable_reason, 1);
	fclose (fifo_fp);

	signal (SIGHUP, old_sighup);
	signal (SIGINT, old_sigint);
	signal (SIGQUIT, old_sigquit);
	signal (SIGTERM, old_sigterm);

	return (1);
}

/**
 ** accept() - MAKE PRINTER ACCEPT REQUESTS
 **/

static void		accept (printer)
	char			*printer;
{
	int			rc;

	BEGIN_CRITICAL
		send_message (S_ACCEPT_DEST, printer);
		rc = output(R_ACCEPT_DEST);
	END_CRITICAL

	switch (rc) {
	case MOK:
	case MERRDEST:	/* someone may have snuck in an accept */
		break;

	case MNODEST:	/* make up your mind, Spooler! */
	case MNOPERM:	/* taken care of up front */
	default:
		LP_ERRMSG1 (ERROR, E_LP_BADSTATUS, rc);
		done (1);
	}
	return;
}

/**
 ** reject() - MAKE PRINTER REJECT REQUESTS
 **/

static void		reject (printer, reason)
	char			*printer,
				*reason;
{
	int			rc;

	BEGIN_CRITICAL
		send_message (S_REJECT_DEST, printer, reason);
		rc = output(R_REJECT_DEST);
	END_CRITICAL

	switch (rc) {

	case MOK:
	case MERRDEST:	/* someone may have snuck in a reject */
		break;

	case MNODEST:	/* make up your mind, Spooler! */
	case MNOPERM:	/* taken care of up front */
	default:
		LP_ERRMSG1 (ERROR, E_LP_BADSTATUS, rc);
		done (1);
	}
	return;
}

/**
 ** enable() - ENABLE THE PRINTER
 **/

static void		enable (printer)
	char			*printer;
{
	int			rc;

	BEGIN_CRITICAL
		send_message (S_ENABLE_DEST, printer);
		rc = output(R_ENABLE_DEST);
	END_CRITICAL

	switch (rc) {
	case MOK:
	case MERRDEST:	/* someone may have snuck in an enable */
		break;

	case MNODEST:	/* make up your mind, Spooler! */
	case MNOPERM:	/* taken care of up front */
	default:
		LP_ERRMSG1 (ERROR, E_LP_BADSTATUS, rc);
		done (1);
	}
	return;
}

/**
 ** disable() - DISABLE THE PRINTER
 **/

static void		disable (printer, reason, when)
	char			*printer,
				*reason;
	int			when;
{
	int			rc;

	BEGIN_CRITICAL
		send_message (S_DISABLE_DEST, printer, reason, when);
		rc = output(R_DISABLE_DEST);
	END_CRITICAL

	switch (rc) {
	case MOK:
	case MERRDEST:	/* someone may have snuck in a disable */
		break;

	case MNODEST:	/* make up your mind, Spooler! */
	case MNOPERM:	/* taken care of up front */
	default:
		LP_ERRMSG1 (ERROR, E_LP_BADSTATUS, rc);
		done (1);
	}
	return;
}

/**
 ** cancel() - MAKE PRINTER ACCEPT REQUESTS
 **/

static void		cancel (req_id)
	char			*req_id;
{
	int			rc;

	BEGIN_CRITICAL
		send_message (S_CANCEL_REQUEST, req_id);
		rc = output(R_CANCEL_REQUEST);
	END_CRITICAL

	switch (rc) {
	case MOK:
	case MUNKNOWN:
	case M2LATE:
		break;

	case MNOPERM:
	default:
		LP_ERRMSG1 (ERROR, E_LP_BADSTATUS, rc);
		done (1);
	}
	return;
}

/**
 ** again()
 **/

static int		again ()
{
	char			answer[BUFSIZ];


	for (;;) {

		printf (
		"Press return to print an alignment pattern [q to quit]: "
		);

		if (
			!gets(answer)
		     || STREQU(answer, "q")
		     || STREQU(answer, "n")
		     || STREQU(answer, "no")
		)
			return (0);

		else if (
			!*answer
		     || STREQU(answer, "y")
		     || STREQU(answer, "yes")
		)
			return (1);

		printf ("Sorry?\n");
	}
}

/**
 ** sigpipe()
 ** sigother()
 **/

static void		sigpipe ()
{
	signal (SIGPIPE, SIG_IGN);
	longjmp (pipe_env, 1);
	/*NOTREACHED*/
}

static void		sigother (sig)
	int			sig;
{
	signal (sig, SIG_IGN);
	longjmp (cleanup_env, 1);
	/*NOTREACHED*/
}
