schedule.c revision 0a44ef6d9afbfe052a7e975f55ea0d2954b62a82
/*
* 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"
#include "stdarg.h"
#include "lpsched.h"
#include <syslog.h>
extern int isStartingForms;
typedef struct later {
struct later * next;
int event,
ticks;
union arg {
PSTATUS * printer;
RSTATUS * request;
FSTATUS * form;
} arg;
} LATER;
static LATER LaterHead = { 0 },
TempHead;
static void ev_interf(PSTATUS *);
static void ev_message(PSTATUS *);
static void ev_form_message(FSTATUS *);
static int ev_slowf(RSTATUS *);
static int ev_notify(RSTATUS *);
static EXEC *find_exec_slot(EXEC **);
static char *_event_name(int event)
{
static char *_names[] = {
"", "EV_SLOWF", "EV_INTERF", "EV_NOTIFY", "EV_LATER", "EV_ALARM",
"EV_MESSAGE", "EV_ENABLE", "EV_FORM_MESSAGE", NULL };
if ((event < 0) || (event > EV_FORM_MESSAGE))
return ("BAD_EVENT");
else
return (_names[event]);
}
/*
* schedule() - SCHEDULE BY EVENT
*/
/*VARARGS1*/
void
schedule(int event, ...)
{
va_list ap;
LATER * plprev;
LATER * pl;
LATER * plnext = 0;
register PSTATUS * pps;
register RSTATUS * prs;
register FSTATUS * pfs;
int i;
/*
* If we're in the process of shutting down, don't
* schedule anything.
*/
syslog(LOG_DEBUG, "schedule(%s)", _event_name(event));
if (Shutdown)
return;
va_start (ap, event);
/*
* If we're still in the process of starting up, don't start
* anything! Schedule it for one tick later. While we're starting
* ticks aren't counted, so the events won't be started.
* HOWEVER, with a count of 1, a single EV_ALARM after we're
* finished starting will be enough to clear all things scheduled
* for later.
*/
if (Starting) {
switch (event) {
case EV_INTERF:
case EV_ENABLE:
pps = va_arg(ap, PSTATUS *);
schedule (EV_LATER, 1, event, pps);
goto Return;
case EV_SLOWF:
case EV_NOTIFY:
prs = va_arg(ap, RSTATUS *);
schedule (EV_LATER, 1, event, prs);
goto Return;
case EV_MESSAGE:
pps = va_arg(ap, PSTATUS *);
schedule (EV_LATER, 1, event, pps);
goto Return;
case EV_FORM_MESSAGE:
pfs = va_arg(ap, FSTATUS *);
schedule (EV_LATER, 1, event, pfs);
goto Return;
case EV_LATER:
/*
* This is okay--in fact it may be us!
*/
break;
case EV_ALARM:
/*
* The alarm will go off again, hold off for now.
*/
goto Return;
}
}
/*
* Schedule something:
*/
switch (event) {
case EV_INTERF:
if ((pps = va_arg(ap, PSTATUS *)) != NULL)
ev_interf (pps);
else
for (i = 0; PStatus != NULL && PStatus[i] != NULL; i++)
ev_interf (PStatus[i]);
break;
/*
* The EV_ENABLE event is used to get a printer going again
* after waiting for a fault to be cleared. We used to use
* just the EV_INTERF event, but this wasn't enough: For
* requests that can go on several different printers (e.g.
* queued for class, queued for ``any''), a printer is
* arbitrarily assigned. The EV_INTERF event just checks
* assignments, not possibilities, so a printer with no
* assigned requests but still eligible to handle one or
* more requests would never automatically start up again after
* a fault. The EV_ENABLE event calls "enable()" which eventually
* gets around to invoking the EV_INTERF event. However, it first
* calls "queue_attract()" to get an eligible request assigned
* so that things proceed. This also makes sense from the
* following standpoint: The documented method of getting a
* printer going, while it is waiting for auto-retry, is to
* manually issue the enable command!
*
* Note: "enable()" will destroy the current record of the fault,
* so if the fault is still with us any new alert will not include
* the history of each repeated fault. This is a plus and a minus,
* usually a minus: While a repeated fault may occasionally show
* a varied record, usually the same reason is given each time;
* before switching to EV_ENABLE we typically saw a boring, long
* list of identical reasons.
*/
case EV_ENABLE:
if ((pps = va_arg(ap, PSTATUS *)) != NULL)
enable (pps);
else
for (i = 0; PStatus != NULL && PStatus[i] != NULL; i++)
enable (PStatus[i]);
break;
case EV_SLOWF:
if ((prs = va_arg(ap, RSTATUS *)) != NULL)
(void) ev_slowf (prs);
else
for (prs = Request_List; prs && ev_slowf(prs) != -1;
prs = prs->next);
break;
case EV_NOTIFY:
if ((prs = va_arg(ap, RSTATUS *)) != NULL)
(void) ev_notify (prs);
else
for (prs = Request_List; prs && ev_notify(prs) != -1;
prs = prs->next);
break;
case EV_MESSAGE:
pps = va_arg(ap, PSTATUS *);
ev_message(pps);
break;
case EV_FORM_MESSAGE:
pfs = va_arg(ap, FSTATUS *);
ev_form_message(pfs);
break;
case EV_LATER:
pl = (LATER *)Malloc(sizeof (LATER));
if (!LaterHead.next)
alarm (CLOCK_TICK);
pl->next = LaterHead.next;
LaterHead.next = pl;
pl->ticks = va_arg(ap, int);
pl->event = va_arg(ap, int);
switch (pl->event) {
case EV_MESSAGE:
case EV_INTERF:
case EV_ENABLE:
pl->arg.printer = va_arg(ap, PSTATUS *);
if (pl->arg.printer)
pl->arg.printer->status |= PS_LATER;
break;
case EV_FORM_MESSAGE:
pl->arg.form = va_arg(ap, FSTATUS *);
break;
case EV_SLOWF:
case EV_NOTIFY:
pl->arg.request = va_arg(ap, RSTATUS *);
break;
}
break;
case EV_ALARM:
Sig_Alrm = 0;
/*
* The act of scheduling some of the ``laters'' may
* cause new ``laters'' to be added to the list.
* To ease the handling of the linked list, we first
* run through the list and move all events ready to
* be scheduled to another list. Then we schedule the
* events off the new list. This leaves the main ``later''
* list ready for new events.
*/
TempHead.next = 0;
for (pl = (plprev = &LaterHead)->next; pl; pl = plnext) {
plnext = pl->next;
if (--pl->ticks)
plprev = pl;
else {
plprev->next = plnext;
pl->next = TempHead.next;
TempHead.next = pl;
}
}
for (pl = TempHead.next; pl; pl = plnext) {
plnext = pl->next;
switch (pl->event) {
case EV_MESSAGE:
case EV_INTERF:
case EV_ENABLE:
pl->arg.printer->status &= ~PS_LATER;
schedule (pl->event, pl->arg.printer);
break;
case EV_FORM_MESSAGE:
schedule (pl->event, pl->arg.form);
break;
case EV_SLOWF:
case EV_NOTIFY:
schedule (pl->event, pl->arg.request);
break;
}
Free ((char *)pl);
}
if (LaterHead.next)
alarm (CLOCK_TICK);
break;
}
Return: va_end (ap);
return;
}
/*
* maybe_schedule() - MAYBE SCHEDULE SOMETHING FOR A REQUEST
*/
void
maybe_schedule(RSTATUS *prs)
{
/*
* Use this routine if a request has been changed by some
* means so that it is ready for filtering or printing,
* but a previous filtering or printing process for this
* request MAY NOT have finished yet. If a process is still
* running, then the cleanup of that process will cause
* "schedule()" to be called. Calling "schedule()" regardless
* might make another request slip ahead of this request.
*/
/*
* "schedule()" will refuse if this request is filtering.
* It will also refuse if the request ``was'' filtering
* but the filter was terminated in "validate_request()",
* because we can not have heard from the filter process
* yet. Also, when called with a particular request,
* "schedule()" won't slip another request ahead.
*/
if (NEEDS_FILTERING(prs))
schedule (EV_SLOWF, prs);
else if (!(prs->request->outcome & RS_STOPPED))
schedule (EV_INTERF, prs->printer);
return;
}
static void
ev_message(PSTATUS *pps)
{
register RSTATUS *prs;
char toSelf;
syslog(LOG_DEBUG, "ev_message(%s)",
(pps && pps->request && pps->request->req_file ?
pps->request->req_file : "NULL"));
toSelf = 0;
for (prs = Request_List; prs != NULL; prs = prs->next)
if (prs->printer == pps) {
note("prs (%d) pps (%d)\n", prs, pps);
if (!toSelf) {
toSelf = 1;
exec(EX_FAULT_MESSAGE, pps, prs);
}
}
}
static void
ev_form_message_body(FSTATUS *pfs, RSTATUS *prs, char *toSelf, char ***sysList)
{
syslog(LOG_DEBUG, "ev_form_message_body(%s, %d, 0x%x)",
(pfs && pfs->form && pfs->form->name ? pfs->form->name : "NULL"),
(toSelf ? *toSelf : 0),
sysList);
if (!*toSelf) {
*toSelf = 1;
exec(EX_FORM_MESSAGE, pfs);
}
}
static void
ev_form_message(FSTATUS *pfs)
{
register RSTATUS *prs;
char **sysList;
char toSelf;
syslog(LOG_DEBUG, "ev_form_message(%s)",
(pfs && pfs->form && pfs->form->name ?
pfs->form->name : "NULL"));
toSelf = 0;
sysList = NULL;
for (prs = Request_List; prs != NULL; prs = prs->next)
if (prs->form == pfs)
ev_form_message_body(pfs, prs, &toSelf, &sysList);
if (NewRequest && (NewRequest->form == pfs))
ev_form_message_body(pfs, NewRequest, &toSelf, &sysList);
freelist(sysList);
}
/*
* ev_interf() - CHECK AND EXEC INTERFACE PROGRAM
*/
/*
* Macro to check if the request needs a print wheel or character set (S)
* and the printer (P) has it mounted or can select it. Since the request
* has already been approved for the printer, we don't have to check the
* character set, just the mount. If the printer has selectable character
* sets, there's nothing to check so the request is ready to print.
*/
#define MATCH(PRS, PPS) (\
!(PPS)->printer->daisy || \
!(PRS)->pwheel_name || \
!((PRS)->status & RSS_PWMAND) || \
STREQU((PRS)->pwheel_name, NAME_ANY) || \
((PPS)->pwheel_name && \
STREQU((PPS)->pwheel_name, (PRS)->pwheel_name)))
static void
ev_interf(PSTATUS *pps)
{
register RSTATUS *prs;
syslog(LOG_DEBUG, "ev_interf(%s)",
(pps && pps->request && pps->request->req_file ?
pps->request->req_file : "NULL"));
/*
* If the printer isn't tied up doing something
* else, and isn't disabled, see if there is a request
* waiting to print on it. Note: We don't include
* PS_FAULTED here, because simply having a printer
* fault (without also being disabled) isn't sufficient
* to keep us from trying again. (In fact, we HAVE TO
* try again, to see if the fault has gone away.)
*
* NOTE: If the printer is faulted but the filter controlling
* the printer is waiting for the fault to clear, a
* request will still be attached to the printer, as
* evidenced by "pps->request", so we won't try to
* schedule another request!
*/
if (pps->request || pps->status & (PS_DISABLED|PS_LATER|PS_BUSY))
return;
for (prs = Request_List; prs != NULL; prs = prs->next) {
if ((prs->printer == pps) && (qchk_waiting(prs)) &&
isFormUsableOnPrinter(pps, prs->form) && MATCH(prs, pps)) {
/*
* Just because the printer isn't busy and the
* request is assigned to this printer, don't get the
* idea that the request can't be printing (RS_ACTIVE),
* because another printer may still have the request
* attached but we've not yet heard from the child
* process controlling that printer.
*
* We have the waiting request, we have
* the ready (local) printer. If the exec fails
* because the fork failed, schedule a
* try later and claim we succeeded. The
* later attempt will sort things out,
* e.g. will re-schedule if the fork fails
* again.
*/
pps->request = prs;
if (exec(EX_INTERF, pps) == 0) {
pps->status |= PS_BUSY;
return;
}
pps->request = 0;
if (errno == EAGAIN) {
load_str (&pps->dis_reason, CUZ_NOFORK);
schedule (EV_LATER, WHEN_FORK, EV_ENABLE, pps);
return;
}
}
}
return;
}
/*
* ev_slowf() - CHECK AND EXEC SLOW FILTER
*/
static int
ev_slowf(RSTATUS *prs)
{
register EXEC *ep;
syslog(LOG_DEBUG, "ev_slowf(%s)",
(prs && prs->req_file ? prs->req_file : "NULL"));
/*
* Return -1 if no more can be executed (no more exec slots)
* or if it's unwise to execute any more (fork failed).
*/
if (!(ep = find_exec_slot(Exec_Slow))) {
syslog(LOG_DEBUG, "ev_slowf(%s): no slot",
(prs && prs->req_file ? prs->req_file : "NULL"));
return (-1);
}
if (!(prs->request->outcome & (RS_DONE|RS_HELD|RS_ACTIVE)) &&
NEEDS_FILTERING(prs)) {
(prs->exec = ep)->ex.request = prs;
if (exec(EX_SLOWF, prs) != 0) {
ep->ex.request = 0;
prs->exec = 0;
if (errno == EAGAIN) {
schedule (EV_LATER, WHEN_FORK, EV_SLOWF, prs);
return (-1);
}
}
}
return (0);
}
/*
* ev_notify() - CHECK AND EXEC NOTIFICATION
*/
static int
ev_notify(RSTATUS *prs)
{
register EXEC *ep;
syslog(LOG_DEBUG, "ev_notify(%s)",
(prs && prs->req_file ? prs->req_file : "NULL"));
/*
* Return -1 if no more can be executed (no more exec slots)
* or if it's unwise to execute any more (fork failed, already
* sent one to remote side).
*/
/*
* If the job came from a remote machine, we forward the
* outcome of the request to the network manager for sending
* to the remote side.
*/
if (prs->request->actions & ACT_NOTIFY) {
if (prs->request->outcome & RS_NOTIFY) {
prs->request->actions &= ~ACT_NOTIFY;
return (0); /* but try another request */
}
/*
* If the job didn't come from a remote system,
* we'll try to start a process to send the notification
* to the user. But we only allow so many notifications
* to run at the same time, so we may not be able to
* do it.
*/
} else if (!(ep = find_exec_slot(Exec_Notify)))
return (-1);
else if (prs->request->outcome & RS_NOTIFY &&
!(prs->request->outcome & RS_NOTIFYING)) {
(prs->exec = ep)->ex.request = prs;
if (exec(EX_NOTIFY, prs) != 0) {
ep->ex.request = 0;
prs->exec = 0;
if (errno == EAGAIN) {
schedule (EV_LATER, WHEN_FORK, EV_NOTIFY, prs);
return (-1);
}
}
}
return (0);
}
/*
* find_exec_slot() - FIND AVAILABLE EXEC SLOT
*/
static EXEC *
find_exec_slot(EXEC **exec_table)
{
int i;
for (i = 0; exec_table[i] != NULL; i++)
if (exec_table[i]->pid == 0)
return (exec_table[i]);
syslog(LOG_DEBUG, "find_exec_slot(0x%8.8x): after %d, no slots",
exec_table, i);
return (0);
}