xref: /illumos-gate/usr/src/cmd/lp/cmd/lpsched/schedule.c (revision 3db86aab)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 1998 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
28 /*	  All Rights Reserved  	*/
29 
30 
31 #pragma ident	"%Z%%M%	%I%	%E% SMI"	/* SVr4.0 1.15.1.7	*/
32 
33 #include "stdarg.h"
34 #include "lpsched.h"
35 #include <syslog.h>
36 
37 extern int isStartingForms;
38 
39 typedef struct later {
40 	struct later *		next;
41 	int			event,
42 				ticks;
43 	union arg {
44 		PSTATUS *		printer;
45 		RSTATUS *		request;
46 		FSTATUS *		form;
47 	}			arg;
48 }			LATER;
49 
50 static LATER		LaterHead	= { 0 },
51 			TempHead;
52 
53 static void		ev_interf(PSTATUS *);
54 static void		ev_message(PSTATUS *);
55 static void		ev_form_message(FSTATUS *);
56 static int		ev_slowf(RSTATUS *);
57 static int		ev_notify(RSTATUS *);
58 
59 static EXEC		*find_exec_slot(EXEC *, int);
60 
61 static char *_event_name(int event)
62 {
63 	static char *_names[] = {
64 	"", "EV_SLOWF", "EV_INTERF", "EV_NOTIFY", "EV_LATER", "EV_ALARM",
65 	"EV_MESSAGE", "EV_CHECKCHILD"," EV_SYSTEM", "EV_ENABLE",
66 	"EV_POLLBSDSYSTEMS", "EV_FORM_MESSAGE", "EV_STATUS",
67 	NULL };
68 
69 	if ((event < 0) || (event > EV_STATUS))
70 		return ("BAD_EVENT");
71 	else
72 		return (_names[event]);
73 }
74 
75 /*
76  * schedule() - SCHEDULE BY EVENT
77  */
78 
79 /*VARARGS1*/
80 void
81 schedule(int event, ...)
82 {
83 	va_list			ap;
84 
85 	LATER *			plprev;
86 	LATER *			pl;
87 	LATER *			plnext	= 0;
88 
89 	register PSTATUS *	pps;
90 	register RSTATUS *	prs;
91 	register FSTATUS *	pfs;
92 
93 	/*
94 	 * If we're in the process of shutting down, don't
95 	 * schedule anything.
96 	 */
97 	syslog(LOG_DEBUG, "schedule(%s)", _event_name(event));
98 
99 	if (Shutdown)
100 		return;
101 
102 	va_start (ap, event);
103 
104 	/*
105 	 * If we're still in the process of starting up, don't start
106 	 * anything! Schedule it for one tick later. While we're starting
107 	 * ticks aren't counted, so the events won't be started.
108 	 * HOWEVER, with a count of 1, a single EV_ALARM after we're
109 	 * finished starting will be enough to clear all things scheduled
110 	 * for later.
111 	 */
112 	if (Starting) {
113 		switch (event) {
114 
115 		case EV_INTERF:
116 		case EV_ENABLE:
117 			pps = va_arg(ap, PSTATUS *);
118 			schedule (EV_LATER, 1, event, pps);
119 			goto Return;
120 
121 		case EV_SLOWF:
122 		case EV_NOTIFY:
123 			prs = va_arg(ap, RSTATUS *);
124 			schedule (EV_LATER, 1, event, prs);
125 			goto Return;
126 
127 		case EV_MESSAGE:
128 			pps = va_arg(ap, PSTATUS *);
129 			schedule (EV_LATER, 1, event, pps);
130 			goto Return;
131 
132 		case EV_FORM_MESSAGE:
133 			pfs = va_arg(ap, FSTATUS *);
134 			schedule (EV_LATER, 1, event, pfs);
135 			goto Return;
136 
137 		case EV_LATER:
138 			/*
139 			 * This is okay--in fact it may be us!
140 			 */
141 			break;
142 
143 		case EV_ALARM:
144 			/*
145 			 * The alarm will go off again, hold off for now.
146 			 */
147 			goto Return;
148 
149 		}
150 	}
151 
152 	/*
153 	 * Schedule something:
154 	 */
155 	switch (event) {
156 
157 	case EV_INTERF:
158 		if ((pps = va_arg(ap, PSTATUS *)) != NULL)
159 			ev_interf (pps);
160 
161 		else
162 			for (pps = walk_ptable(1); pps; pps = walk_ptable(0))
163 				ev_interf (pps);
164 
165 		break;
166 
167 	/*
168 	 * The EV_ENABLE event is used to get a printer going again
169 	 * after waiting for a fault to be cleared. We used to use
170 	 * just the EV_INTERF event, but this wasn't enough: For
171 	 * requests that can go on several different printers (e.g.
172 	 * queued for class, queued for ``any''), a printer is
173 	 * arbitrarily assigned. The EV_INTERF event just checks
174 	 * assignments, not possibilities, so a printer with no
175 	 * assigned requests but still eligible to handle one or
176 	 * more requests would never automatically start up again after
177 	 * a fault. The EV_ENABLE event calls "enable()" which eventually
178 	 * gets around to invoking the EV_INTERF event. However, it first
179 	 * calls "queue_attract()" to get an eligible request assigned
180 	 * so that things proceed. This also makes sense from the
181 	 * following standpoint: The documented method of getting a
182 	 * printer going, while it is waiting for auto-retry, is to
183 	 * manually issue the enable command!
184 	 *
185 	 * Note: "enable()" will destroy the current record of the fault,
186 	 * so if the fault is still with us any new alert will not include
187 	 * the history of each repeated fault. This is a plus and a minus,
188 	 * usually a minus: While a repeated fault may occasionally show
189 	 * a varied record, usually the same reason is given each time;
190 	 * before switching to EV_ENABLE we typically saw a boring, long
191 	 * list of identical reasons.
192 	 */
193 	case EV_ENABLE:
194 		if ((pps = va_arg(ap, PSTATUS *)) != NULL)
195 			enable (pps);
196 		else
197 			for (pps = walk_ptable(1); pps; pps = walk_ptable(0))
198 				enable (pps);
199 		break;
200 
201 	case EV_SLOWF:
202 		if ((prs = va_arg(ap, RSTATUS *)) != NULL)
203 			(void) ev_slowf (prs);
204 		else
205 			for (prs = Request_List; prs && ev_slowf(prs) != -1;
206 				prs = prs->next);
207 		break;
208 
209 	case EV_NOTIFY:
210 		if ((prs = va_arg(ap, RSTATUS *)) != NULL)
211 			(void) ev_notify (prs);
212 		else
213 			for (prs = Request_List; prs && ev_notify(prs) != -1;
214 				prs = prs->next);
215 		break;
216 
217 	case EV_MESSAGE:
218 		pps = va_arg(ap, PSTATUS *);
219 		ev_message(pps);
220 		break;
221 
222 	case EV_FORM_MESSAGE:
223 		pfs = va_arg(ap, FSTATUS *);
224 		ev_form_message(pfs);
225 		break;
226 
227 	case EV_LATER:
228 		pl = (LATER *)Malloc(sizeof (LATER));
229 
230 		if (!LaterHead.next)
231 			alarm (CLOCK_TICK);
232 
233 		pl->next = LaterHead.next;
234 		LaterHead.next = pl;
235 
236 		pl->ticks = va_arg(ap, int);
237 		pl->event = va_arg(ap, int);
238 		switch (pl->event) {
239 
240 		case EV_MESSAGE:
241 		case EV_INTERF:
242 		case EV_ENABLE:
243 			pl->arg.printer = va_arg(ap, PSTATUS *);
244 			if (pl->arg.printer)
245 				pl->arg.printer->status |= PS_LATER;
246 			break;
247 
248 		case EV_FORM_MESSAGE:
249 			pl->arg.form = va_arg(ap, FSTATUS *);
250 			break;
251 
252 		case EV_SLOWF:
253 		case EV_NOTIFY:
254 			pl->arg.request = va_arg(ap, RSTATUS *);
255 			break;
256 
257 		}
258 		break;
259 
260 	case EV_ALARM:
261 		Sig_Alrm = 0;
262 
263 		/*
264 		 * The act of scheduling some of the ``laters'' may
265 		 * cause new ``laters'' to be added to the list.
266 		 * To ease the handling of the linked list, we first
267 		 * run through the list and move all events ready to
268 		 * be scheduled to another list. Then we schedule the
269 		 * events off the new list. This leaves the main ``later''
270 		 * list ready for new events.
271 		 */
272 		TempHead.next = 0;
273 		for (pl = (plprev = &LaterHead)->next; pl; pl = plnext) {
274 			plnext = pl->next;
275 			if (--pl->ticks)
276 				plprev = pl;
277 			else {
278 				plprev->next = plnext;
279 
280 				pl->next = TempHead.next;
281 				TempHead.next = pl;
282 			}
283 		}
284 
285 		for (pl = TempHead.next; pl; pl = plnext) {
286 			plnext = pl->next;
287 			switch (pl->event) {
288 
289 			case EV_MESSAGE:
290 			case EV_INTERF:
291 			case EV_ENABLE:
292 				pl->arg.printer->status &= ~PS_LATER;
293 				schedule (pl->event, pl->arg.printer);
294 				break;
295 
296 			case EV_FORM_MESSAGE:
297 				schedule (pl->event, pl->arg.form);
298 				break;
299 
300 			case EV_SLOWF:
301 			case EV_NOTIFY:
302 				schedule (pl->event, pl->arg.request);
303 				break;
304 
305 			}
306 			Free ((char *)pl);
307 		}
308 
309 		if (LaterHead.next)
310 			alarm (CLOCK_TICK);
311 		break;
312 
313 	}
314 
315 Return:	va_end (ap);
316 
317 	return;
318 }
319 
320 /*
321  * maybe_schedule() - MAYBE SCHEDULE SOMETHING FOR A REQUEST
322  */
323 
324 void
325 maybe_schedule(RSTATUS *prs)
326 {
327 	/*
328 	 * Use this routine if a request has been changed by some
329 	 * means so that it is ready for filtering or printing,
330 	 * but a previous filtering or printing process for this
331 	 * request MAY NOT have finished yet. If a process is still
332 	 * running, then the cleanup of that process will cause
333 	 * "schedule()" to be called. Calling "schedule()" regardless
334 	 * might make another request slip ahead of this request.
335 	 */
336 
337 	/*
338 	 * "schedule()" will refuse if this request is filtering.
339 	 * It will also refuse if the request ``was'' filtering
340 	 * but the filter was terminated in "validate_request()",
341 	 * because we can not have heard from the filter process
342 	 * yet. Also, when called with a particular request,
343 	 * "schedule()" won't slip another request ahead.
344 	 */
345 	if (NEEDS_FILTERING(prs))
346 		schedule (EV_SLOWF, prs);
347 
348 	else if (!(prs->request->outcome & RS_STOPPED))
349 		schedule (EV_INTERF, prs->printer);
350 
351 	return;
352 }
353 
354 static void
355 ev_message(PSTATUS *pps)
356 {
357 	register RSTATUS	*prs;
358 	char			*systemName;
359 	char			toSelf;
360 
361 	syslog(LOG_DEBUG, "ev_message(%s)",
362 	       (pps && pps->request && pps->request->req_file ?
363 		pps->request->req_file : "NULL"));
364 
365 	toSelf = 0;
366 	BEGIN_WALK_BY_PRINTER_LOOP (prs, pps)
367 		note("prs (%d) pps (%d)\n", prs, pps);
368 		systemName = (prs->secure ? prs->secure->system : NULL);
369 		if ((!systemName) || STREQU(systemName, Local_System))
370 			if (!toSelf) {
371 				toSelf = 1;
372 				exec(EX_FAULT_MESSAGE, pps, prs);
373 			}
374 	END_WALK_LOOP
375 }
376 
377 static void
378 ev_form_message_body(FSTATUS *pfs, RSTATUS *prs, char *toSelf, char ***sysList)
379 {
380 	char *systemName;
381 	systemName = (prs && prs->secure ? prs->secure->system : NULL);
382 
383 	syslog(LOG_DEBUG, "ev_form_message_body(%s, %s, %d, 0x%x)",
384 	      (pfs && pfs->form && pfs->form->name ? pfs->form->name : "NULL"),
385 	      (systemName ? systemName : "NULL"), (toSelf ? *toSelf : 0),
386 		sysList);
387 
388 	if ((!systemName) || STREQU(systemName, Local_System))
389 		if (!*toSelf) {
390 			*toSelf = 1;
391 			exec(EX_FORM_MESSAGE, pfs);
392 		}
393 }
394 
395 static void
396 ev_form_message(FSTATUS *pfs)
397 {
398 	register RSTATUS	*prs;
399 	char **sysList;
400 	char toSelf;
401 
402 	syslog(LOG_DEBUG, "ev_form_message(%s)",
403 	       (pfs && pfs->form && pfs->form->name ?
404 		pfs->form->name : "NULL"));
405 
406 	toSelf = 0;
407 	sysList = NULL;
408 	BEGIN_WALK_BY_FORM_LOOP (prs, pfs)
409 	ev_form_message_body(pfs, prs, &toSelf, &sysList);
410 	END_WALK_LOOP
411 	if (NewRequest && (NewRequest->form == pfs))
412 		ev_form_message_body(pfs, NewRequest, &toSelf, &sysList);
413 
414 	freelist(sysList);
415 }
416 
417 /*
418  * ev_interf() - CHECK AND EXEC INTERFACE PROGRAM
419  */
420 
421 /*
422  * Macro to check if the request needs a print wheel or character set (S)
423  * and the printer (P) has it mounted or can select it. Since the request
424  * has already been approved for the printer, we don't have to check the
425  * character set, just the mount. If the printer has selectable character
426  * sets, there's nothing to check so the request is ready to print.
427  */
428 #define	MATCH(PRS, PPS) (\
429 		!(PPS)->printer->daisy || \
430 		!(PRS)->pwheel_name || \
431 		!((PRS)->status & RSS_PWMAND) || \
432 		STREQU((PRS)->pwheel_name, NAME_ANY) || \
433 		((PPS)->pwheel_name && \
434 		STREQU((PPS)->pwheel_name, (PRS)->pwheel_name)))
435 
436 
437 static void
438 ev_interf(PSTATUS *pps)
439 {
440 	register RSTATUS	*prs;
441 
442 	syslog(LOG_DEBUG, "ev_interf(%s)",
443 	       (pps && pps->request && pps->request->req_file ?
444 		pps->request->req_file : "NULL"));
445 
446 
447 	/*
448 	 * If the printer isn't tied up doing something
449 	 * else, and isn't disabled, see if there is a request
450 	 * waiting to print on it. Note: We don't include
451 	 * PS_FAULTED here, because simply having a printer
452 	 * fault (without also being disabled) isn't sufficient
453 	 * to keep us from trying again. (In fact, we HAVE TO
454 	 * try again, to see if the fault has gone away.)
455 	 *
456 	 * NOTE: If the printer is faulted but the filter controlling
457 	 * the printer is waiting for the fault to clear, a
458 	 * request will still be attached to the printer, as
459 	 * evidenced by "pps->request", so we won't try to
460 	 * schedule another request!
461 	 */
462 	if (pps->request || pps->status & (PS_DISABLED|PS_LATER|PS_BUSY))
463 		return;
464 
465 	BEGIN_WALK_BY_PRINTER_LOOP (prs, pps)
466 	/*
467 	 * Just because the printer isn't busy and the
468 	 * request is assigned to this printer, don't get the
469 	 * idea that the request can't be printing (RS_ACTIVE),
470 	 * because another printer may still have the request
471 	 * attached but we've not yet heard from the child
472 	 * process controlling that printer.
473 	 */
474 	if (qchk_waiting(prs))
475 
476 		if (isFormUsableOnPrinter(pps, prs->form) && MATCH(prs, pps)) {
477 			/*
478 			 * We have the waiting request, we have
479 			 * the ready (local) printer. If the exec fails
480 			 * because the fork failed, schedule a
481 			 * try later and claim we succeeded. The
482 			 * later attempt will sort things out,
483 			 * e.g. will re-schedule if the fork fails
484 			 * again.
485 			 */
486 			pps->request = prs;
487 			if (exec(EX_INTERF, pps) == 0) {
488 				pps->status |= PS_BUSY;
489 				return;
490 			}
491 			pps->request = 0;
492 			if (errno == EAGAIN) {
493 				load_str (&pps->dis_reason, CUZ_NOFORK);
494 				schedule (EV_LATER, WHEN_FORK, EV_ENABLE, pps);
495 				return;
496 			}
497 		}
498 	END_WALK_LOOP
499 
500 	return;
501 }
502 
503 /*
504  * ev_slowf() - CHECK AND EXEC SLOW FILTER
505  */
506 
507 static int
508 ev_slowf(RSTATUS *prs)
509 {
510 	register EXEC		*ep;
511 
512 	syslog(LOG_DEBUG, "ev_slowf(%s)",
513 	       (prs && prs->req_file ? prs->req_file : "NULL"));
514 
515 	/*
516 	 * Return -1 if no more can be executed (no more exec slots)
517 	 * or if it's unwise to execute any more (fork failed).
518 	 */
519 
520 	if (!(ep = find_exec_slot(Exec_Slow, ET_SlowSize)))
521 		return (-1);
522 
523 	if (!(prs->request->outcome & (RS_DONE|RS_HELD|RS_ACTIVE)) &&
524 	    NEEDS_FILTERING(prs)) {
525 		(prs->exec = ep)->ex.request = prs;
526 		if (exec(EX_SLOWF, prs) != 0) {
527 			ep->ex.request = 0;
528 			prs->exec = 0;
529 			if (errno == EAGAIN) {
530 				schedule (EV_LATER, WHEN_FORK, EV_SLOWF, prs);
531 				return (-1);
532 			}
533 		}
534 	}
535 	return (0);
536 }
537 
538 /*
539  * ev_notify() - CHECK AND EXEC NOTIFICATION
540  */
541 
542 static int
543 ev_notify(RSTATUS *prs)
544 {
545 	register EXEC		*ep;
546 
547 	syslog(LOG_DEBUG, "ev_notify(%s)",
548 	       (prs && prs->req_file ? prs->req_file : "NULL"));
549 
550 	/*
551 	 * Return -1 if no more can be executed (no more exec slots)
552 	 * or if it's unwise to execute any more (fork failed, already
553 	 * sent one to remote side).
554 	 */
555 
556 	/*
557 	 * If the job came from a remote machine, we forward the
558 	 * outcome of the request to the network manager for sending
559 	 * to the remote side.
560 	 */
561 	if (prs->request->actions & ACT_NOTIFY) {
562 		if (prs->request->outcome & RS_NOTIFY) {
563 			prs->request->actions &= ~ACT_NOTIFY;
564 			return (0);  /* but try another request */
565 		}
566 	/*
567 	 * If the job didn't come from a remote system,
568 	 * we'll try to start a process to send the notification
569 	 * to the user. But we only allow so many notifications
570 	 * to run at the same time, so we may not be able to
571 	 * do it.
572 	 */
573 	} else if (!(ep = find_exec_slot(Exec_Notify, ET_NotifySize)))
574 		return (-1);
575 
576 	else if (prs->request->outcome & RS_NOTIFY &&
577 		!(prs->request->outcome & RS_NOTIFYING)) {
578 
579 		(prs->exec = ep)->ex.request = prs;
580 		if (exec(EX_NOTIFY, prs) != 0) {
581 			ep->ex.request = 0;
582 			prs->exec = 0;
583 			if (errno == EAGAIN) {
584 				schedule (EV_LATER, WHEN_FORK, EV_NOTIFY, prs);
585 				return (-1);
586 			}
587 		}
588 	}
589 	return (0);
590 }
591 
592 
593 /*
594  * find_exec_slot() - FIND AVAILABLE EXEC SLOT
595  */
596 
597 static EXEC *
598 find_exec_slot(EXEC *exec_table, int size)
599 {
600 	register EXEC *		ep;
601 	register EXEC *		last_ep	= exec_table + size - 1;
602 
603 	for (ep = exec_table; ep <= last_ep; ep++)
604 		if (ep->pid == 0)
605 			return (ep);
606 
607 	return (0);
608 }
609