root/usr/src/cmd/lp/cmd/lpadmin/do_align.c
/*
 * 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 (c) 1984, 1986, 1987, 1988, 1989 AT&T */
/*        All Rights Reserved   */


/*
 * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 * Copyright (c) 2016 by Delphix. All rights reserved.
 */

#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <signal.h>
#include <setjmp.h>
#include <string.h>
#include <stdlib.h>
#include <libintl.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 them.
#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 (
                gettext("Press return to print an alignment pattern [q to quit]: ")
                );

                if (!fgets(answer, sizeof (answer), stdin))
                        return (0);

                answer[strlen(answer) -1] = '\0';

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

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

                printf (gettext("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*/
}