/* * 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 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ #pragma ident "%Z%%M% %I% %E% SMI" /* EMACS_MODES: !fill, lnumb, !overwrite, !nodelete, !picture */ #include "lpsched.h" #include "ctype.h" #include "sys/stat.h" #include /* * Macro to test if we should notify the user. */ #define SHOULD_NOTIFY(PRS) \ ( \ (PRS)->request->actions & (ACT_MAIL|ACT_WRITE|ACT_NOTIFY)\ || (PRS)->request->alert \ ) static char * geterrbuf ( RSTATUS * ); /** ** dowait() - CLEAN UP CHILD THAT HAS FINISHED, RESCHEDULE ANOTHER TASK **/ void dowait (void) { int exited, killed, canned, i; EXEC *ep; char *errbuf = NULL; register RSTATUS *prs; register PSTATUS *pps; register ALERT *pas; syslog(LOG_DEBUG, "dowait(%d)", DoneChildren); while (DoneChildren > 0) { DoneChildren--; for (i = 0; (ep = Exec_Table[i]) != NULL; i++) if (ep->pid == -99) break; syslog(LOG_DEBUG, "dowait(): 0x%8.8x", ep); if (Exec_Table[i] == NULL) /* nothing to cleanup */ continue; syslog(LOG_DEBUG, "dowait(): cleaning up 0x%8.8x", ep); ep->pid = 0; ep->key = 0; /* avoid subsequent sneaks */ if (ep->md) DROP_MD(ep->md); killed = KILLED(ep->status); exited = EXITED(ep->status); syslog(LOG_DEBUG, "dowait(): type %d, killed %d, exited %d", ep->type, killed, exited); switch (ep->type) { case EX_INTERF: /* * WARNING: It could be that when we get here * * pps->request->printer != pps * * because the request has been assigned to * another printer. */ pps = ep->ex.printer; prs = pps->request; pps->request = 0; pps->status &= ~PS_BUSY; /* * If the interface program exited cleanly * or with just a user error, the printer * is assumed to be working. */ if (0 <= exited && exited < EXEC_EXIT_USER) { pps->status &= ~PS_FAULTED; if (pps->alert->active) cancel_alert (A_PRINTER, pps); } /* * If the interface program was killed with * SIGTERM, it may have been because we canceled * the request, disabled the printer, or for some * other reason stopped the request. * If so, clear the "killed" flag because that's * not the condition of importance here. */ canned = 0; if (killed == SIGTERM) { if (prs->request->outcome & RS_CANCELLED) canned = 1; if ( canned || pps->status & (PS_DISABLED|PS_FAULTED) || prs->request->outcome & RS_STOPPED || Shutdown ) killed = 0; } /* * If there was standard error output from the * interface program, or if the interface program * exited with a (user) exit code, or if it got * a strange signal, the user should be notified. */ errbuf = geterrbuf(prs); if ( errbuf || (0 < exited && exited <= EXEC_EXIT_USER) || killed ) { if (exited != EXIT_RETRY) { prs->request->outcome |= RS_FAILED; } prs->request->outcome |= RS_NOTIFY; notify (prs, errbuf, killed, exited, 0); if (errbuf) Free (errbuf); /* * If the request was canceled, call "notify()" * in case we're to notify the user. */ } else if (canned) { if (SHOULD_NOTIFY(prs)) prs->request->outcome |= RS_NOTIFY; notify (prs, (char *)0, 0, 0, 0); /* * If the request finished successfully, call * "notify()" in case we're to notify the user. */ } else if (exited == 0) { prs->request->outcome |= RS_PRINTED; if (SHOULD_NOTIFY(prs)) prs->request->outcome |= RS_NOTIFY; notify (prs, (char *)0, 0, 0, 0); } /* * If the interface program exits with an * exit code higher than EXEC_EXIT_USER, it's * a special case. */ switch (exited) { case EXEC_EXIT_FAULT: printer_fault (pps, prs, 0, 0); break; case EXEC_EXIT_HUP: printer_fault (pps, prs, HANGUP_FAULT, 0); break; case EXEC_EXIT_INTR: printer_fault (pps, prs, INTERRUPT_FAULT, 0); break; case EXEC_EXIT_PIPE: printer_fault (pps, prs, PIPE_FAULT, 0); break; case EXEC_EXIT_EXIT: note ( "Bad exit from interface program for printer %s: %d\n", pps->printer->name, ep->Errno ); printer_fault (pps, prs, EXIT_FAULT, 0); break; case EXEC_EXIT_NPORT: printer_fault (pps, prs, OPEN_FAULT, ep->Errno); break; case EXEC_EXIT_TMOUT: printer_fault (pps, prs, TIMEOUT_FAULT, 0); break; case EXEC_EXIT_NOPEN: errno = ep->Errno; note ( "Failed to open a print service file (%s).\n", PERROR ); break; case EXEC_EXIT_NEXEC: errno = ep->Errno; note ( "Failed to exec child process (%s).\n", PERROR ); break; case EXEC_EXIT_NOMEM: mallocfail (); break; case EXEC_EXIT_NFORK: errno = ep->Errno; note ( "Failed to fork child process (%s).\n", PERROR ); break; case EXEC_EXIT_NPUSH: printer_fault (pps, prs, PUSH_FAULT, ep->Errno); break; default: if ((exited & EXEC_EXIT_NMASK) == EXEC_EXIT_NDIAL) dial_problem ( pps, prs, exited & ~EXEC_EXIT_NMASK ); else if ( exited < -1 || exited > EXEC_EXIT_USER ) note ( "Bad exit from exec() for printer %s: %d\n", pps->printer->name, exited ); break; } /* * Being in the "dowait()" routine means the * interface (and fast filter!) have stopped. * If we have a fault and we're expected to try * again later, make sure we try again later. */ if ( (pps->status & PS_FAULTED) && !STREQU(pps->printer->fault_rec, NAME_WAIT) && !(pps->status & (PS_LATER|PS_DISABLED)) ) { load_str (&pps->dis_reason, CUZ_STOPPED); schedule (EV_LATER, WHEN_PRINTER, EV_ENABLE, pps); } prs->request->outcome &= ~(RS_PRINTING|RS_STOPPED); /* * If the printer to which this request was * assigned is not able to handle requests now, * push waiting requests off on to another * printer. */ if (prs->printer->status & (PS_FAULTED|PS_DISABLED|PS_LATER)) (void)queue_repel (prs->printer, 0, (qchk_fnc_type)0); /* * If the request is now assigned to a different * printer, call "schedule()" to fire up an * interface. If this request also happens to * be dead, or in need of refiltering, it won't * get scheduled. */ if ( prs->printer != pps ) schedule (EV_INTERF, prs->printer); check_request (prs); /* * Attract the FIRST request that is waiting to * print to this printer, unless the printer isn't * ready to print another request. We do this * even though requests may already be assigned * to this printer, because a request NOT assigned * might be ahead of them in the queue. */ if (!(pps->status & (PS_FAULTED|PS_DISABLED|PS_LATER))) queue_attract (pps, qchk_waiting, 1); break; case EX_SLOWF: prs = ep->ex.request; ep->ex.request = 0; prs->exec = 0; prs->request->outcome &= ~RS_FILTERING; /* * If the slow filter was killed with SIGTERM, * it may have been because we canceled the * request, stopped the filtering, or put a * change hold on the request. If so, clear * the "killed" flag because that's not the * condition of importance. */ canned = 0; if (killed == SIGTERM){ if (prs->request->outcome & RS_CANCELLED) canned = 1; if ( canned || prs->request->outcome & RS_STOPPED || Shutdown ) killed = 0; } /* * If there was standard error output from the * slow filter, or if the interface program exited * with a non-zero exit code, the user should * be notified. */ errbuf = geterrbuf(prs); if (prs->request->outcome & (RS_REFILTER | RS_STOPPED)) { if (errbuf) { Free(errbuf); errbuf = NULL; } } if ( errbuf || 0 < exited && exited <= EXEC_EXIT_USER || killed ) { prs->request->outcome |= RS_FAILED; prs->request->outcome |= RS_NOTIFY; notify (prs, errbuf, killed, exited, 1); if (errbuf) Free (errbuf); /* * If the request was canceled, call "notify()" * in case we're to notify the user. */ } else if (canned) { if (SHOULD_NOTIFY(prs)) prs->request->outcome |= RS_NOTIFY; notify (prs, (char *)0, 0, 0, 1); /* * If the slow filter exited normally, mark * the request as finished slow filtering. */ } else if (exited == 0) { prs->request->outcome |= RS_FILTERED; } else if (exited == -1) { /*EMPTY*/; } else if (exited == EXEC_EXIT_NOPEN) { errno = ep->Errno; note ( "Failed to open a print service file (%s).\n", PERROR ); } else if (exited == EXEC_EXIT_NEXEC) { errno = ep->Errno; note ( "Failed to exec child process (%s).\n", PERROR ); } else if (exited == EXEC_EXIT_NOMEM) { mallocfail (); } prs->request->outcome &= ~RS_STOPPED; schedule (EV_INTERF, prs->printer); if ( prs->request->outcome & RS_REFILTER ) schedule (EV_SLOWF, prs); else schedule (EV_SLOWF, (RSTATUS *)0); check_request (prs); break; case EX_NOTIFY: prs = ep->ex.request; ep->ex.request = 0; prs->exec = 0; prs->request->outcome &= ~RS_NOTIFYING; if (!Shutdown || !killed) prs->request->outcome &= ~RS_NOTIFY; /* * Now that this notification process slot * has opened up, schedule the next notification * (if any). */ schedule (EV_NOTIFY, (RSTATUS *)0); check_request (prs); break; case EX_ALERT: pas = ep->ex.printer->alert; goto CleanUpAlert; case EX_FALERT: pas = ep->ex.form->alert; goto CleanUpAlert; case EX_PALERT: pas = ep->ex.pwheel->alert; /* * CAUTION: It may well be that we've removed * the print wheel by the time we get here. * Only the alert structure (and exec structure) * can be considered okay. */ CleanUpAlert: if (Shutdown) break; if (ep->flags & EXF_RESTART) { ep->flags &= ~(EXF_RESTART); if (exec(ep->type, ep->ex.form) == 0) { pas->active = 1; break; } } (void)Unlink (pas->msgfile); break; } } return; } /** ** geterrbuf() - READ NON-BLANK STANDARD ERROR OUTPUT **/ static char * geterrbuf(RSTATUS *prs) { register char *cp; int fd, n; char *buf = 0, *file; struct stat statbuf; if (!prs) return(NULL); file = makereqerr(prs); if ( Stat(file, &statbuf) == 0 && statbuf.st_size && (fd = Open(file, O_RDONLY)) != -1 ) { /* * Don't die if we can't allocate space for this * file--the file may be huge! */ lp_alloc_fail_handler = 0; if ((buf = Malloc(statbuf.st_size + 1))) if ((n = Read(fd, buf, statbuf.st_size)) > 0) { buf[n] = 0; /* * NOTE: Ignore error output with no * printable text. This hides problems we * have with some shell scripts that * occasionally cause spurious newlines * when stopped via SIGTERM. Without this * check for non-blank output, stopping * a request sometimes causes a request * failure. */ for (cp = buf; *cp && isspace(*cp); cp++) ; if (!*cp) { Free (buf); buf = 0; } } else { Free (buf); buf = 0; } lp_alloc_fail_handler = mallocfail; Close(fd); } if (file) Free (file); return (buf); } /** ** check_request() - CLEAN UP AFTER REQUEST **/ void check_request(RSTATUS *prs) { /* * If the request is done, decrement the count of requests * needing the form or print wheel. Update the disk copy of * the request. If we're finished with the request, get rid of it. */ if (prs->request->outcome & RS_DONE) { unqueue_form (prs); unqueue_pwheel (prs); putrequest (prs->req_file, prs->request); if (!(prs->request->outcome & (RS_ACTIVE | RS_NOTIFY))) { rmfiles (prs, 1); free_rstatus (prs); } } return; } /** ** check_children() **/ void check_children(void) { register int i; for (i = 0; Exec_Table[i] != NULL; i++) if (Exec_Table[i]->pid > 0) break; if (Exec_Table[i] == NULL) Shutdown = 2; }