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