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