]> git.ipfire.org Git - thirdparty/cups.git/blame - scheduler/main.c
To prepare to load cups into easysw/current, perform 4 renames.
[thirdparty/cups.git] / scheduler / main.c
CommitLineData
ef416fc2 1/*
a4d04587 2 * "$Id: main.c 5023 2006-01-29 14:39:44Z mike $"
ef416fc2 3 *
4 * Scheduler main loop for the Common UNIX Printing System (CUPS).
5 *
fa73b229 6 * Copyright 1997-2006 by Easy Software Products, all rights reserved.
ef416fc2 7 *
8 * These coded instructions, statements, and computer programs are the
9 * property of Easy Software Products and are protected by Federal
10 * copyright law. Distribution and use rights are outlined in the file
11 * "LICENSE" which should have been included with this file. If this
12 * file is missing or damaged please contact Easy Software Products
13 * at:
14 *
15 * Attn: CUPS Licensing Information
16 * Easy Software Products
17 * 44141 Airport View Drive, Suite 204
18 * Hollywood, Maryland 20636 USA
19 *
20 * Voice: (301) 373-9600
21 * EMail: cups-info@cups.org
22 * WWW: http://www.cups.org
23 *
24 * Contents:
25 *
26 * main() - Main entry for the CUPS scheduler.
27 * cupsdClosePipe() - Close a pipe as necessary.
28 * cupsdOpenPipe() - Create a pipe which is closed on exec.
29 * cupsdCatchChildSignals() - Catch SIGCHLD signals...
30 * cupsdHoldSignals() - Hold child and termination signals.
31 * cupsdIgnoreChildSignals() - Ignore SIGCHLD signals...
32 * cupsdReleaseSignals() - Release signals for delivery.
33 * cupsdSetString() - Set a string value.
34 * cupsdSetStringf() - Set a formatted string value.
a4d04587 35 * launchd_checkin() - Check-in with launchd and collect the
36 * listening fds.
37 * launchd_reload() - Tell launchd to reload the configuration
38 * file to pick up the new listening directives.
39 * launchd_sync_conf() - Re-write the launchd(8) config file
40 * com.easysw.cupsd.plist based on cupsd.conf.
ef416fc2 41 * parent_handler() - Catch USR1/CHLD signals...
42 * process_children() - Process all dead children...
43 * sigchld_handler() - Handle 'child' signals from old processes.
44 * sighup_handler() - Handle 'hangup' signals to reconfigure the
45 * scheduler.
46 * sigterm_handler() - Handle 'terminate' signals that stop the
47 * scheduler.
48 * select_timeout() - Calculate the select timeout value.
49 * usage() - Show scheduler usage.
50 */
51
52/*
53 * Include necessary headers...
54 */
55
56#define _MAIN_C_
57#include "cupsd.h"
58#include <sys/resource.h>
59#include <syslog.h>
60#include <grp.h>
61
a4d04587 62#ifdef HAVE_LAUNCH_H
63# include <launch.h>
64# include <libgen.h>
65#endif /* HAVE_LAUNCH_H */
66
ef416fc2 67#if defined(HAVE_MALLOC_H) && defined(HAVE_MALLINFO)
68# include <malloc.h>
69#endif /* HAVE_MALLOC_H && HAVE_MALLINFO */
fa73b229 70#ifdef HAVE_NOTIFY_H
71# include <notify.h>
72#endif /* HAVE_NOTIFY_H */
ef416fc2 73
74
75/*
76 * Local functions...
77 */
78
a4d04587 79#ifdef HAVE_LAUNCHD
80static void launchd_checkin(void);
81static void launchd_reload(void);
82static int launchd_sync_conf(void);
83#endif /* HAVE_LAUNCHD */
84
ef416fc2 85static void parent_handler(int sig);
86static void process_children(void);
87static void sigchld_handler(int sig);
88static void sighup_handler(int sig);
89static void sigterm_handler(int sig);
90static long select_timeout(int fds);
a4d04587 91static void usage(int status);
ef416fc2 92
93
94/*
95 * Local globals...
96 */
97
98static int parent_signal = 0; /* Set to signal number from child */
99static int holdcount = 0; /* Number of times "hold" was called */
100#if defined(HAVE_SIGACTION) && !defined(HAVE_SIGSET)
101static sigset_t holdmask; /* Old POSIX signal mask */
102#endif /* HAVE_SIGACTION && !HAVE_SIGSET */
103static int dead_children = 0; /* Dead children? */
104static int stop_scheduler = 0; /* Should the scheduler stop? */
105
106
107/*
108 * 'main()' - Main entry for the CUPS scheduler.
109 */
110
111int /* O - Exit status */
112main(int argc, /* I - Number of command-line arguments */
113 char *argv[]) /* I - Command-line arguments */
114{
115 int i; /* Looping var */
116 char *opt; /* Option character */
117 int fg; /* Run in the foreground */
118 int fds; /* Number of ready descriptors select returns */
119 fd_set *input, /* Input set for select() */
120 *output; /* Output set for select() */
121 cupsd_client_t *con; /* Current client */
122 cupsd_job_t *job; /* Current job */
123 cupsd_listener_t *lis; /* Current listener */
124 time_t current_time, /* Current time */
a4d04587 125 activity, /* Client activity timer */
ef416fc2 126 browse_time, /* Next browse send time */
127 senddoc_time, /* Send-Document time */
128 expire_time; /* Subscription expire time */
129#ifdef HAVE_MALLINFO
130 time_t mallinfo_time; /* Malloc information time */
131#endif /* HAVE_MALLINFO */
132 struct timeval timeout; /* select() timeout */
133 struct rlimit limit; /* Runtime limit */
134#if defined(HAVE_SIGACTION) && !defined(HAVE_SIGSET)
135 struct sigaction action; /* Actions for POSIX signals */
136#endif /* HAVE_SIGACTION && !HAVE_SIGSET */
137#ifdef __sgi
138 cups_file_t *fp; /* Fake lpsched lock file */
139 struct stat statbuf; /* Needed for checking lpsched FIFO */
140#endif /* __sgi */
a4d04587 141#if HAVE_LAUNCHD
142 int launchd, /* Started with the -l option? */
143 launchd_idle_exit;
144 /* Idle exit on select timeout? */
145#endif /* HAVE_LAUNCHD */
ef416fc2 146
147
148 /*
149 * Check for command-line arguments...
150 */
151
a4d04587 152 fg = 0;
153#if HAVE_LAUNCHD
154 launchd = 0;
155#endif /* HAVE_LAUNCHD */
ef416fc2 156
157 for (i = 1; i < argc; i ++)
158 if (argv[i][0] == '-')
159 for (opt = argv[i] + 1; *opt != '\0'; opt ++)
160 switch (*opt)
161 {
162 case 'c' : /* Configuration file */
163 i ++;
164 if (i >= argc)
a4d04587 165 {
166 _cupsLangPuts(stderr, _("cupsd: Expected config filename "
167 "after \"-c\" option!\n"));
168 usage(1);
169 }
ef416fc2 170
171 if (argv[i][0] == '/')
172 {
173 /*
174 * Absolute directory...
175 */
176
177 cupsdSetString(&ConfigurationFile, argv[i]);
178 }
179 else
180 {
181 /*
182 * Relative directory...
183 */
184
09ec0018 185 char *current; /* Current directory */
ef416fc2 186
187
09ec0018 188 /*
189 * Allocate a buffer for the current working directory to
190 * reduce run-time stack usage; this approximates the
191 * behavior of some implementations of getcwd() when they
192 * are passed a NULL pointer.
193 */
194
195 current = malloc(1024);
196 getcwd(current, 1024);
197
ef416fc2 198 cupsdSetStringf(&ConfigurationFile, "%s/%s", current, argv[i]);
09ec0018 199
200 free(current);
ef416fc2 201 }
202 break;
203
204 case 'f' : /* Run in foreground... */
205 fg = 1;
206 break;
207
208 case 'F' : /* Run in foreground, but still disconnect from terminal... */
209 fg = -1;
210 break;
211
a4d04587 212 case 'h' : /* Show usage/help */
213 usage(0);
214 break;
215
216 case 'l' : /* Started by launchd... */
217#ifdef HAVE_LAUNCHD
218 launchd = 1;
219 fg = 1;
220#else
221 _cupsLangPuts(stderr, _("cupsd: launchd(8) support not compiled "
222 "in, running in normal mode.\n"));
223 fg = 0;
224#endif /* HAVE_LAUNCHD */
225 break;
226
ef416fc2 227 default : /* Unknown option */
a4d04587 228 _cupsLangPrintf(stderr, _("cupsd: Unknown option \"%c\" - "
229 "aborting!\n"), *opt);
230 usage(1);
ef416fc2 231 break;
232 }
233 else
234 {
a4d04587 235 _cupsLangPrintf(stderr, _("cupsd: Unknown argument \"%s\" - aborting!\n"),
236 argv[i]);
237 usage(1);
ef416fc2 238 }
239
240 if (!ConfigurationFile)
241 cupsdSetString(&ConfigurationFile, CUPS_SERVERROOT "/cupsd.conf");
242
243 /*
244 * If the user hasn't specified "-f", run in the background...
245 */
246
247 if (!fg)
248 {
249 /*
250 * Setup signal handlers for the parent...
251 */
252
253#ifdef HAVE_SIGSET /* Use System V signals over POSIX to avoid bugs */
254 sigset(SIGUSR1, parent_handler);
255 sigset(SIGCHLD, parent_handler);
256
257 sigset(SIGHUP, SIG_IGN);
258#elif defined(HAVE_SIGACTION)
259 memset(&action, 0, sizeof(action));
260 sigemptyset(&action.sa_mask);
261 sigaddset(&action.sa_mask, SIGUSR1);
262 action.sa_handler = parent_handler;
263 sigaction(SIGUSR1, &action, NULL);
264 sigaction(SIGCHLD, &action, NULL);
265
266 sigemptyset(&action.sa_mask);
267 action.sa_handler = SIG_IGN;
268 sigaction(SIGHUP, &action, NULL);
269#else
270 signal(SIGUSR1, parent_handler);
271 signal(SIGCLD, parent_handler);
272
273 signal(SIGHUP, SIG_IGN);
274#endif /* HAVE_SIGSET */
275
276 if (fork() > 0)
277 {
278 /*
279 * OK, wait for the child to startup and send us SIGUSR1 or to crash
280 * and the OS send us SIGCHLD... We also need to ignore SIGHUP which
281 * might be sent by the init script to restart the scheduler...
282 */
283
284 for (; parent_signal == 0;)
285 sleep(1);
286
287 if (parent_signal == SIGUSR1)
288 return (0);
289
290 if (wait(&i) < 0)
291 {
292 perror("cupsd");
293 return (1);
294 }
295 else if (WIFEXITED(i))
296 {
297 fprintf(stderr, "cupsd: Child exited with status %d!\n", WEXITSTATUS(i));
298 return (2);
299 }
300 else
301 {
302 fprintf(stderr, "cupsd: Child exited on signal %d!\n", WTERMSIG(i));
303 return (3);
304 }
305 }
306 }
307
308 if (fg < 1)
309 {
310 /*
311 * Make sure we aren't tying up any filesystems...
312 */
313
314 chdir("/");
315
316#ifndef DEBUG
317 /*
318 * Disable core dumps...
319 */
320
321 getrlimit(RLIMIT_CORE, &limit);
322 limit.rlim_cur = 0;
323 setrlimit(RLIMIT_CORE, &limit);
324
325 /*
326 * Disconnect from the controlling terminal...
327 */
328
329 setsid();
330
331 /*
332 * Close all open files...
333 */
334
335 getrlimit(RLIMIT_NOFILE, &limit);
336
337 for (i = 0; i < limit.rlim_cur; i ++)
338 close(i);
339#endif /* DEBUG */
340 }
341
342 /*
343 * Set the timezone info...
344 */
345
346 tzset();
347
348#ifdef LC_TIME
349 setlocale(LC_TIME, "");
350#endif /* LC_TIME */
351
352 /*
353 * Set the maximum number of files...
354 */
355
356 getrlimit(RLIMIT_NOFILE, &limit);
357
358 if (limit.rlim_max > CUPS_MAX_FDS)
359 MaxFDs = CUPS_MAX_FDS;
360 else
361 MaxFDs = limit.rlim_max;
362
363 limit.rlim_cur = MaxFDs;
364
365 setrlimit(RLIMIT_NOFILE, &limit);
366
367 /*
368 * Allocate memory for the input and output sets...
369 */
370
371 SetSize = (MaxFDs + 31) / 8 + 4;
372 if (SetSize < sizeof(fd_set))
373 SetSize = sizeof(fd_set);
374
375 InputSet = (fd_set *)calloc(1, SetSize);
376 OutputSet = (fd_set *)calloc(1, SetSize);
377 input = (fd_set *)calloc(1, SetSize);
378 output = (fd_set *)calloc(1, SetSize);
379
380 if (InputSet == NULL || OutputSet == NULL || input == NULL || output == NULL)
381 {
382 syslog(LOG_LPR, "Unable to allocate memory for select() sets - exiting!");
383 return (1);
384 }
385
386 /*
387 * Read configuration...
388 */
389
390 if (!cupsdReadConfiguration())
391 {
392 syslog(LOG_LPR, "Unable to read configuration file \'%s\' - exiting!",
393 ConfigurationFile);
394 return (1);
395 }
396
a4d04587 397#if HAVE_LAUNCHD
398 if (launchd)
399 {
400 /*
401 * If we were started by launchd make sure the cupsd plist file contains the
402 * same listeners as cupsd.conf; If it didn't then reload it before getting
403 * the list of listening file descriptors...
404 */
405
406 if (launchd_sync_conf())
407 {
408 launchd_reload();
409
410 /*
411 * Until rdar://3854821 is fixed we have to exit after the reload...
412 */
413
414 cupsdLogMessage(CUPSD_LOG_DEBUG2, "Exiting on launchd_reload");
415 exit(0);
416 }
417
418 launchd_checkin();
419 }
420#endif /* HAVE_LAUNCHD */
421
422 /*
423 * Startup the server...
424 */
425
426 cupsdStartServer();
427
ef416fc2 428 /*
429 * Catch hangup and child signals and ignore broken pipes...
430 */
431
432#ifdef HAVE_SIGSET /* Use System V signals over POSIX to avoid bugs */
433 if (RunAsUser)
434 sigset(SIGHUP, sigterm_handler);
435 else
436 sigset(SIGHUP, sighup_handler);
437
438 sigset(SIGPIPE, SIG_IGN);
439 sigset(SIGTERM, sigterm_handler);
440#elif defined(HAVE_SIGACTION)
441 memset(&action, 0, sizeof(action));
442
443 sigemptyset(&action.sa_mask);
444 sigaddset(&action.sa_mask, SIGHUP);
445
446 if (RunAsUser)
447 action.sa_handler = sigterm_handler;
448 else
449 action.sa_handler = sighup_handler;
450
451 sigaction(SIGHUP, &action, NULL);
452
453 sigemptyset(&action.sa_mask);
454 action.sa_handler = SIG_IGN;
455 sigaction(SIGPIPE, &action, NULL);
456
457 sigemptyset(&action.sa_mask);
458 sigaddset(&action.sa_mask, SIGTERM);
459 sigaddset(&action.sa_mask, SIGCHLD);
460 action.sa_handler = sigterm_handler;
461 sigaction(SIGTERM, &action, NULL);
462#else
463 if (RunAsUser)
464 signal(SIGHUP, sigterm_handler);
465 else
466 signal(SIGHUP, sighup_handler);
467
468 signal(SIGPIPE, SIG_IGN);
469 signal(SIGTERM, sigterm_handler);
470#endif /* HAVE_SIGSET */
471
472#ifdef __sgi
473 /*
474 * Try to create a fake lpsched lock file if one is not already there.
475 * Some Adobe applications need it under IRIX in order to enable
476 * printing...
477 */
478
479 if ((fp = cupsFileOpen("/var/spool/lp/SCHEDLOCK", "w")) == NULL)
480 {
481 syslog(LOG_LPR, "Unable to create fake lpsched lock file "
482 "\"/var/spool/lp/SCHEDLOCK\"\' - %s!",
483 strerror(errno));
484 }
485 else
486 {
487 fchmod(cupsFileNumber(fp), 0644);
488 fchown(cupsFileNumber(fp), User, Group);
489
490 cupsFileClose(fp);
491 }
492#endif /* __sgi */
493
494 /*
495 * Initialize authentication certificates...
496 */
497
498 cupsdInitCerts();
499
500 /*
501 * If we are running in the background, signal the parent process that
502 * we are up and running...
503 */
504
505 if (!fg)
506 {
507 /*
508 * Send a signal to the parent process, but only if the parent is
509 * not PID 1 (init). This avoids accidentally shutting down the
510 * system on OpenBSD if you CTRL-C the server before it is up...
511 */
512
513 i = getppid(); /* Save parent PID to avoid race condition */
514
515 if (i != 1)
516 kill(i, SIGUSR1);
517 }
518
09ec0018 519 /*
520 * Start power management framework...
521 */
522
523 cupsdStartSystemMonitor();
524
ef416fc2 525 /*
526 * If the administrator has configured the server to run as an unpriviledged
527 * user, change to that user now...
528 */
529
530 if (RunAsUser)
531 {
532 setgid(Group);
533 setgroups(1, &Group);
534 setuid(User);
535 }
536
537 /*
538 * Catch signals...
539 */
540
541 cupsdCatchChildSignals();
542
543 /*
544 * Start any pending print jobs...
545 */
546
547 cupsdCheckJobs();
548
549 /*
550 * Loop forever...
551 */
552
553#ifdef HAVE_MALLINFO
554 mallinfo_time = 0;
555#endif /* HAVE_MALLINFO */
556 browse_time = time(NULL);
557 senddoc_time = time(NULL);
558 expire_time = time(NULL);
559 fds = 1;
560
561 while (!stop_scheduler)
562 {
563#ifdef DEBUG
564 cupsdLogMessage(CUPSD_LOG_DEBUG2,
565 "main: Top of loop, dead_children=%d, NeedReload=%d",
566 dead_children, NeedReload);
567#endif /* DEBUG */
568
569 /*
570 * Check if there are dead children to handle...
571 */
572
573 if (dead_children)
574 process_children();
575
576 /*
577 * Check if we need to load the server configuration file...
578 */
579
580 if (NeedReload)
581 {
582 /*
583 * Close any idle clients...
584 */
585
586 if (NumClients > 0)
587 {
588 for (i = NumClients, con = Clients; i > 0; i --, con ++)
589 if (con->http.state == HTTP_WAITING)
590 {
591 cupsdCloseClient(con);
592 con --;
593 }
594 else
595 con->http.keep_alive = HTTP_KEEPALIVE_OFF;
596
597 cupsdPauseListening();
598 }
599
600 /*
601 * Check for any active jobs...
602 */
603
604 for (job = (cupsd_job_t *)cupsArrayFirst(ActiveJobs);
605 job;
606 job = (cupsd_job_t *)cupsArrayNext(ActiveJobs))
607 if (job->state->values[0].integer == IPP_JOB_PROCESSING)
608 break;
609
610 /*
611 * Restart if all clients are closed and all jobs finished, or
612 * if the reload timeout has elapsed...
613 */
614
615 if ((NumClients == 0 && (!job || NeedReload != RELOAD_ALL)) ||
616 (time(NULL) - ReloadTime) >= ReloadTimeout)
617 {
a4d04587 618 /*
619 * Shutdown the server...
620 */
621
622 cupsdStopServer();
623
624 /*
625 * Read configuration...
626 */
627
ef416fc2 628 if (!cupsdReadConfiguration())
629 {
630 syslog(LOG_LPR, "Unable to read configuration file \'%s\' - exiting!",
631 ConfigurationFile);
632 break;
633 }
a4d04587 634
635#if HAVE_LAUNCHD
636 if (launchd)
637 {
638 if (launchd_sync_conf())
639 {
640 launchd_reload();
641
642 /*
643 * Until rdar://3854821 is fixed we have to exit after the reload...
644 */
645
646 cupsdLogMessage(CUPSD_LOG_DEBUG2, "Exiting on launchd_reload");
647 stop_scheduler = 1;
648 break;
649 }
650
651 launchd_checkin();
652 }
653#endif /* HAVE_LAUNCHD */
654
655 /*
656 * Startup the server...
657 */
658
659 cupsdStartServer();
ef416fc2 660 }
661 }
662
663 /*
664 * Check for available input or ready output. If select() returns
665 * 0 or -1, something bad happened and we should exit immediately.
666 *
667 * Note that we at least have one listening socket open at all
668 * times.
669 */
670
671 memcpy(input, InputSet, SetSize);
672 memcpy(output, OutputSet, SetSize);
673
674 timeout.tv_sec = select_timeout(fds);
675 timeout.tv_usec = 0;
676
a4d04587 677#if HAVE_LAUNCHD
678 /*
679 * If no other work is scheduled and we're being controlled by
680 * launchd(8) then timeout after 'LaunchdTimeout' seconds of
681 * inactivity...
682 */
683
684 if (timeout.tv_sec == 86400 && launchd && LaunchdTimeout &&
685 (!Browsing || !(BrowseLocalProtocols & BROWSE_DNSSD) ||
686 cupsArrayCount(Printers) == 0))
687 {
688 timeout.tv_sec = LaunchdTimeout;
689 launchd_idle_exit = 1;
690 }
691 else
692 launchd_idle_exit = 0;
693#endif /* HAVE_LAUNCHD */
694
923edb68 695 if (timeout.tv_sec < 86400) /* Only use timeout for < 1 day */
696 fds = select(MaxFDs, input, output, NULL, &timeout);
697 else
698 fds = select(MaxFDs, input, output, NULL, NULL);
699
700 if (fds < 0)
ef416fc2 701 {
702 char s[16384], /* String buffer */
703 *sptr; /* Pointer into buffer */
704 int slen; /* Length of string buffer */
705
706
707 /*
708 * Got an error from select!
709 */
710
711 if (errno == EINTR) /* Just interrupted by a signal */
712 continue;
713
714 /*
715 * Log all sorts of debug info to help track down the problem.
716 */
717
718 cupsdLogMessage(CUPSD_LOG_EMERG, "select() failed - %s!",
719 strerror(errno));
720
721 strcpy(s, "InputSet =");
722 slen = 10;
723 sptr = s + 10;
724
725 for (i = 0; i < MaxFDs; i ++)
726 if (FD_ISSET(i, InputSet))
727 {
728 snprintf(sptr, sizeof(s) - slen, " %d", i);
729 slen += strlen(sptr);
730 sptr += strlen(sptr);
731 }
732
733 cupsdLogMessage(CUPSD_LOG_EMERG, s);
734
735 strcpy(s, "OutputSet =");
736 slen = 11;
737 sptr = s + 11;
738
739 for (i = 0; i < MaxFDs; i ++)
740 if (FD_ISSET(i, OutputSet))
741 {
742 snprintf(sptr, sizeof(s) - slen, " %d", i);
743 slen += strlen(sptr);
744 sptr += strlen(sptr);
745 }
746
747 cupsdLogMessage(CUPSD_LOG_EMERG, s);
748
749 for (i = 0, con = Clients; i < NumClients; i ++, con ++)
750 cupsdLogMessage(CUPSD_LOG_EMERG,
751 "Clients[%d] = %d, file = %d, state = %d",
752 i, con->http.fd, con->file, con->http.state);
753
754 for (i = 0, lis = Listeners; i < NumListeners; i ++, lis ++)
755 cupsdLogMessage(CUPSD_LOG_EMERG, "Listeners[%d] = %d", i, lis->fd);
756
757 cupsdLogMessage(CUPSD_LOG_EMERG, "BrowseSocket = %d", BrowseSocket);
758
923edb68 759 cupsdLogMessage(CUPSD_LOG_EMERG, "CGIPipes[0] = %d", CGIPipes[0]);
760
09ec0018 761#ifdef __APPLE__
762 cupsdLogMessage(CUPSD_LOG_EMERG, "SysEventPipes[0] = %d",
763 SysEventPipes[0]);
764#endif /* __APPLE__ */
765
ef416fc2 766 for (job = (cupsd_job_t *)cupsArrayFirst(ActiveJobs);
767 job;
768 job = (cupsd_job_t *)cupsArrayNext(ActiveJobs))
769 cupsdLogMessage(CUPSD_LOG_EMERG, "Jobs[%d] = %d < [%d %d] > [%d %d]",
770 job->id,
771 job->status_buffer ? job->status_buffer->fd : -1,
772 job->print_pipes[0], job->print_pipes[1],
773 job->back_pipes[0], job->back_pipes[1]);
774 break;
775 }
776
777 current_time = time(NULL);
778
a4d04587 779#if HAVE_LAUNCHD
780 /*
781 * If no other work was scheduled and we're being controlled by launchd(8)
782 * then timeout after 'LaunchdTimeout' seconds of inactivity...
783 */
784
785 if (!fds && launchd_idle_exit)
786 {
787 cupsdLogMessage(CUPSD_LOG_INFO,
788 "Printer sharing is off and there are no jobs pending, "
789 "will restart on demand.");
790 stop_scheduler = 1;
791 break;
792 }
793#endif /* HAVE_LAUNCHD */
794
ef416fc2 795 /*
796 * Check for status info from job filters...
797 */
798
799 for (job = (cupsd_job_t *)cupsArrayFirst(ActiveJobs);
800 job;
801 job = (cupsd_job_t *)cupsArrayNext(ActiveJobs))
802 if (job->status_buffer && FD_ISSET(job->status_buffer->fd, input))
803 {
804 /*
805 * Clear the input bit to avoid updating the next job
806 * using the same status pipe file descriptor...
807 */
808
809 FD_CLR(job->status_buffer->fd, input);
810
811 /*
812 * Read any status messages from the filters...
813 */
814
815 cupsdUpdateJob(job);
816 }
817
818 /*
819 * Update CGI messages as needed...
820 */
821
822 if (CGIPipes[0] >= 0 && FD_ISSET(CGIPipes[0], input))
823 cupsdUpdateCGI();
824
09ec0018 825 /*
826 * Handle system management events as needed...
827 */
828
829#ifdef __APPLE__
830 if (SysEventPipes[0] >= 0 && FD_ISSET(SysEventPipes[0], input))
831 cupsdUpdateSystemMonitor();
832#endif /* __APPLE__ */
833
ef416fc2 834 /*
835 * Update notifier messages as needed...
836 */
837
838 if (NotifierPipes[0] >= 0 && FD_ISSET(NotifierPipes[0], input))
839 cupsdUpdateNotifierStatus();
840
841 /*
842 * Expire subscriptions as needed...
843 */
844
845 if (cupsArrayCount(Subscriptions) > 0 && current_time > expire_time)
846 {
847 cupsdExpireSubscriptions(NULL, NULL);
848
849 expire_time = current_time;
850 }
851
852 /*
853 * Update the browse list as needed...
854 */
855
fa73b229 856 if (Browsing && BrowseRemoteProtocols)
ef416fc2 857 {
858 if (BrowseSocket >= 0 && FD_ISSET(BrowseSocket, input))
859 cupsdUpdateCUPSBrowse();
860
861 if (PollPipe >= 0 && FD_ISSET(PollPipe, input))
862 cupsdUpdatePolling();
863
864#ifdef HAVE_LIBSLP
865 if (((BrowseLocalProtocols | BrowseRemoteProtocols) & BROWSE_SLP) &&
866 BrowseSLPRefresh <= current_time)
867 cupsdUpdateSLPBrowse();
868#endif /* HAVE_LIBSLP */
fa73b229 869 }
ef416fc2 870
fa73b229 871 if (Browsing && BrowseLocalProtocols && current_time > browse_time)
872 {
873 cupsdSendBrowseList();
874 browse_time = current_time;
ef416fc2 875 }
876
877 /*
878 * Check for new connections on the "listen" sockets...
879 */
880
881 for (i = NumListeners, lis = Listeners; i > 0; i --, lis ++)
882 if (lis->fd >= 0 && FD_ISSET(lis->fd, input))
883 {
884 FD_CLR(lis->fd, input);
885 cupsdAcceptClient(lis);
886 }
887
888 /*
889 * Check for new data on the client sockets...
890 */
891
892 for (i = NumClients, con = Clients; i > 0; i --, con ++)
893 {
894 /*
895 * Process the input buffer...
896 */
897
898 if (FD_ISSET(con->http.fd, input) || con->http.used)
899 {
900 FD_CLR(con->http.fd, input);
901
902 if (!cupsdReadClient(con))
903 {
904 if (con->pipe_pid)
905 FD_CLR(con->file, input);
906
907 con --;
908 continue;
909 }
910 }
911
912 /*
913 * Write data as needed...
914 */
915
916 if (con->pipe_pid && FD_ISSET(con->file, input))
917 {
918 /*
919 * Keep track of pending input from the file/pipe separately
920 * so that we don't needlessly spin on select() when the web
921 * client is not ready to receive data...
922 */
923
924 FD_CLR(con->file, input);
925 con->file_ready = 1;
926
927#ifdef DEBUG
928 cupsdLogMessage(CUPSD_LOG_DEBUG2, "main: Data ready file %d!",
929 con->file);
930#endif /* DEBUG */
931
932 if (!FD_ISSET(con->http.fd, output))
933 {
934 cupsdLogMessage(CUPSD_LOG_DEBUG2,
935 "main: Removing fd %d from InputSet...", con->file);
923edb68 936 FD_CLR(con->file, input);
ef416fc2 937 FD_CLR(con->file, InputSet);
938 }
939 }
940
941 if (FD_ISSET(con->http.fd, output))
942 {
943 FD_CLR(con->http.fd, output);
944
945 if (!con->pipe_pid || con->file_ready)
946 if (!cupsdWriteClient(con))
947 {
948 con --;
949 continue;
950 }
951 }
952
953 /*
954 * Check the activity and close old clients...
955 */
956
957 activity = current_time - Timeout;
958 if (con->http.activity < activity && !con->pipe_pid)
959 {
960 cupsdLogMessage(CUPSD_LOG_DEBUG,
961 "Closing client %d after %d seconds of inactivity...",
962 con->http.fd, Timeout);
963
964 cupsdCloseClient(con);
965 con --;
966 continue;
967 }
968 }
969
970 /*
971 * Update any pending multi-file documents...
972 */
973
974 if ((current_time - senddoc_time) >= 10)
975 {
976 cupsdCheckJobs();
977 senddoc_time = current_time;
978 }
979
980#ifdef HAVE_MALLINFO
981 /*
982 * Log memory usage every minute...
983 */
984
985 if ((current_time - mallinfo_time) >= 60 && LogLevel >= CUPSD_LOG_DEBUG)
986 {
987 struct mallinfo mem; /* Malloc information */
988
989
990 mem = mallinfo();
991 cupsdLogMessage(CUPSD_LOG_DEBUG,
992 "mallinfo: arena = %d, used = %d, free = %d\n",
993 mem.arena, mem.usmblks + mem.uordblks,
994 mem.fsmblks + mem.fordblks);
995 mallinfo_time = current_time;
996 }
997#endif /* HAVE_MALLINFO */
998
999 /*
1000 * Update the root certificate once every 5 minutes...
1001 */
1002
1003 if ((current_time - RootCertTime) >= RootCertDuration && RootCertDuration &&
1004 !RunUser)
1005 {
1006 /*
1007 * Update the root certificate...
1008 */
1009
1010 cupsdDeleteCert(0);
1011 cupsdAddCert(0, "root");
1012 }
fa73b229 1013
1014 /*
1015 * Handle OS-specific event notification for any events that have
1016 * accumulated. Don't send these more than once a second...
1017 */
1018
1019 if (LastEvent && (time(NULL) - LastEventTime) > 1)
1020 {
1021#ifdef HAVE_NOTIFY_POST
1022 if (LastEvent & CUPSD_EVENT_PRINTER_CHANGED)
1023 {
1024 cupsdLogMessage(CUPSD_LOG_DEBUG,
1025 "notify_post(\"com.apple.printerListChange\")");
1026 notify_post("com.apple.printerListChange");
1027 }
1028
1029 if (LastEvent & CUPSD_EVENT_PRINTER_STATE_CHANGED)
1030 {
1031 cupsdLogMessage(CUPSD_LOG_DEBUG,
1032 "notify_post(\"com.apple.printerHistoryChange\")");
1033 notify_post("com.apple.printerHistoryChange");
1034 }
1035
1036 if (LastEvent & (CUPSD_EVENT_JOB_STATE_CHANGED |
1037 CUPSD_EVENT_JOB_CONFIG_CHANGED |
1038 CUPSD_EVENT_JOB_PROGRESS))
1039 {
1040 cupsdLogMessage(CUPSD_LOG_DEBUG,
1041 "notify_post(\"com.apple.jobChange\")");
1042 notify_post("com.apple.jobChange");
1043 }
1044#endif /* HAVE_NOTIFY_POST */
1045
1046 /*
1047 * Reset the accumulated events and notification time...
1048 */
1049
1050 LastEventTime = time(NULL);
1051 LastEvent = CUPSD_EVENT_NONE;
1052 }
ef416fc2 1053 }
1054
1055 /*
1056 * Log a message based on what happened...
1057 */
1058
1059 if (stop_scheduler)
1060 cupsdLogMessage(CUPSD_LOG_INFO, "Scheduler shutting down normally.");
1061 else
1062 cupsdLogMessage(CUPSD_LOG_ERROR,
1063 "Scheduler shutting down due to program error.");
1064
1065 /*
1066 * Close all network clients and stop all jobs...
1067 */
1068
1069 cupsdStopServer();
1070
1071 cupsdStopAllJobs();
1072
09ec0018 1073 cupsdStopSystemMonitor();
1074
a4d04587 1075#ifdef HAVE_LAUNCHD
1076 /*
1077 * Update the launchd config file as needed...
1078 */
1079
1080 launchd_sync_conf();
1081#endif /* HAVE_LAUNCHD */
1082
ef416fc2 1083#ifdef __sgi
1084 /*
1085 * Remove the fake IRIX lpsched lock file, but only if the existing
1086 * file is not a FIFO which indicates that the real IRIX lpsched is
1087 * running...
1088 */
1089
1090 if (!stat("/var/spool/lp/FIFO", &statbuf))
1091 if (!S_ISFIFO(statbuf.st_mode))
1092 unlink("/var/spool/lp/SCHEDLOCK");
1093#endif /* __sgi */
1094
1095 /*
1096 * Free memory used by FD sets and return...
1097 */
1098
1099 free(InputSet);
1100 free(OutputSet);
1101 free(input);
1102 free(output);
1103
1104 return (!stop_scheduler);
1105}
1106
1107
1108/*
1109 * 'cupsdClosePipe()' - Close a pipe as necessary.
1110 */
1111
1112void
1113cupsdClosePipe(int *fds) /* I - Pipe file descriptors (2) */
1114{
1115 /*
1116 * Close file descriptors as needed...
1117 */
1118
1119 if (fds[0] >= 0)
1120 {
1121 close(fds[0]);
1122 fds[0] = -1;
1123 }
1124
1125 if (fds[1] >= 0)
1126 {
1127 close(fds[1]);
1128 fds[1] = -1;
1129 }
1130}
1131
1132
1133/*
1134 * 'cupsdOpenPipe()' - Create a pipe which is closed on exec.
1135 */
1136
1137int /* O - 0 on success, -1 on error */
1138cupsdOpenPipe(int *fds) /* O - Pipe file descriptors (2) */
1139{
1140 /*
1141 * Create the pipe...
1142 */
1143
1144 if (pipe(fds))
1145 return (-1);
1146
1147 /*
1148 * Set the "close on exec" flag on each end of the pipe...
1149 */
1150
1151 if (fcntl(fds[0], F_SETFD, fcntl(fds[0], F_GETFD) | FD_CLOEXEC))
1152 {
1153 close(fds[0]);
1154 close(fds[1]);
1155 return (-1);
1156 }
1157
1158 if (fcntl(fds[1], F_SETFD, fcntl(fds[1], F_GETFD) | FD_CLOEXEC))
1159 {
1160 close(fds[0]);
1161 close(fds[1]);
1162 return (-1);
1163 }
1164
1165 /*
1166 * Return 0 indicating success...
1167 */
1168
1169 return (0);
1170}
1171
1172
1173/*
1174 * 'cupsdCatchChildSignals()' - Catch SIGCHLD signals...
1175 */
1176
1177void
1178cupsdCatchChildSignals(void)
1179{
1180#if defined(HAVE_SIGACTION) && !defined(HAVE_SIGSET)
1181 struct sigaction action; /* Actions for POSIX signals */
1182#endif /* HAVE_SIGACTION && !HAVE_SIGSET */
1183
1184
1185#ifdef HAVE_SIGSET /* Use System V signals over POSIX to avoid bugs */
1186 sigset(SIGCHLD, sigchld_handler);
1187#elif defined(HAVE_SIGACTION)
1188 memset(&action, 0, sizeof(action));
1189
1190 sigemptyset(&action.sa_mask);
1191 sigaddset(&action.sa_mask, SIGTERM);
1192 sigaddset(&action.sa_mask, SIGCHLD);
1193 action.sa_handler = sigchld_handler;
1194 sigaction(SIGCHLD, &action, NULL);
1195#else
1196 signal(SIGCLD, sigchld_handler); /* No, SIGCLD isn't a typo... */
1197#endif /* HAVE_SIGSET */
1198}
1199
1200
1201/*
1202 * 'cupsdClearString()' - Clear a string.
1203 */
1204
1205void
1206cupsdClearString(char **s) /* O - String value */
1207{
1208 if (s && *s)
1209 {
1210 free(*s);
1211 *s = NULL;
1212 }
1213}
1214
1215
1216/*
1217 * 'cupsdHoldSignals()' - Hold child and termination signals.
1218 */
1219
1220void
1221cupsdHoldSignals(void)
1222{
1223#if defined(HAVE_SIGACTION) && !defined(HAVE_SIGSET)
1224 sigset_t newmask; /* New POSIX signal mask */
1225#endif /* HAVE_SIGACTION && !HAVE_SIGSET */
1226
1227
1228 holdcount ++;
1229 if (holdcount > 1)
1230 return;
1231
1232#ifdef HAVE_SIGSET
1233 sighold(SIGTERM);
1234 sighold(SIGCHLD);
1235#elif defined(HAVE_SIGACTION)
1236 sigemptyset(&newmask);
1237 sigaddset(&newmask, SIGTERM);
1238 sigaddset(&newmask, SIGCHLD);
1239 sigprocmask(SIG_BLOCK, &newmask, &holdmask);
1240#endif /* HAVE_SIGSET */
1241}
1242
1243
1244/*
1245 * 'cupsdIgnoreChildSignals()' - Ignore SIGCHLD signals...
1246 *
1247 * We don't really ignore them, we set the signal handler to SIG_DFL,
1248 * since some OS's rely on signals for the wait4() function to work.
1249 */
1250
1251void
1252cupsdIgnoreChildSignals(void)
1253{
1254#if defined(HAVE_SIGACTION) && !defined(HAVE_SIGSET)
1255 struct sigaction action; /* Actions for POSIX signals */
1256#endif /* HAVE_SIGACTION && !HAVE_SIGSET */
1257
1258
1259#ifdef HAVE_SIGSET /* Use System V signals over POSIX to avoid bugs */
1260 sigset(SIGCHLD, SIG_DFL);
1261#elif defined(HAVE_SIGACTION)
1262 memset(&action, 0, sizeof(action));
1263
1264 sigemptyset(&action.sa_mask);
1265 sigaddset(&action.sa_mask, SIGCHLD);
1266 action.sa_handler = SIG_DFL;
1267 sigaction(SIGCHLD, &action, NULL);
1268#else
1269 signal(SIGCLD, SIG_DFL); /* No, SIGCLD isn't a typo... */
1270#endif /* HAVE_SIGSET */
1271}
1272
1273
1274/*
1275 * 'cupsdReleaseSignals()' - Release signals for delivery.
1276 */
1277
1278void
1279cupsdReleaseSignals(void)
1280{
1281 holdcount --;
1282 if (holdcount > 0)
1283 return;
1284
1285#ifdef HAVE_SIGSET
1286 sigrelse(SIGTERM);
1287 sigrelse(SIGCHLD);
1288#elif defined(HAVE_SIGACTION)
1289 sigprocmask(SIG_SETMASK, &holdmask, NULL);
1290#endif /* HAVE_SIGSET */
1291}
1292
1293
1294/*
1295 * 'cupsdSetString()' - Set a string value.
1296 */
1297
1298void
1299cupsdSetString(char **s, /* O - New string */
1300 const char *v) /* I - String value */
1301{
1302 if (!s || *s == v)
1303 return;
1304
1305 if (*s)
1306 free(*s);
1307
1308 if (v)
1309 *s = strdup(v);
1310 else
1311 *s = NULL;
1312}
1313
1314
1315/*
1316 * 'cupsdSetStringf()' - Set a formatted string value.
1317 */
1318
1319void
1320cupsdSetStringf(char **s, /* O - New string */
1321 const char *f, /* I - Printf-style format string */
1322 ...) /* I - Additional args as needed */
1323{
1324 char v[4096]; /* Formatting string value */
1325 va_list ap; /* Argument pointer */
1326 char *olds; /* Old string */
1327
1328
1329 if (!s)
1330 return;
1331
1332 olds = *s;
1333
1334 if (f)
1335 {
1336 va_start(ap, f);
1337 vsnprintf(v, sizeof(v), f, ap);
1338 va_end(ap);
1339
1340 *s = strdup(v);
1341 }
1342 else
1343 *s = NULL;
1344
1345 if (olds)
1346 free(olds);
1347}
1348
1349
a4d04587 1350#ifdef HAVE_LAUNCHD
1351/*
1352 * 'launchd_checkin()' - Check-in with launchd and collect the listening fds.
1353 */
1354
1355static void
1356launchd_checkin(void)
1357{
1358 int i, /* Looping var */
1359 portnum; /* Port number */
1360 launch_data_t ld_msg, /* Launch data message */
1361 ld_resp, /* Launch data response */
1362 ld_array, /* Launch data array */
1363 ld_sockets, /* Launch data sockets dictionary */
1364 ld_runatload, /* Run-at-load setting */
1365 tmp; /* Launch data */
1366 cupsd_listener_t *lis; /* Listeners array */
1367 http_addr_t addr; /* Address variable */
1368 socklen_t addrlen; /* Length of address */
1369 bool runatload; /* Run-at-load setting value */
1370
1371
1372 cupsdLogMessage(CUPSD_LOG_DEBUG, "launchd_checkin: pid=%d", (int)getpid());
1373
1374 /*
1375 * Check-in with launchd...
1376 */
1377
1378 ld_msg = launch_data_new_string(LAUNCH_KEY_CHECKIN);
1379 if ((ld_resp = launch_msg(ld_msg)) == NULL)
1380 {
1381 cupsdLogMessage(CUPSD_LOG_ERROR,
1382 "launchd_checkin: launch_msg(\"" LAUNCH_KEY_CHECKIN
1383 "\") IPC failure");
1384 exit(EXIT_FAILURE);
1385 }
1386
1387 if (launch_data_get_type(ld_resp) == LAUNCH_DATA_ERRNO)
1388 {
1389 errno = launch_data_get_errno(ld_resp);
1390 cupsdLogMessage(CUPSD_LOG_ERROR, "launchd_checkin: Check-in failed: %s",
1391 strerror(errno));
1392 exit(EXIT_FAILURE);
1393 }
1394
1395 /*
1396 * Get the "run-at-load" setting...
1397 */
1398
1399 if ((ld_runatload = launch_data_dict_lookup(ld_resp,
1400 LAUNCH_JOBKEY_RUNATLOAD)) != NULL &&
1401 launch_data_get_type(ld_runatload) == LAUNCH_DATA_BOOL)
1402 runatload = launch_data_get_bool(ld_runatload);
1403 else
1404 {
1405 errno = launch_data_get_errno(ld_resp);
1406 cupsdLogMessage(CUPSD_LOG_ERROR,
1407 "launchd_checkin: Unable to find Run-at-load setting: %s",
1408 strerror(errno));
1409 exit(EXIT_FAILURE);
1410 }
1411
1412 cupsdLogMessage(CUPSD_LOG_DEBUG, "launchd_checkin: Run-at-load=%s",
1413 runatload ? "true" : "false");
1414
1415 /*
1416 * Get the sockets dictionary...
1417 */
1418
1419 if (!(ld_sockets = launch_data_dict_lookup(ld_resp, LAUNCH_JOBKEY_SOCKETS)))
1420 {
1421 cupsdLogMessage(CUPSD_LOG_ERROR,
1422 "launchd_checkin: No sockets found to answer requests on!");
1423 exit(EXIT_FAILURE);
1424 }
1425
1426 /*
1427 * Get the array of listener sockets...
1428 */
1429
1430 if (!(ld_array = launch_data_dict_lookup(ld_sockets, "Listeners")))
1431 {
1432 cupsdLogMessage(CUPSD_LOG_ERROR,
1433 "launchd_checkin: No sockets found to answer requests on!");
1434 exit(EXIT_FAILURE);
1435 }
1436
1437 /*
1438 * Add listening fd(s) to the Listener array...
1439 */
1440
1441 if (launch_data_get_type(ld_array) == LAUNCH_DATA_ARRAY)
1442 {
1443 /*
1444 * Free the listeners array built from cupsd.conf...
1445 */
1446
1447 if (NumListeners > 0)
1448 free(Listeners);
1449
1450 NumListeners = launch_data_array_get_count(ld_array);
1451 Listeners = calloc(NumListeners, sizeof(cupsd_listener_t));
1452
1453 if (!Listeners)
1454 {
1455 cupsdLogMessage(CUPSD_LOG_ERROR,
1456 "launchd_checkin: Unable to allocate new Listeners - %s.",
1457 strerror(errno));
1458 exit(EXIT_FAILURE);
1459 }
1460
1461 /*
1462 * Note: launchd wants us to access the array in ascending order,
1463 * thus "i" counts up and not down as we normally do elsewhere...
1464 */
1465
1466 for (i = 0, lis = Listeners; i < NumListeners; i ++, lis ++)
1467 {
1468 /*
1469 * Copy the current address and log it...
1470 */
1471
1472 tmp = launch_data_array_get_index(ld_array, i);
1473 lis->fd = launch_data_get_fd(tmp);
1474 addrlen = sizeof(lis->address);
1475
1476 if (getsockname(lis->fd, (struct sockaddr *)&(lis->address), &addrlen))
1477 {
1478 cupsdLogMessage(CUPSD_LOG_ERROR,
1479 "launchd_checkin: Unable to get local address - %s",
1480 strerror(errno));
1481 }
1482
1483# ifdef HAVE_SSL
1484 portnum = 0;
1485
1486# ifdef AF_INET6
1487 if (addr.addr.sa_family == AF_INET6)
1488 portnum = ntohs(addr.ipv6.sin6_port);
1489 else
1490# endif /* AF_INET6 */
1491# ifdef AF_LOCAL
1492 if (addr.addr.sa_family == AF_LOCAL)
1493 {
1494 /*
1495 * Make sure the domain socket is accessible to all...
1496 */
1497
1498 fchmod(lis->fd, 0140777);
1499 }
1500 else
1501# endif /* AF_LOCAL */
1502 if (addr.addr.sa_family == AF_INET)
1503 portnum = ntohs(addr.ipv4.sin_port);
1504
1505 if (portnum == 443)
1506 lis->encryption = HTTP_ENCRYPT_ALWAYS;
1507# endif /* HAVE_SSL */
1508 }
1509 }
1510
1511 /*
1512 * Collect the browse socket (if there is one)...
1513 */
1514
1515 if ((ld_array = launch_data_dict_lookup(ld_sockets, "BrowseSockets")))
1516 {
1517 if (launch_data_get_type(ld_array) == LAUNCH_DATA_ARRAY)
1518 {
1519 tmp = launch_data_array_get_index(ld_array, 0);
1520
1521 if (launch_data_get_type(tmp) == LAUNCH_DATA_FD)
1522 {
1523 if (BrowseSocket != -1)
1524 close(BrowseSocket);
1525
1526 BrowseSocket = launch_data_get_fd(tmp);
1527 }
1528 else
1529 cupsdLogMessage(CUPSD_LOG_WARN,
1530 "launchd_checkin: BrowseSocket not a fd!");
1531 }
1532 else
1533 cupsdLogMessage(CUPSD_LOG_WARN,
1534 "launchd_checkin: BrowseSockets is not an array!");
1535 }
1536 else
1537 cupsdLogMessage(CUPSD_LOG_DEBUG, "launchd_checkin: No BrowseSockets");
1538
1539 launch_data_free(ld_msg);
1540 launch_data_free(ld_resp);
1541}
1542
1543
1544/*
1545 * 'launchd_reload()' - Tell launchd to reload the configuration file to pick
1546 * up the new listening directives.
1547 */
1548
1549static void
1550launchd_reload(void)
1551{
1552 int child_status; /* Exit status of child process */
1553 pid_t child_pid, /* Child PID */
1554 waitpid_status; /* Child process exit status */
1555 char *argv[4]; /* Argument strings */
1556
1557
1558 /*
1559 * The current launchd doesn't support a reload option (rdar://3854821).
1560 * Until this is fixed we need to reload the config file by execing launchctl
1561 * twice (to unload then load). NOTE: This will cause us to exit on SIGTERM
1562 * which will cancel all client & job activity.
1563 *
1564 * After this is fixed we'll be able to tell launchd to reload the file
1565 * and pick up the new listening descriptors without disrupting current
1566 * activity.
1567 */
1568
1569 /*
1570 * Unloading the current configuration will cause launchd to send us a SIGTERM;
1571 * block it for now so we can get our work done...
1572 */
1573
1574 cupsdHoldSignals();
1575
1576 /*
1577 * Set up the unload arguments to launchctl...
1578 */
1579
1580 argv[0] = "/bin/launchctl";
1581 argv[1] = "unload";
1582 argv[2] = LaunchdConf;
1583 argv[3] = NULL;
1584
1585 if (cupsdStartProcess(argv[0], argv, NULL, -1, -1, -1, -1, 1, &child_pid) < 0)
1586 cupsdLogMessage(CUPSD_LOG_ERROR,
1587 "launchd_reload: Unable to execute %s - %s", argv[0],
1588 strerror(errno));
1589 else
1590 {
1591 do
1592 {
1593 waitpid_status = waitpid(child_pid, &child_status, 0);
1594 }
1595 while (waitpid_status == (pid_t)-1 && errno == EINTR);
1596
1597 if (WIFSIGNALED(child_status))
1598 cupsdLogMessage(CUPSD_LOG_DEBUG,
1599 "launchd_reload: %s pid %d crashed on signal %d!",
1600 basename(argv[0]), child_pid, WTERMSIG(child_status));
1601 else
1602 cupsdLogMessage(CUPSD_LOG_DEBUG,
1603 "launchd_reload: %s pid %d stopped with status %d!",
1604 basename(argv[0]), child_pid, WEXITSTATUS(child_status));
1605
1606 /*
1607 * Do it again with the load command...
1608 */
1609
1610 argv[1] = "load";
1611
1612 if (cupsdStartProcess(argv[0], argv, NULL, -1, -1, -1, -1, 1,
1613 &child_pid) < 0)
1614 {
1615 cupsdLogMessage(CUPSD_LOG_ERROR,
1616 "launchd_reload: Unable to fork for %s - %s", argv[0],
1617 strerror(errno));
1618 }
1619 else
1620 {
1621 do
1622 {
1623 waitpid_status = waitpid(child_pid, &child_status, 0);
1624 } while (waitpid_status == (pid_t)-1 && errno == EINTR);
1625
1626 if (WIFSIGNALED(child_status))
1627 cupsdLogMessage(CUPSD_LOG_DEBUG,
1628 "launchd_reload: %s pid %d crashed on signal %d!",
1629 basename(argv[0]), child_pid, WTERMSIG(child_status));
1630 else
1631 cupsdLogMessage(CUPSD_LOG_DEBUG,
1632 "launchd_reload: %s pid %d stopped with status %d",
1633 basename(argv[0]), child_pid,
1634 WEXITSTATUS(child_status));
1635 }
1636 }
1637
1638 /*
1639 * Leave signals blocked since exit() will be called momentarily anyways...
1640 */
1641}
1642
1643
1644/*
1645 * 'launchd_sync_conf()' - Re-write the launchd(8) config file
1646 * org.cups.cupsd.plist based on cupsd.conf.
1647 */
1648
1649static int /* O - 1 if the file was updated */
1650launchd_sync_conf(void)
1651{
1652 int i, /* Looping var */
1653 portnum; /* Port number */
1654 CFMutableDictionaryRef cupsd_dict, /* com.easysw.cupsd.plist dictionary */
1655 sockets, /* Sockets dictionary */
1656 listener; /* Listener dictionary */
1657 CFDataRef resourceData; /* XML representation of the property list */
1658 CFMutableArrayRef array; /* Array */
1659 CFNumberRef socket_mode; /* Domain socket mode bits */
1660 CFStringRef socket_path; /* Domain socket path */
1661 CFTypeRef value; /* CF value */
1662 CFURLRef fileURL; /* File URL */
1663 SInt32 errorCode; /* Error code */
1664 cupsd_listener_t *lis; /* Current listening socket */
1665 struct servent *service; /* Services data base entry */
1666 char temp[1024]; /* Temporary buffer for value */
1667 struct stat cupsd_sb, /* File info for cupsd.conf */
1668 launchd_sb; /* File info for com.easysw.cupsd.plist */
1669
1670
1671 /*
1672 * If the launchd conf file modification time is newer than the cupsd.conf
1673 * time then there's nothing to do...
1674 */
1675
1676 if (!stat(ConfigurationFile, &cupsd_sb) &&
1677 !stat(LaunchdConf, &launchd_sb) &&
1678 launchd_sb.st_mtimespec.tv_sec >= cupsd_sb.st_mtimespec.tv_sec)
1679 {
1680 cupsdLogMessage(CUPSD_LOG_DEBUG,
1681 "launchd_sync_conf: Nothing to do, pid=%d.",
1682 (int)getpid());
1683 return (0);
1684 }
1685
1686 /*
1687 * Time to write a new 'com.easysw.cupsd.plist' file.
1688 * Create the new dictionary and populate it with values...
1689 */
1690
1691 if ((cupsd_dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
1692 &kCFTypeDictionaryKeyCallBacks,
1693 &kCFTypeDictionaryValueCallBacks)) != NULL)
1694 {
1695 CFDictionaryAddValue(cupsd_dict, CFSTR("Label"), CFSTR("org.cups.cupsd"));
1696 CFDictionaryAddValue(cupsd_dict, CFSTR("Enabled"), kCFBooleanTrue);
1697 CFDictionaryAddValue(cupsd_dict, CFSTR("OnDemand"), kCFBooleanTrue);
1698
1699 if ((Browsing && BrowseLocalProtocols && cupsArrayCount(Printers)) ||
1700 cupsArrayCount(ActiveJobs))
1701 CFDictionaryAddValue(cupsd_dict, CFSTR("RunAtLoad"), kCFBooleanTrue);
1702 else
1703 CFDictionaryAddValue(cupsd_dict, CFSTR("RunAtLoad"), kCFBooleanFalse);
1704
1705 CFDictionaryAddValue(cupsd_dict, CFSTR("ServiceIPC"), kCFBooleanTrue);
1706
1707 if ((array = CFArrayCreateMutable(kCFAllocatorDefault, 2,
1708 &kCFTypeArrayCallBacks)) != NULL)
1709 {
1710 CFDictionaryAddValue(cupsd_dict, CFSTR("ProgramArguments"), array);
1711 CFArrayAppendValue(array, CFSTR("/usr/sbin/cupsd"));
1712 CFArrayAppendValue(array, CFSTR("-l"));
1713 CFRelease(array);
1714 }
1715
1716 /*
1717 * Add a sockets dictionary...
1718 */
1719
1720 if ((sockets = (CFMutableDictionaryRef)CFDictionaryCreateMutable(
1721 kCFAllocatorDefault, 0,
1722 &kCFTypeDictionaryKeyCallBacks,
1723 &kCFTypeDictionaryValueCallBacks)) != NULL)
1724 {
1725 CFDictionaryAddValue(cupsd_dict, CFSTR("Sockets"), sockets);
1726
1727 /*
1728 * Add a Listeners array to the sockets dictionary...
1729 */
1730
1731 if ((array = CFArrayCreateMutable(kCFAllocatorDefault, 0,
1732 &kCFTypeArrayCallBacks)) != NULL)
1733 {
1734 CFDictionaryAddValue(sockets, CFSTR("Listeners"), array);
1735
1736 /*
1737 * For each listener add a dictionary to the listeners array...
1738 */
1739
1740 for (i = NumListeners, lis = Listeners; i > 0; i --, lis ++)
1741 {
1742 if ((listener = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
1743 &kCFTypeDictionaryKeyCallBacks,
1744 &kCFTypeDictionaryValueCallBacks)) != NULL)
1745 {
1746 CFArrayAppendValue(array, listener);
1747
1748# ifdef AF_LOCAL
1749 if (lis->address.addr.sa_family == AF_LOCAL)
1750 {
1751 if ((socket_path = CFStringCreateWithCString(kCFAllocatorDefault,
1752 lis->address.un.sun_path,
1753 kCFStringEncodingUTF8)))
1754 {
1755 CFDictionaryAddValue(listener, CFSTR("SockPathName"),
1756 socket_path);
1757 CFRelease(socket_path);
1758 }
1759 portnum = 0140777; /* (S_IFSOCK|S_IRWXU|S_IRWXG|S_IRWXO) or *
1760 * 49663d decimal */
1761 if ((socket_mode = CFNumberCreate(kCFAllocatorDefault,
1762 kCFNumberIntType, &portnum)))
1763 {
1764 CFDictionaryAddValue(listener, CFSTR("SockPathMode"),
1765 socket_mode);
1766 CFRelease(socket_mode);
1767 }
1768 }
1769 else
1770# endif /* AF_LOCAL */
1771 {
1772# ifdef AF_INET6
1773 if (lis->address.addr.sa_family == AF_INET6)
1774 {
1775 CFDictionaryAddValue(listener, CFSTR("SockFamily"),
1776 CFSTR("IPv6"));
1777 portnum = lis->address.ipv6.sin6_port;
1778 }
1779 else
1780# endif /* AF_INET6 */
1781 {
1782 CFDictionaryAddValue(listener, CFSTR("SockFamily"),
1783 CFSTR("IPv4"));
1784 portnum = lis->address.ipv4.sin_port;
1785 }
1786
1787 if ((service = getservbyport(portnum, NULL)))
1788 value = CFStringCreateWithCString(kCFAllocatorDefault,
1789 service->s_name,
1790 kCFStringEncodingUTF8);
1791 else
1792 value = CFNumberCreate(kCFAllocatorDefault,
1793 kCFNumberIntType, &portnum);
1794
1795 if (value)
1796 {
1797 CFDictionaryAddValue(listener, CFSTR("SockServiceName"), value);
1798 CFRelease(value);
1799 }
1800
1801 httpAddrString(&lis->address, temp, sizeof(temp));
1802 if ((value = CFStringCreateWithCString(kCFAllocatorDefault, temp,
1803 kCFStringEncodingUTF8)))
1804 {
1805 CFDictionaryAddValue(listener, CFSTR("SockNodeName"), value);
1806 CFRelease(value);
1807 }
1808 }
1809
1810 CFRelease(listener);
1811 }
1812 }
1813
1814 CFRelease(array);
1815 }
1816
1817 /*
1818 * Add the BrowseSocket to the sockets dictionary...
1819 */
1820
1821 if (Browsing && (BrowseRemoteProtocols & BROWSE_CUPS))
1822 {
1823 if ((array = CFArrayCreateMutable(kCFAllocatorDefault, 0,
1824 &kCFTypeArrayCallBacks)) != NULL)
1825 {
1826 CFDictionaryAddValue(sockets, CFSTR("BrowseSockets"), array);
1827
1828 if ((listener = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
1829 &kCFTypeDictionaryKeyCallBacks,
1830 &kCFTypeDictionaryValueCallBacks)) != NULL)
1831 {
1832 CFArrayAppendValue(array, listener);
1833
1834 CFDictionaryAddValue(listener, CFSTR("SockFamily"), CFSTR("IPv4"));
1835 CFDictionaryAddValue(listener, CFSTR("SockType"), CFSTR("dgram"));
1836
1837 if ((service = getservbyport(BrowsePort, NULL)))
1838 value = CFStringCreateWithCString(kCFAllocatorDefault,
1839 service->s_name,
1840 kCFStringEncodingUTF8);
1841 else
1842 value = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType,
1843 &BrowsePort);
1844
1845 CFDictionaryAddValue(listener, CFSTR("SockServiceName"), value);
1846 CFRelease(value);
1847
1848 CFRelease(listener);
1849 }
1850
1851 CFRelease(array);
1852 }
1853 }
1854
1855 CFRelease(sockets);
1856 }
1857
1858 cupsdLogMessage(CUPSD_LOG_DEBUG,
1859 "launchd_sync_conf: Updating \"%s\", pid=%d\n",
1860 LaunchdConf, (int)getpid());
1861
1862 if ((fileURL = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault,
1863 (const unsigned char *)LaunchdConf,
1864 strlen(LaunchdConf), false)))
1865 {
1866 if ((resourceData = CFPropertyListCreateXMLData(kCFAllocatorDefault,
1867 cupsd_dict)))
1868 {
1869 if (!CFURLWriteDataAndPropertiesToResource(fileURL, resourceData,
1870 NULL, &errorCode))
1871 {
1872 cupsdLogMessage(CUPSD_LOG_WARN,
1873 "launchd_sync_conf: "
1874 "CFURLWriteDataAndPropertiesToResource(\"%s\") "
1875 "failed: %d\n",
1876 LaunchdConf, (int)errorCode);
1877 }
1878
1879 CFRelease(resourceData);
1880 }
1881
1882 CFRelease(fileURL);
1883 }
1884
1885 CFRelease(cupsd_dict);
1886 }
1887
1888 /*
1889 * Let the caller know we updated the file...
1890 */
1891
1892 return (1);
1893}
1894#endif /* HAVE_LAUNCHD */
1895
1896
ef416fc2 1897/*
1898 * 'parent_handler()' - Catch USR1/CHLD signals...
1899 */
1900
1901static void
1902parent_handler(int sig) /* I - Signal */
1903{
1904 /*
1905 * Store the signal we got from the OS and return...
1906 */
1907
1908 parent_signal = sig;
1909}
1910
1911
1912/*
1913 * 'process_children()' - Process all dead children...
1914 */
1915
1916static void
1917process_children(void)
1918{
1919 int status; /* Exit status of child */
1920 int pid; /* Process ID of child */
1921 cupsd_job_t *job; /* Current job */
1922 int i; /* Looping var */
1923
1924
1925 cupsdLogMessage(CUPSD_LOG_DEBUG2, "process_children()");
1926
1927 /*
1928 * Reset the dead_children flag...
1929 */
1930
1931 dead_children = 0;
1932
1933 /*
1934 * Collect the exit status of some children...
1935 */
1936
1937#ifdef HAVE_WAITPID
1938 while ((pid = waitpid(-1, &status, WNOHANG)) > 0)
1939#elif defined(HAVE_WAIT3)
1940 while ((pid = wait3(&status, WNOHANG, NULL)) > 0)
1941#else
1942 if ((pid = wait(&status)) > 0)
1943#endif /* HAVE_WAITPID */
1944 {
1945 cupsdLogMessage(CUPSD_LOG_DEBUG2,
1946 "process_children: pid = %d, status = %d\n", pid, status);
1947
1948 /*
1949 * Ignore SIGTERM errors - that comes when a job is cancelled...
1950 */
1951
1952 if (status == SIGTERM)
1953 status = 0;
1954
1955 if (status)
1956 {
1957 if (WIFEXITED(status))
1958 cupsdLogMessage(CUPSD_LOG_ERROR, "PID %d stopped with status %d!", pid,
1959 WEXITSTATUS(status));
1960 else
1961 cupsdLogMessage(CUPSD_LOG_ERROR, "PID %d crashed on signal %d!", pid,
1962 WTERMSIG(status));
1963
1964 if (LogLevel < CUPSD_LOG_DEBUG)
1965 cupsdLogMessage(CUPSD_LOG_INFO,
1966 "Hint: Try setting the LogLevel to \"debug\" to find out more.");
1967 }
1968 else
1969 cupsdLogMessage(CUPSD_LOG_DEBUG2, "PID %d exited with no errors.", pid);
1970
1971 /*
1972 * Delete certificates for CGI processes...
1973 */
1974
1975 if (pid)
1976 cupsdDeleteCert(pid);
1977
1978 /*
1979 * Lookup the PID in the jobs list...
1980 */
1981
1982 for (job = (cupsd_job_t *)cupsArrayFirst(ActiveJobs);
1983 job;
1984 job = (cupsd_job_t *)cupsArrayNext(ActiveJobs))
1985 if (job->state != NULL &&
1986 job->state->values[0].integer == IPP_JOB_PROCESSING)
1987 {
1988 for (i = 0; job->filters[i]; i ++)
1989 if (job->filters[i] == pid)
1990 break;
1991
1992 if (job->filters[i] || job->backend == pid)
1993 {
1994 /*
1995 * OK, this process has gone away; what's left?
1996 */
1997
1998 if (job->filters[i])
1999 job->filters[i] = -pid;
2000 else
2001 job->backend = -pid;
2002
2003 if (status && job->status >= 0)
2004 {
2005 /*
2006 * An error occurred; save the exit status so we know to stop
2007 * the printer or cancel the job when all of the filters finish...
2008 *
2009 * A negative status indicates that the backend failed and the
2010 * printer needs to be stopped.
2011 */
2012
2013 if (job->filters[i])
2014 job->status = status; /* Filter failed */
2015 else
2016 job->status = -status; /* Backend failed */
2017 }
2018
2019 /*
2020 * If this is not the last file in a job, see if all of the
2021 * filters are done, and if so move to the next file.
2022 */
2023
2024 if (job->current_file < job->num_files)
2025 {
2026 for (i = 0; job->filters[i] < 0; i ++);
2027
2028 if (!job->filters[i])
2029 {
2030 /*
2031 * Process the next file...
2032 */
2033
2034 cupsdFinishJob(job);
2035 }
2036 }
2037 break;
2038 }
2039 }
2040 }
2041}
2042
2043
2044/*
2045 * 'sigchld_handler()' - Handle 'child' signals from old processes.
2046 */
2047
2048static void
2049sigchld_handler(int sig) /* I - Signal number */
2050{
2051 (void)sig;
2052
2053 /*
2054 * Flag that we have dead children...
2055 */
2056
2057 dead_children = 1;
2058
2059 /*
2060 * Reset the signal handler as needed...
2061 */
2062
2063#if !defined(HAVE_SIGSET) && !defined(HAVE_SIGACTION)
2064 signal(SIGCLD, sigchld_handler);
2065#endif /* !HAVE_SIGSET && !HAVE_SIGACTION */
2066}
2067
2068
2069/*
2070 * 'sighup_handler()' - Handle 'hangup' signals to reconfigure the scheduler.
2071 */
2072
2073static void
2074sighup_handler(int sig) /* I - Signal number */
2075{
2076 (void)sig;
2077
2078 NeedReload = RELOAD_ALL;
2079 ReloadTime = time(NULL);
2080
2081#if !defined(HAVE_SIGSET) && !defined(HAVE_SIGACTION)
2082 signal(SIGHUP, sighup_handler);
2083#endif /* !HAVE_SIGSET && !HAVE_SIGACTION */
2084}
2085
2086
2087/*
2088 * 'sigterm_handler()' - Handle 'terminate' signals that stop the scheduler.
2089 */
2090
2091static void
2092sigterm_handler(int sig) /* I - Signal */
2093{
2094 (void)sig; /* remove compiler warnings... */
2095
2096 /*
2097 * Flag that we should stop and return...
2098 */
2099
2100 stop_scheduler = 1;
2101}
2102
2103
2104/*
2105 * 'select_timeout()' - Calculate the select timeout value.
2106 *
2107 */
2108
2109static long /* O - Number of seconds */
2110select_timeout(int fds) /* I - Number of ready descriptors select returned */
2111{
2112 int i; /* Looping var */
2113 long timeout; /* Timeout for select */
2114 time_t now; /* Current time */
2115 cupsd_client_t *con; /* Client information */
2116 cupsd_printer_t *p; /* Printer information */
2117 cupsd_job_t *job; /* Job information */
2118 cupsd_subscription_t *sub; /* Subscription information */
2119 const char *why; /* Debugging aid */
2120
2121
2122 /*
2123 * Check to see if any of the clients have pending data to be
2124 * processed; if so, the timeout should be 0...
2125 */
2126
2127 for (i = NumClients, con = Clients; i > 0; i --, con ++)
2128 if (con->http.used > 0)
2129 return (0);
2130
2131 /*
2132 * If select has been active in the last second (fds != 0) or we have
2133 * many resources in use then don't bother trying to optimize the
2134 * timeout, just make it 1 second.
2135 */
2136
2137 if (fds || NumClients > 50)
2138 return (1);
2139
fa73b229 2140 /*
2141 * If we had a recent event notification, timeout in 1 second...
2142 */
2143
2144 if (LastEvent)
2145 return (1);
2146
ef416fc2 2147 /*
2148 * Otherwise, check all of the possible events that we need to wake for...
2149 */
2150
2151 now = time(NULL);
2152 timeout = now + 86400; /* 86400 == 1 day */
2153 why = "do nothing";
2154
2155 /*
2156 * Check the activity and close old clients...
2157 */
2158
2159 for (i = NumClients, con = Clients; i > 0; i --, con ++)
2160 if ((con->http.activity + Timeout) < timeout)
2161 {
2162 timeout = con->http.activity + Timeout;
2163 why = "timeout a client connection";
2164 }
2165
2166 /*
2167 * Update the browse list as needed...
2168 */
2169
2170 if (Browsing && BrowseLocalProtocols)
2171 {
2172#ifdef HAVE_LIBSLP
2173 if ((BrowseLocalProtocols & BROWSE_SLP) && (BrowseSLPRefresh < timeout))
2174 {
2175 timeout = BrowseSLPRefresh;
2176 why = "update SLP browsing";
2177 }
2178#endif /* HAVE_LIBSLP */
2179
2180 if (BrowseLocalProtocols & BROWSE_CUPS)
2181 {
2182 for (p = (cupsd_printer_t *)cupsArrayFirst(Printers);
2183 p;
2184 p = (cupsd_printer_t *)cupsArrayNext(Printers))
2185 {
2186 if (p->type & CUPS_PRINTER_REMOTE)
2187 {
2188 if ((p->browse_time + BrowseTimeout) < timeout)
2189 {
2190 timeout = p->browse_time + BrowseTimeout;
2191 why = "browse timeout a printer";
2192 }
2193 }
2194 else if (!(p->type & CUPS_PRINTER_IMPLICIT))
2195 {
2196 if (BrowseInterval && (p->browse_time + BrowseInterval) < timeout)
2197 {
2198 timeout = p->browse_time + BrowseInterval;
2199 why = "send browse update";
2200 }
2201 }
2202 }
2203 }
2204 }
2205
2206 /*
2207 * Check for any active jobs...
2208 */
2209
2210 if (timeout > (now + 10) && ActiveJobs)
2211 {
2212 for (job = (cupsd_job_t *)cupsArrayFirst(ActiveJobs);
2213 job;
2214 job = (cupsd_job_t *)cupsArrayNext(ActiveJobs))
2215 if (job->state->values[0].integer <= IPP_JOB_PROCESSING)
2216 {
2217 timeout = now + 10;
2218 why = "process active jobs";
2219 break;
2220 }
2221 }
2222
2223#ifdef HAVE_MALLINFO
2224 /*
2225 * Log memory usage every minute...
2226 */
2227
2228 if (LogLevel >= CUPSD_LOG_DEBUG && (mallinfo_time + 60) < timeout)
2229 {
2230 timeout = mallinfo_time + 60;
2231 why = "display memory usage";
2232 }
2233#endif /* HAVE_MALLINFO */
2234
ef416fc2 2235 /*
2236 * Expire subscriptions as needed...
2237 */
2238
2239 for (sub = (cupsd_subscription_t *)cupsArrayFirst(Subscriptions);
2240 sub;
2241 sub = (cupsd_subscription_t *)cupsArrayNext(Subscriptions))
2242 if (!sub->job && sub->expire < timeout)
2243 {
2244 timeout = sub->expire;
2245 why = "expire subscription";
2246 }
2247
2248 /*
2249 * Adjust from absolute to relative time. If p->browse_time above
2250 * was 0 then we can end up with a negative value here, so check.
2251 * We add 1 second to the timeout since events occur after the
2252 * timeout expires, and limit the timeout to 86400 seconds (1 day)
2253 * to avoid select() timeout limits present on some operating
2254 * systems...
2255 */
2256
2257 timeout = timeout - now + 1;
2258
2259 if (timeout < 1)
2260 timeout = 1;
2261 else if (timeout > 86400)
2262 timeout = 86400;
2263
2264 /*
2265 * Log and return the timeout value...
2266 */
2267
2268 cupsdLogMessage(CUPSD_LOG_DEBUG2, "select_timeout: %ld seconds to %s",
2269 timeout, why);
2270
2271 return (timeout);
2272}
2273
2274
2275/*
2276 * 'usage()' - Show scheduler usage.
2277 */
2278
2279static void
a4d04587 2280usage(int status) /* O - Exit status */
ef416fc2 2281{
a4d04587 2282 _cupsLangPuts(status ? stderr : stdout,
2283 _("Usage: cupsd [-c config-file] [-f] [-F] [-h] [-l]\n"
2284 "\n"
2285 "-c config-file Load alternate configuration file\n"
2286 "-f Run in the foreground\n"
2287 "-F Run in the foreground but detach\n"
2288 "-h Show this usage message\n"
2289 "-l Run cupsd from launchd(8)\n"));
2290 exit(status);
ef416fc2 2291}
2292
2293
2294/*
a4d04587 2295 * End of "$Id: main.c 5023 2006-01-29 14:39:44Z mike $".
ef416fc2 2296 */