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