]> git.ipfire.org Git - thirdparty/cups.git/blame - scheduler/main.c
Changelog.
[thirdparty/cups.git] / scheduler / main.c
CommitLineData
ef416fc2 1/*
f2d18633 2 * "$Id$"
ef416fc2 3 *
6d2f911b 4 * Main loop for the CUPS scheduler.
ef416fc2 5 *
3e7fe0ca 6 * Copyright 2007-2012 by Apple Inc.
f7deaa1a 7 * Copyright 1997-2007 by Easy Software Products, all rights reserved.
ef416fc2 8 *
9 * These coded instructions, statements, and computer programs are the
bc44d920 10 * property of Apple Inc. and are protected by Federal copyright
11 * law. Distribution and use rights are outlined in the file "LICENSE.txt"
ef416fc2 12 * "LICENSE" which should have been included with this file. If this
bc44d920 13 * file is missing or damaged, see the license at "http://www.cups.org/".
ef416fc2 14 *
15 * Contents:
16 *
10d09e33
MS
17 * main() - Main entry for the CUPS scheduler.
18 * cupsdAddString() - Copy and add a string to an array.
19 * cupsdCheckProcess() - Tell the main loop to check for dead children.
20 * cupsdClearString() - Clear a string.
10d09e33
MS
21 * cupsdFreeStrings() - Free an array of strings.
22 * cupsdHoldSignals() - Hold child and termination signals.
10d09e33
MS
23 * cupsdReleaseSignals() - Release signals for delivery.
24 * cupsdSetString() - Set a string value.
25 * cupsdSetStringf() - Set a formatted string value.
10d09e33
MS
26 * launchd_checkin() - Check-in with launchd and collect the listening
27 * fds.
28 * launchd_checkout() - Update the launchd KeepAlive file as needed.
29 * parent_handler() - Catch USR1/CHLD signals...
30 * process_children() - Process all dead children...
31 * select_timeout() - Calculate the select timeout value.
32 * sigchld_handler() - Handle 'child' signals from old processes.
33 * sighup_handler() - Handle 'hangup' signals to reconfigure the
34 * scheduler.
35 * sigterm_handler() - Handle 'terminate' signals that stop the scheduler.
36 * usage() - Show scheduler usage.
ef416fc2 37 */
38
39/*
40 * Include necessary headers...
41 */
42
43#define _MAIN_C_
44#include "cupsd.h"
45#include <sys/resource.h>
46#include <syslog.h>
47#include <grp.h>
48
a4d04587 49#ifdef HAVE_LAUNCH_H
50# include <launch.h>
51# include <libgen.h>
ba55dc12 52# define CUPS_KEEPALIVE CUPS_CACHEDIR "/org.cups.cupsd"
f7deaa1a 53 /* Name of the launchd KeepAlive file */
54# ifndef LAUNCH_JOBKEY_KEEPALIVE
55# define LAUNCH_JOBKEY_KEEPALIVE "KeepAlive"
56# endif /* !LAUNCH_JOBKEY_KEEPALIVE */
57# ifndef LAUNCH_JOBKEY_PATHSTATE
58# define LAUNCH_JOBKEY_PATHSTATE "PathState"
59# endif /* !LAUNCH_JOBKEY_PATHSTATE */
60# ifndef LAUNCH_JOBKEY_SERVICEIPC
61# define LAUNCH_JOBKEY_SERVICEIPC "ServiceIPC"
62# endif /* !LAUNCH_JOBKEY_SERVICEIPC */
a4d04587 63#endif /* HAVE_LAUNCH_H */
64
ef416fc2 65#if defined(HAVE_MALLOC_H) && defined(HAVE_MALLINFO)
66# include <malloc.h>
67#endif /* HAVE_MALLOC_H && HAVE_MALLINFO */
3dd9c340 68
fa73b229 69#ifdef HAVE_NOTIFY_H
70# include <notify.h>
71#endif /* HAVE_NOTIFY_H */
ef416fc2 72
3dd9c340
MS
73#ifdef HAVE_SYS_PARAM_H
74# include <sys/param.h>
75#endif /* HAVE_SYS_PARAM_H */
76
ef416fc2 77
78/*
79 * Local functions...
80 */
81
a4d04587 82#ifdef HAVE_LAUNCHD
411affcf 83static void launchd_checkin(void);
f7deaa1a 84static void launchd_checkout(void);
a4d04587 85#endif /* HAVE_LAUNCHD */
411affcf 86static void parent_handler(int sig);
87static void process_children(void);
88static void sigchld_handler(int sig);
89static void sighup_handler(int sig);
90static void sigterm_handler(int sig);
91static long select_timeout(int fds);
85dda01c 92static void usage(int status) __attribute__((noreturn));
ef416fc2 93
94
95/*
96 * Local globals...
97 */
98
411affcf 99static int parent_signal = 0;
100 /* Set to signal number from child */
101static int holdcount = 0; /* Number of times "hold" was called */
ef416fc2 102#if defined(HAVE_SIGACTION) && !defined(HAVE_SIGSET)
411affcf 103static sigset_t holdmask; /* Old POSIX signal mask */
ef416fc2 104#endif /* HAVE_SIGACTION && !HAVE_SIGSET */
411affcf 105static int dead_children = 0;
106 /* Dead children? */
107static int stop_scheduler = 0;
108 /* Should the scheduler stop? */
109
ef416fc2 110
111/*
112 * 'main()' - Main entry for the CUPS scheduler.
113 */
114
115int /* O - Exit status */
bd7854cb 116main(int argc, /* I - Number of command-line args */
ef416fc2 117 char *argv[]) /* I - Command-line arguments */
118{
119 int i; /* Looping var */
120 char *opt; /* Option character */
121 int fg; /* Run in the foreground */
bd7854cb 122 int fds; /* Number of ready descriptors */
ef416fc2 123 cupsd_client_t *con; /* Current client */
124 cupsd_job_t *job; /* Current job */
125 cupsd_listener_t *lis; /* Current listener */
126 time_t current_time, /* Current time */
a4d04587 127 activity, /* Client activity timer */
ef416fc2 128 senddoc_time, /* Send-Document time */
411affcf 129 expire_time, /* Subscription expire time */
50fe7201 130 report_time, /* Malloc/client/job report time */
5a662dc0 131 event_time; /* Last event notification time */
f7deaa1a 132 long timeout; /* Timeout for cupsdDoSelect() */
ef416fc2 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 */
68b10830
MS
137 int run_as_child = 0;
138 /* Needed for background fork/exec */
a41f09e2 139#ifdef __APPLE__
68b10830 140 int use_sysman = !getuid();
d7871c8c 141 /* Use system management functions? */
a4924f6c
MS
142#else
143 time_t netif_time = 0; /* Time since last network update */
a41f09e2 144#endif /* __APPLE__ */
a4d04587 145#if HAVE_LAUNCHD
4400e98d 146 int launchd_idle_exit;
a4d04587 147 /* Idle exit on select timeout? */
148#endif /* HAVE_LAUNCHD */
ef416fc2 149
150
db1f069b
MS
151#ifdef HAVE_GETEUID
152 /*
153 * Check for setuid invocation, which we do not support!
154 */
155
156 if (getuid() != geteuid())
157 {
4d301e69 158 fputs("cupsd: Cannot run as a setuid program\n", stderr);
db1f069b
MS
159 return (1);
160 }
161#endif /* HAVE_GETEUID */
162
ef416fc2 163 /*
164 * Check for command-line arguments...
165 */
166
4400e98d 167 fg = 0;
ef416fc2 168
09a101d6 169#ifdef HAVE_LAUNCHD
170 if (getenv("CUPSD_LAUNCHD"))
171 {
172 Launchd = 1;
173 fg = 1;
174 }
175#endif /* HAVE_LAUNCHD */
176
ef416fc2 177 for (i = 1; i < argc; i ++)
178 if (argv[i][0] == '-')
179 for (opt = argv[i] + 1; *opt != '\0'; opt ++)
180 switch (*opt)
181 {
a41f09e2
MS
182 case 'C' : /* Run as child with config file */
183 run_as_child = 1;
184 fg = -1;
a41f09e2 185
ef416fc2 186 case 'c' : /* Configuration file */
187 i ++;
188 if (i >= argc)
a4d04587 189 {
190 _cupsLangPuts(stderr, _("cupsd: Expected config filename "
0837b7e8 191 "after \"-c\" option."));
a4d04587 192 usage(1);
193 }
ef416fc2 194
195 if (argv[i][0] == '/')
196 {
197 /*
198 * Absolute directory...
199 */
200
201 cupsdSetString(&ConfigurationFile, argv[i]);
202 }
203 else
204 {
205 /*
206 * Relative directory...
207 */
208
09ec0018 209 char *current; /* Current directory */
ef416fc2 210
09ec0018 211 /*
212 * Allocate a buffer for the current working directory to
213 * reduce run-time stack usage; this approximates the
214 * behavior of some implementations of getcwd() when they
215 * are passed a NULL pointer.
216 */
217
91c84a35
MS
218 if ((current = malloc(1024)) == NULL)
219 {
220 _cupsLangPuts(stderr,
0837b7e8 221 _("cupsd: Unable to get current directory."));
91c84a35
MS
222 return (1);
223 }
224
225 if (!getcwd(current, 1024))
226 {
227 _cupsLangPuts(stderr,
0837b7e8 228 _("cupsd: Unable to get current directory."));
91c84a35
MS
229 free(current);
230 return (1);
231 }
09ec0018 232
ef416fc2 233 cupsdSetStringf(&ConfigurationFile, "%s/%s", current, argv[i]);
09ec0018 234 free(current);
ef416fc2 235 }
236 break;
237
238 case 'f' : /* Run in foreground... */
239 fg = 1;
240 break;
241
bd7854cb 242 case 'F' : /* Run in foreground, but disconnect from terminal... */
ef416fc2 243 fg = -1;
244 break;
245
a4d04587 246 case 'h' : /* Show usage/help */
247 usage(0);
248 break;
249
250 case 'l' : /* Started by launchd... */
251#ifdef HAVE_LAUNCHD
4400e98d 252 Launchd = 1;
a4d04587 253 fg = 1;
254#else
255 _cupsLangPuts(stderr, _("cupsd: launchd(8) support not compiled "
0837b7e8 256 "in, running in normal mode."));
a4d04587 257 fg = 0;
258#endif /* HAVE_LAUNCHD */
259 break;
260
a74454a7 261 case 'p' : /* Stop immediately for profiling */
5a662dc0
MS
262 fputs("cupsd: -p (startup profiling) is for internal testing "
263 "use only!\n", stderr);
a74454a7 264 stop_scheduler = 1;
265 fg = 1;
266 break;
267
1340db2d 268 case 'P' : /* Disable security profiles */
5a662dc0
MS
269 fputs("cupsd: -P (disable security profiles) is for internal "
270 "testing use only!\n", stderr);
1340db2d
MS
271 UseProfiles = 0;
272 break;
273
c41769ff
MS
274 case 's' : /* Set cups-files.conf location */
275 i ++;
276 if (i >= argc)
277 {
278 _cupsLangPuts(stderr, _("cupsd: Expected cups-files.conf "
279 "filename after \"-s\" option."));
280 usage(1);
281 }
282
283 if (argv[i][0] != '/')
284 {
285 /*
286 * Relative filename not allowed...
287 */
288
289 _cupsLangPuts(stderr, _("cupsd: Relative cups-files.conf "
290 "filename not allowed."));
291 usage(1);
292 }
293
294 cupsdSetString(&CupsFilesFile, argv[i]);
295 break;
296
68b10830
MS
297#ifdef __APPLE__
298 case 'S' : /* Disable system management functions */
5a662dc0
MS
299 fputs("cupsd: -S (disable system management) for internal "
300 "testing use only!\n", stderr);
68b10830
MS
301 use_sysman = 0;
302 break;
303#endif /* __APPLE__ */
304
2e4ff8af
MS
305 case 't' : /* Test the cupsd.conf file... */
306 TestConfigFile = 1;
307 fg = 1;
308 break;
309
ef416fc2 310 default : /* Unknown option */
a4d04587 311 _cupsLangPrintf(stderr, _("cupsd: Unknown option \"%c\" - "
0837b7e8 312 "aborting."), *opt);
a4d04587 313 usage(1);
ef416fc2 314 break;
315 }
316 else
317 {
0837b7e8 318 _cupsLangPrintf(stderr, _("cupsd: Unknown argument \"%s\" - aborting."),
a4d04587 319 argv[i]);
320 usage(1);
ef416fc2 321 }
322
323 if (!ConfigurationFile)
324 cupsdSetString(&ConfigurationFile, CUPS_SERVERROOT "/cupsd.conf");
325
cb7f98ee
MS
326 if (!CupsFilesFile)
327 {
328 char *filename, /* Copy of cupsd.conf filename */
329 *slash; /* Final slash in cupsd.conf filename */
330 size_t len; /* Size of buffer */
331
332 len = strlen(ConfigurationFile) + 15;
333 if ((filename = malloc(len)) == NULL)
334 {
335 _cupsLangPrintf(stderr,
336 _("cupsd: Unable to get path to "
337 "cups-files.conf file."));
338 return (1);
339 }
340
341 strlcpy(filename, ConfigurationFile, len);
342 if ((slash = strrchr(filename, '/')) == NULL)
343 {
344 _cupsLangPrintf(stderr,
345 _("cupsd: Unable to get path to "
346 "cups-files.conf file."));
347 return (1);
348 }
349
350 strlcpy(slash, "/cups-files.conf", len - (slash - filename));
351 cupsdSetString(&CupsFilesFile, filename);
352 free(filename);
353 }
354
ef416fc2 355 /*
356 * If the user hasn't specified "-f", run in the background...
357 */
358
359 if (!fg)
360 {
361 /*
362 * Setup signal handlers for the parent...
363 */
364
365#ifdef HAVE_SIGSET /* Use System V signals over POSIX to avoid bugs */
366 sigset(SIGUSR1, parent_handler);
367 sigset(SIGCHLD, parent_handler);
368
369 sigset(SIGHUP, SIG_IGN);
370#elif defined(HAVE_SIGACTION)
371 memset(&action, 0, sizeof(action));
372 sigemptyset(&action.sa_mask);
373 sigaddset(&action.sa_mask, SIGUSR1);
374 action.sa_handler = parent_handler;
375 sigaction(SIGUSR1, &action, NULL);
376 sigaction(SIGCHLD, &action, NULL);
377
378 sigemptyset(&action.sa_mask);
379 action.sa_handler = SIG_IGN;
380 sigaction(SIGHUP, &action, NULL);
381#else
382 signal(SIGUSR1, parent_handler);
383 signal(SIGCLD, parent_handler);
384
385 signal(SIGHUP, SIG_IGN);
386#endif /* HAVE_SIGSET */
387
388 if (fork() > 0)
389 {
390 /*
391 * OK, wait for the child to startup and send us SIGUSR1 or to crash
392 * and the OS send us SIGCHLD... We also need to ignore SIGHUP which
393 * might be sent by the init script to restart the scheduler...
394 */
395
396 for (; parent_signal == 0;)
397 sleep(1);
398
399 if (parent_signal == SIGUSR1)
400 return (0);
401
402 if (wait(&i) < 0)
403 {
404 perror("cupsd");
405 return (1);
406 }
407 else if (WIFEXITED(i))
408 {
4d301e69 409 fprintf(stderr, "cupsd: Child exited with status %d\n",
bd7854cb 410 WEXITSTATUS(i));
ef416fc2 411 return (2);
412 }
413 else
414 {
4d301e69 415 fprintf(stderr, "cupsd: Child exited on signal %d\n", WTERMSIG(i));
ef416fc2 416 return (3);
417 }
418 }
a41f09e2 419
3dd9c340 420#if defined(__OpenBSD__) && OpenBSD < 201211
68b10830
MS
421 /*
422 * Call _thread_sys_closefrom() so the child process doesn't reset the
423 * parent's file descriptors to be blocking. This is a workaround for a
3dd9c340 424 * limitation of userland libpthread on older versions of OpenBSD.
68b10830 425 */
ef55b745 426
68b10830 427 _thread_sys_closefrom(0);
3dd9c340 428#endif /* __OpenBSD__ && OpenBSD < 201211 */
68b10830 429
a41f09e2 430 /*
68b10830
MS
431 * Since CoreFoundation and DBUS both create fork-unsafe data on execution of
432 * a program, and since this kind of really unfriendly behavior seems to be
433 * more common these days in system libraries, we need to re-execute the
434 * background cupsd with the "-C" option to avoid problems. Unfortunately,
435 * we also have to assume that argv[0] contains the name of the cupsd
436 * executable - there is no portable way to get the real pathname...
a41f09e2
MS
437 */
438
439 execlp(argv[0], argv[0], "-C", ConfigurationFile, (char *)0);
440 exit(errno);
ef416fc2 441 }
442
443 if (fg < 1)
444 {
445 /*
446 * Make sure we aren't tying up any filesystems...
447 */
448
449 chdir("/");
450
451#ifndef DEBUG
452 /*
453 * Disable core dumps...
454 */
455
456 getrlimit(RLIMIT_CORE, &limit);
457 limit.rlim_cur = 0;
458 setrlimit(RLIMIT_CORE, &limit);
459
460 /*
461 * Disconnect from the controlling terminal...
462 */
463
464 setsid();
465
466 /*
467 * Close all open files...
468 */
469
470 getrlimit(RLIMIT_NOFILE, &limit);
471
b94498cf 472 for (i = 0; i < limit.rlim_cur && i < 1024; i ++)
ef416fc2 473 close(i);
a4924f6c
MS
474
475 /*
476 * Redirect stdin/out/err to /dev/null...
477 */
478
68b10830
MS
479 if ((i = open("/dev/null", O_RDONLY)) != 0)
480 {
481 dup2(i, 0);
482 close(i);
483 }
484
485 if ((i = open("/dev/null", O_WRONLY)) != 1)
486 {
487 dup2(i, 1);
488 close(i);
489 }
490
491 if ((i = open("/dev/null", O_WRONLY)) != 2)
492 {
493 dup2(i, 2);
494 close(i);
495 }
ef416fc2 496#endif /* DEBUG */
497 }
498
499 /*
500 * Set the timezone info...
501 */
502
503 tzset();
504
505#ifdef LC_TIME
506 setlocale(LC_TIME, "");
507#endif /* LC_TIME */
508
509 /*
510 * Set the maximum number of files...
511 */
512
513 getrlimit(RLIMIT_NOFILE, &limit);
514
f7deaa1a 515#if !defined(HAVE_POLL) && !defined(HAVE_EPOLL) && !defined(HAVE_KQUEUE)
f7faf1f5 516 if (limit.rlim_max > FD_SETSIZE)
517 MaxFDs = FD_SETSIZE;
ef416fc2 518 else
f7deaa1a 519#endif /* !HAVE_POLL && !HAVE_EPOLL && !HAVE_KQUEUE */
520#ifdef RLIM_INFINITY
521 if (limit.rlim_max == RLIM_INFINITY)
522 MaxFDs = 16384;
523 else
524#endif /* RLIM_INFINITY */
ef416fc2 525 MaxFDs = limit.rlim_max;
526
527 limit.rlim_cur = MaxFDs;
528
529 setrlimit(RLIMIT_NOFILE, &limit);
530
f7deaa1a 531 cupsdStartSelect();
ef416fc2 532
533 /*
534 * Read configuration...
535 */
536
537 if (!cupsdReadConfiguration())
ef416fc2 538 return (1);
2e4ff8af
MS
539 else if (TestConfigFile)
540 {
cb7f98ee
MS
541 printf("\"%s\" is OK.\n", CupsFilesFile);
542 printf("\"%s\" is OK.\n", ConfigurationFile);
2e4ff8af
MS
543 return (0);
544 }
ef416fc2 545
ba55dc12
MS
546 /*
547 * Clean out old temp files and printer cache data.
548 */
cc0d019f 549
ba55dc12 550 if (!strncmp(TempDir, RequestRoot, strlen(RequestRoot)))
321d8d57 551 cupsdCleanFiles(TempDir, NULL);
cc0d019f 552
321d8d57 553 cupsdCleanFiles(CacheDir, "*.ipp");
cc0d019f 554
a4d04587 555#if HAVE_LAUNCHD
4400e98d 556 if (Launchd)
a4d04587 557 {
558 /*
b94498cf 559 * If we were started by launchd get the listen sockets file descriptors...
f7deaa1a 560 */
a4d04587 561
562 launchd_checkin();
e4572d57 563 launchd_checkout();
a4d04587 564 }
565#endif /* HAVE_LAUNCHD */
566
567 /*
568 * Startup the server...
569 */
570
6d2f911b
MS
571 httpInitialize();
572
a4d04587 573 cupsdStartServer();
574
ef416fc2 575 /*
576 * Catch hangup and child signals and ignore broken pipes...
577 */
578
579#ifdef HAVE_SIGSET /* Use System V signals over POSIX to avoid bugs */
e1d6a774 580 sigset(SIGCHLD, sigchld_handler);
4744bd90 581 sigset(SIGHUP, sighup_handler);
ef416fc2 582 sigset(SIGPIPE, SIG_IGN);
583 sigset(SIGTERM, sigterm_handler);
584#elif defined(HAVE_SIGACTION)
585 memset(&action, 0, sizeof(action));
586
e1d6a774 587 sigemptyset(&action.sa_mask);
588 sigaddset(&action.sa_mask, SIGTERM);
589 sigaddset(&action.sa_mask, SIGCHLD);
590 action.sa_handler = sigchld_handler;
591 sigaction(SIGCHLD, &action, NULL);
592
ef416fc2 593 sigemptyset(&action.sa_mask);
594 sigaddset(&action.sa_mask, SIGHUP);
4744bd90 595 action.sa_handler = sighup_handler;
ef416fc2 596 sigaction(SIGHUP, &action, NULL);
597
598 sigemptyset(&action.sa_mask);
599 action.sa_handler = SIG_IGN;
600 sigaction(SIGPIPE, &action, NULL);
601
602 sigemptyset(&action.sa_mask);
603 sigaddset(&action.sa_mask, SIGTERM);
604 sigaddset(&action.sa_mask, SIGCHLD);
605 action.sa_handler = sigterm_handler;
606 sigaction(SIGTERM, &action, NULL);
607#else
e1d6a774 608 signal(SIGCLD, sigchld_handler); /* No, SIGCLD isn't a typo... */
4744bd90 609 signal(SIGHUP, sighup_handler);
ef416fc2 610 signal(SIGPIPE, SIG_IGN);
611 signal(SIGTERM, sigterm_handler);
612#endif /* HAVE_SIGSET */
613
ef416fc2 614 /*
615 * Initialize authentication certificates...
616 */
617
618 cupsdInitCerts();
619
620 /*
621 * If we are running in the background, signal the parent process that
622 * we are up and running...
623 */
624
a41f09e2 625 if (!fg || run_as_child)
ef416fc2 626 {
627 /*
628 * Send a signal to the parent process, but only if the parent is
629 * not PID 1 (init). This avoids accidentally shutting down the
630 * system on OpenBSD if you CTRL-C the server before it is up...
631 */
632
633 i = getppid(); /* Save parent PID to avoid race condition */
634
635 if (i != 1)
636 kill(i, SIGUSR1);
637 }
638
e1d6a774 639#ifdef __APPLE__
09ec0018 640 /*
641 * Start power management framework...
642 */
643
1340db2d
MS
644 if (use_sysman)
645 cupsdStartSystemMonitor();
e1d6a774 646#endif /* __APPLE__ */
ef416fc2 647
49d87452
MS
648 /*
649 * Send server-started event...
650 */
651
652#ifdef HAVE_LAUNCHD
653 if (Launchd)
654 cupsdAddEvent(CUPSD_EVENT_SERVER_STARTED, NULL, NULL,
655 "Scheduler started via launchd.");
656 else
657#endif /* HAVE_LAUNCHD */
658 if (fg)
659 cupsdAddEvent(CUPSD_EVENT_SERVER_STARTED, NULL, NULL,
660 "Scheduler started in foreground.");
661 else
662 cupsdAddEvent(CUPSD_EVENT_SERVER_STARTED, NULL, NULL,
663 "Scheduler started in background.");
664
ef416fc2 665 /*
666 * Start any pending print jobs...
667 */
668
669 cupsdCheckJobs();
670
671 /*
672 * Loop forever...
673 */
674
50fe7201 675 current_time = time(NULL);
50fe7201
MS
676 event_time = current_time;
677 expire_time = current_time;
ef416fc2 678 fds = 1;
5bd77a73 679 report_time = 0;
50fe7201 680 senddoc_time = current_time;
ef416fc2 681
682 while (!stop_scheduler)
683 {
ef416fc2 684 /*
685 * Check if there are dead children to handle...
686 */
687
688 if (dead_children)
689 process_children();
690
691 /*
692 * Check if we need to load the server configuration file...
693 */
694
695 if (NeedReload)
696 {
697 /*
698 * Close any idle clients...
699 */
700
bd7854cb 701 if (cupsArrayCount(Clients) > 0)
ef416fc2 702 {
bd7854cb 703 for (con = (cupsd_client_t *)cupsArrayFirst(Clients);
704 con;
705 con = (cupsd_client_t *)cupsArrayNext(Clients))
996acce8 706 if (httpGetState(con->http) == HTTP_WAITING)
ef416fc2 707 cupsdCloseClient(con);
ef416fc2 708 else
996acce8 709 con->http->keep_alive = HTTP_KEEPALIVE_OFF;
ef416fc2 710
711 cupsdPauseListening();
712 }
713
ef416fc2 714 /*
715 * Restart if all clients are closed and all jobs finished, or
716 * if the reload timeout has elapsed...
717 */
718
bd7854cb 719 if ((cupsArrayCount(Clients) == 0 &&
238c3832 720 (cupsArrayCount(PrintingJobs) == 0 || NeedReload != RELOAD_ALL)) ||
ef416fc2 721 (time(NULL) - ReloadTime) >= ReloadTimeout)
722 {
a4d04587 723 /*
724 * Shutdown the server...
725 */
726
7cf5915e
MS
727 DoingShutdown = 1;
728
a4d04587 729 cupsdStopServer();
730
731 /*
732 * Read configuration...
733 */
734
ef416fc2 735 if (!cupsdReadConfiguration())
736 {
737 syslog(LOG_LPR, "Unable to read configuration file \'%s\' - exiting!",
738 ConfigurationFile);
739 break;
740 }
a4d04587 741
742#if HAVE_LAUNCHD
4400e98d 743 if (Launchd)
a4d04587 744 {
b94498cf 745 /*
5a662dc0
MS
746 * If we were started by launchd, get the listen socket file
747 * descriptors...
b94498cf 748 */
749
a4d04587 750 launchd_checkin();
e4572d57 751 launchd_checkout();
a4d04587 752 }
753#endif /* HAVE_LAUNCHD */
754
755 /*
756 * Startup the server...
757 */
758
7cf5915e
MS
759 DoingShutdown = 0;
760
a4d04587 761 cupsdStartServer();
49d87452
MS
762
763 /*
764 * Send a server-restarted event...
765 */
766
767 cupsdAddEvent(CUPSD_EVENT_SERVER_RESTARTED, NULL, NULL,
768 "Scheduler restarted.");
ef416fc2 769 }
770 }
771
772 /*
f7deaa1a 773 * Check for available input or ready output. If cupsdDoSelect()
774 * returns 0 or -1, something bad happened and we should exit
775 * immediately.
ef416fc2 776 *
777 * Note that we at least have one listening socket open at all
778 * times.
779 */
780
50fe7201
MS
781 if ((timeout = select_timeout(fds)) > 1 && LastEvent)
782 timeout = 1;
ef416fc2 783
a4d04587 784#if HAVE_LAUNCHD
785 /*
786 * If no other work is scheduled and we're being controlled by
411affcf 787 * launchd then timeout after 'LaunchdTimeout' seconds of
a4d04587 788 * inactivity...
789 */
790
a2326b5b 791 if (timeout == 86400 && Launchd && LaunchdTimeout &&
bc44d920 792 !cupsArrayCount(ActiveJobs) &&
a2326b5b 793 (!Browsing || !BrowseLocalProtocols || !cupsArrayCount(Printers)))
a4d04587 794 {
f7deaa1a 795 timeout = LaunchdTimeout;
a4d04587 796 launchd_idle_exit = 1;
797 }
798 else
799 launchd_idle_exit = 0;
800#endif /* HAVE_LAUNCHD */
801
f7deaa1a 802 if ((fds = cupsdDoSelect(timeout)) < 0)
ef416fc2 803 {
ef416fc2 804 /*
805 * Got an error from select!
806 */
807
37e7e6e0 808#if defined(HAVE_DNSSD) || defined(HAVE_AVAHI)
f7deaa1a 809 cupsd_printer_t *p; /* Current printer */
37e7e6e0 810#endif /* HAVE_DNSSD || HAVE_AVAHI */
f7deaa1a 811
812
813 if (errno == EINTR) /* Just interrupted by a signal */
ef416fc2 814 continue;
815
816 /*
817 * Log all sorts of debug info to help track down the problem.
818 */
819
f7deaa1a 820 cupsdLogMessage(CUPSD_LOG_EMERG, "cupsdDoSelect() failed - %s!",
ef416fc2 821 strerror(errno));
822
bd7854cb 823 for (i = 0, con = (cupsd_client_t *)cupsArrayFirst(Clients);
824 con;
825 i ++, con = (cupsd_client_t *)cupsArrayNext(Clients))
ef416fc2 826 cupsdLogMessage(CUPSD_LOG_EMERG,
827 "Clients[%d] = %d, file = %d, state = %d",
996acce8 828 i, con->number, con->file, httpGetState(con->http));
ef416fc2 829
bd7854cb 830 for (i = 0, lis = (cupsd_listener_t *)cupsArrayFirst(Listeners);
831 lis;
832 i ++, lis = (cupsd_listener_t *)cupsArrayNext(Listeners))
ef416fc2 833 cupsdLogMessage(CUPSD_LOG_EMERG, "Listeners[%d] = %d", i, lis->fd);
834
923edb68 835 cupsdLogMessage(CUPSD_LOG_EMERG, "CGIPipes[0] = %d", CGIPipes[0]);
836
09ec0018 837#ifdef __APPLE__
838 cupsdLogMessage(CUPSD_LOG_EMERG, "SysEventPipes[0] = %d",
839 SysEventPipes[0]);
840#endif /* __APPLE__ */
841
ef416fc2 842 for (job = (cupsd_job_t *)cupsArrayFirst(ActiveJobs);
843 job;
844 job = (cupsd_job_t *)cupsArrayNext(ActiveJobs))
845 cupsdLogMessage(CUPSD_LOG_EMERG, "Jobs[%d] = %d < [%d %d] > [%d %d]",
846 job->id,
847 job->status_buffer ? job->status_buffer->fd : -1,
848 job->print_pipes[0], job->print_pipes[1],
849 job->back_pipes[0], job->back_pipes[1]);
f7deaa1a 850
37e7e6e0 851#if defined(HAVE_DNSSD) || defined(HAVE_AVAHI)
f7deaa1a 852 for (p = (cupsd_printer_t *)cupsArrayFirst(Printers);
853 p;
854 p = (cupsd_printer_t *)cupsArrayNext(Printers))
7a14d768
MS
855 cupsdLogMessage(CUPSD_LOG_EMERG, "printer[%s] reg_name=\"%s\"", p->name,
856 p->reg_name ? p->reg_name : "(null)");
37e7e6e0 857#endif /* HAVE_DNSSD || HAVE_AVAHI */
f7deaa1a 858
ef416fc2 859 break;
860 }
861
862 current_time = time(NULL);
863
3dfe78b3
MS
864 /*
865 * Write dirty config/state files...
866 */
867
868 if (DirtyCleanTime && current_time >= DirtyCleanTime)
3dfe78b3 869 cupsdCleanDirty();
3dfe78b3 870
e6013cfa
MS
871#ifdef __APPLE__
872 /*
873 * If we are going to sleep and still have pending jobs, stop them after
874 * a period of time...
875 */
876
877 if (SleepJobs > 0 && current_time >= SleepJobs &&
878 cupsArrayCount(PrintingJobs) > 0)
879 {
880 SleepJobs = 0;
e60ec91f 881 cupsdStopAllJobs(CUPSD_JOB_DEFAULT, 5);
e6013cfa
MS
882 }
883#endif /* __APPLE__ */
884
a4924f6c
MS
885#ifndef __APPLE__
886 /*
887 * Update the network interfaces once a minute...
888 */
889
890 if ((current_time - netif_time) >= 60)
891 {
892 netif_time = current_time;
893 NetIFUpdate = 1;
894 }
895#endif /* !__APPLE__ */
896
a4d04587 897#if HAVE_LAUNCHD
898 /*
411affcf 899 * If no other work was scheduled and we're being controlled by launchd
a4d04587 900 * then timeout after 'LaunchdTimeout' seconds of inactivity...
901 */
902
903 if (!fds && launchd_idle_exit)
904 {
905 cupsdLogMessage(CUPSD_LOG_INFO,
906 "Printer sharing is off and there are no jobs pending, "
907 "will restart on demand.");
908 stop_scheduler = 1;
909 break;
910 }
911#endif /* HAVE_LAUNCHD */
912
76cd9e37
MS
913 /*
914 * Resume listening for new connections as needed...
915 */
916
917 if (ListeningPaused && ListeningPaused <= current_time &&
918 cupsArrayCount(Clients) < MaxClients)
919 cupsdResumeListening();
920
ef416fc2 921 /*
bd7854cb 922 * Expire subscriptions and unload completed jobs as needed...
ef416fc2 923 */
924
bd7854cb 925 if (current_time > expire_time)
ef416fc2 926 {
bd7854cb 927 if (cupsArrayCount(Subscriptions) > 0)
928 cupsdExpireSubscriptions(NULL, NULL);
929
930 cupsdUnloadCompletedJobs();
ef416fc2 931
932 expire_time = current_time;
933 }
934
07ed0e9a 935#ifndef HAVE_AUTHORIZATION_H
ef416fc2 936 /*
f7deaa1a 937 * Update the root certificate once every 5 minutes if we have client
938 * connections...
ef416fc2 939 */
940
f7deaa1a 941 if ((current_time - RootCertTime) >= RootCertDuration && RootCertDuration &&
942 !RunUser && cupsArrayCount(Clients))
943 {
944 /*
945 * Update the root certificate...
946 */
947
948 cupsdDeleteCert(0);
0fa6c7fa 949 cupsdAddCert(0, "root", cupsdDefaultAuthType());
f7deaa1a 950 }
07ed0e9a 951#endif /* !HAVE_AUTHORIZATION_H */
ef416fc2 952
953 /*
954 * Check for new data on the client sockets...
955 */
956
bd7854cb 957 for (con = (cupsd_client_t *)cupsArrayFirst(Clients);
958 con;
959 con = (cupsd_client_t *)cupsArrayNext(Clients))
ef416fc2 960 {
961 /*
f7deaa1a 962 * Process pending data in the input buffer...
ef416fc2 963 */
964
996acce8
MS
965 // TODO: Use httpGetReady()
966 if (con->http->used)
ef416fc2 967 {
f7deaa1a 968 cupsdReadClient(con);
969 continue;
ef416fc2 970 }
971
972 /*
973 * Check the activity and close old clients...
974 */
975
996acce8 976 // TODO: Use httpGetActivity()
ef416fc2 977 activity = current_time - Timeout;
996acce8 978 if (con->http->activity < activity && !con->pipe_pid)
ef416fc2 979 {
980 cupsdLogMessage(CUPSD_LOG_DEBUG,
981 "Closing client %d after %d seconds of inactivity...",
996acce8 982 con->number, Timeout);
ef416fc2 983
984 cupsdCloseClient(con);
ef416fc2 985 continue;
986 }
987 }
988
989 /*
990 * Update any pending multi-file documents...
991 */
992
993 if ((current_time - senddoc_time) >= 10)
994 {
995 cupsdCheckJobs();
996 senddoc_time = current_time;
997 }
998
82cc1f9a
MS
999 /*
1000 * Clean job history...
1001 */
1002
1003 if (JobHistoryUpdate && current_time >= JobHistoryUpdate)
1004 cupsdCleanJobs();
1005
ef416fc2 1006 /*
5bd77a73 1007 * Log statistics at most once a minute when in debug mode...
ef416fc2 1008 */
1009
5bd77a73 1010 if ((current_time - report_time) >= 60 && LogLevel >= CUPSD_LOG_DEBUG)
ef416fc2 1011 {
5bd77a73
MS
1012 size_t string_count, /* String count */
1013 alloc_bytes, /* Allocated string bytes */
1014 total_bytes; /* Total string bytes */
4400e98d 1015#ifdef HAVE_MALLINFO
5bd77a73 1016 struct mallinfo mem; /* Malloc information */
ef416fc2 1017
1018
1019 mem = mallinfo();
5bd77a73
MS
1020 cupsdLogMessage(CUPSD_LOG_DEBUG, "Report: malloc-arena=%lu", mem.arena);
1021 cupsdLogMessage(CUPSD_LOG_DEBUG, "Report: malloc-used=%lu",
1022 mem.usmblks + mem.uordblks);
1023 cupsdLogMessage(CUPSD_LOG_DEBUG, "Report: malloc-free=%lu",
ef416fc2 1024 mem.fsmblks + mem.fordblks);
4400e98d 1025#endif /* HAVE_MALLINFO */
1026
5bd77a73
MS
1027 cupsdLogMessage(CUPSD_LOG_DEBUG, "Report: clients=%d",
1028 cupsArrayCount(Clients));
1029 cupsdLogMessage(CUPSD_LOG_DEBUG, "Report: jobs=%d",
1030 cupsArrayCount(Jobs));
1031 cupsdLogMessage(CUPSD_LOG_DEBUG, "Report: jobs-active=%d",
1032 cupsArrayCount(ActiveJobs));
1033 cupsdLogMessage(CUPSD_LOG_DEBUG, "Report: printers=%d",
1034 cupsArrayCount(Printers));
5bd77a73 1035
757d2cad 1036 string_count = _cupsStrStatistics(&alloc_bytes, &total_bytes);
5bd77a73
MS
1037 cupsdLogMessage(CUPSD_LOG_DEBUG,
1038 "Report: stringpool-string-count=" CUPS_LLFMT,
1039 CUPS_LLCAST string_count);
1040 cupsdLogMessage(CUPSD_LOG_DEBUG,
1041 "Report: stringpool-alloc-bytes=" CUPS_LLFMT,
1042 CUPS_LLCAST alloc_bytes);
1043 cupsdLogMessage(CUPSD_LOG_DEBUG,
1044 "Report: stringpool-total-bytes=" CUPS_LLFMT,
4400e98d 1045 CUPS_LLCAST total_bytes);
1046
5bd77a73 1047 report_time = current_time;
ef416fc2 1048 }
ef416fc2 1049
fa73b229 1050 /*
1051 * Handle OS-specific event notification for any events that have
1052 * accumulated. Don't send these more than once a second...
1053 */
1054
50fe7201 1055 if (LastEvent && (current_time - event_time) >= 1)
fa73b229 1056 {
1057#ifdef HAVE_NOTIFY_POST
a41f09e2
MS
1058 if (LastEvent & (CUPSD_EVENT_PRINTER_ADDED |
1059 CUPSD_EVENT_PRINTER_DELETED |
1060 CUPSD_EVENT_PRINTER_MODIFIED))
fa73b229 1061 {
e00b005a 1062 cupsdLogMessage(CUPSD_LOG_DEBUG2,
fa73b229 1063 "notify_post(\"com.apple.printerListChange\")");
1064 notify_post("com.apple.printerListChange");
1065 }
1066
1067 if (LastEvent & CUPSD_EVENT_PRINTER_STATE_CHANGED)
1068 {
e00b005a 1069 cupsdLogMessage(CUPSD_LOG_DEBUG2,
fa73b229 1070 "notify_post(\"com.apple.printerHistoryChange\")");
1071 notify_post("com.apple.printerHistoryChange");
1072 }
1073
1074 if (LastEvent & (CUPSD_EVENT_JOB_STATE_CHANGED |
1075 CUPSD_EVENT_JOB_CONFIG_CHANGED |
1076 CUPSD_EVENT_JOB_PROGRESS))
1077 {
e00b005a 1078 cupsdLogMessage(CUPSD_LOG_DEBUG2,
fa73b229 1079 "notify_post(\"com.apple.jobChange\")");
1080 notify_post("com.apple.jobChange");
1081 }
1082#endif /* HAVE_NOTIFY_POST */
1083
1084 /*
e53920b9 1085 * Reset the accumulated events...
fa73b229 1086 */
1087
50fe7201
MS
1088 LastEvent = CUPSD_EVENT_NONE;
1089 event_time = current_time;
fa73b229 1090 }
ef416fc2 1091 }
1092
1093 /*
1094 * Log a message based on what happened...
1095 */
1096
1097 if (stop_scheduler)
49d87452 1098 {
ef416fc2 1099 cupsdLogMessage(CUPSD_LOG_INFO, "Scheduler shutting down normally.");
49d87452
MS
1100 cupsdAddEvent(CUPSD_EVENT_SERVER_STOPPED, NULL, NULL,
1101 "Scheduler shutting down normally.");
1102 }
ef416fc2 1103 else
49d87452 1104 {
ef416fc2 1105 cupsdLogMessage(CUPSD_LOG_ERROR,
1106 "Scheduler shutting down due to program error.");
49d87452
MS
1107 cupsdAddEvent(CUPSD_EVENT_SERVER_STOPPED, NULL, NULL,
1108 "Scheduler shutting down due to program error.");
1109 }
ef416fc2 1110
1111 /*
7ff4fea9 1112 * Close all network clients...
ef416fc2 1113 */
1114
7cf5915e
MS
1115 DoingShutdown = 1;
1116
ef416fc2 1117 cupsdStopServer();
1118
7ff4fea9
MS
1119#ifdef HAVE_LAUNCHD
1120 /*
1121 * Update the launchd KeepAlive file as needed...
1122 */
1123
1124 if (Launchd)
1125 launchd_checkout();
1126#endif /* HAVE_LAUNCHD */
1127
1128 /*
1129 * Stop all jobs...
1130 */
1131
bd7854cb 1132 cupsdFreeAllJobs();
ef416fc2 1133
e1d6a774 1134#ifdef __APPLE__
7ff4fea9
MS
1135 /*
1136 * Stop monitoring system event monitoring...
1137 */
1138
1340db2d
MS
1139 if (use_sysman)
1140 cupsdStopSystemMonitor();
e1d6a774 1141#endif /* __APPLE__ */
09ec0018 1142
f7deaa1a 1143 cupsdStopSelect();
ef416fc2 1144
1145 return (!stop_scheduler);
1146}
1147
1148
10d09e33
MS
1149/*
1150 * 'cupsdAddString()' - Copy and add a string to an array.
1151 */
1152
1153int /* O - 1 on success, 0 on failure */
1154cupsdAddString(cups_array_t **a, /* IO - String array */
1155 const char *s) /* I - String to copy and add */
1156{
1157 if (!*a)
1158 *a = cupsArrayNew3((cups_array_func_t)strcmp, NULL,
1159 (cups_ahash_func_t)NULL, 0,
1160 (cups_acopy_func_t)_cupsStrAlloc,
1161 (cups_afree_func_t)_cupsStrFree);
1162
1163 return (cupsArrayAdd(*a, (char *)s));
1164}
1165
1166
d7871c8c
MS
1167/*
1168 * 'cupsdCheckProcess()' - Tell the main loop to check for dead children.
1169 */
1170
1171void
1172cupsdCheckProcess(void)
1173{
1174 /*
1175 * Flag that we have dead children...
1176 */
1177
1178 dead_children = 1;
1179}
1180
1181
10d09e33
MS
1182/*
1183 * 'cupsdClearString()' - Clear a string.
1184 */
1185
1186void
1187cupsdClearString(char **s) /* O - String value */
1188{
1189 if (s && *s)
1190 {
1191 _cupsStrFree(*s);
1192 *s = NULL;
1193 }
1194}
1195
1196
10d09e33
MS
1197/*
1198 * 'cupsdFreeStrings()' - Free an array of strings.
1199 */
1200
1201void
1202cupsdFreeStrings(cups_array_t **a) /* IO - String array */
1203{
1204 if (*a)
1205 {
1206 cupsArrayDelete(*a);
1207 *a = NULL;
1208 }
1209}
1210
1211
1212/*
1213 * 'cupsdHoldSignals()' - Hold child and termination signals.
1214 */
1215
1216void
1217cupsdHoldSignals(void)
1218{
1219#if defined(HAVE_SIGACTION) && !defined(HAVE_SIGSET)
1220 sigset_t newmask; /* New POSIX signal mask */
1221#endif /* HAVE_SIGACTION && !HAVE_SIGSET */
1222
1223
1224 holdcount ++;
1225 if (holdcount > 1)
1226 return;
1227
1228#ifdef HAVE_SIGSET
1229 sighold(SIGTERM);
1230 sighold(SIGCHLD);
1231#elif defined(HAVE_SIGACTION)
1232 sigemptyset(&newmask);
1233 sigaddset(&newmask, SIGTERM);
1234 sigaddset(&newmask, SIGCHLD);
1235 sigprocmask(SIG_BLOCK, &newmask, &holdmask);
1236#endif /* HAVE_SIGSET */
1237}
1238
1239
ef416fc2 1240/*
1241 * 'cupsdReleaseSignals()' - Release signals for delivery.
1242 */
1243
1244void
1245cupsdReleaseSignals(void)
1246{
1247 holdcount --;
1248 if (holdcount > 0)
1249 return;
1250
1251#ifdef HAVE_SIGSET
1252 sigrelse(SIGTERM);
1253 sigrelse(SIGCHLD);
1254#elif defined(HAVE_SIGACTION)
1255 sigprocmask(SIG_SETMASK, &holdmask, NULL);
1256#endif /* HAVE_SIGSET */
1257}
1258
1259
1260/*
1261 * 'cupsdSetString()' - Set a string value.
1262 */
1263
1264void
1265cupsdSetString(char **s, /* O - New string */
1266 const char *v) /* I - String value */
1267{
1268 if (!s || *s == v)
1269 return;
1270
1271 if (*s)
e53920b9 1272 _cupsStrFree(*s);
ef416fc2 1273
1274 if (v)
e53920b9 1275 *s = _cupsStrAlloc(v);
ef416fc2 1276 else
1277 *s = NULL;
1278}
1279
1280
1281/*
1282 * 'cupsdSetStringf()' - Set a formatted string value.
1283 */
1284
1285void
1286cupsdSetStringf(char **s, /* O - New string */
1287 const char *f, /* I - Printf-style format string */
1288 ...) /* I - Additional args as needed */
1289{
94436c5a 1290 char v[65536 + 64]; /* Formatting string value */
ef416fc2 1291 va_list ap; /* Argument pointer */
1292 char *olds; /* Old string */
1293
1294
1295 if (!s)
1296 return;
1297
1298 olds = *s;
1299
1300 if (f)
1301 {
1302 va_start(ap, f);
1303 vsnprintf(v, sizeof(v), f, ap);
1304 va_end(ap);
1305
e53920b9 1306 *s = _cupsStrAlloc(v);
ef416fc2 1307 }
1308 else
1309 *s = NULL;
1310
1311 if (olds)
e53920b9 1312 _cupsStrFree(olds);
ef416fc2 1313}
1314
1315
a4d04587 1316#ifdef HAVE_LAUNCHD
1317/*
1318 * 'launchd_checkin()' - Check-in with launchd and collect the listening fds.
1319 */
1320
1321static void
1322launchd_checkin(void)
1323{
06d4e77b 1324 size_t i, /* Looping var */
f8b3a85b 1325 count; /* Number of listeners */
a4d04587 1326 launch_data_t ld_msg, /* Launch data message */
1327 ld_resp, /* Launch data response */
1328 ld_array, /* Launch data array */
1329 ld_sockets, /* Launch data sockets dictionary */
a4d04587 1330 tmp; /* Launch data */
1331 cupsd_listener_t *lis; /* Listeners array */
1332 http_addr_t addr; /* Address variable */
1333 socklen_t addrlen; /* Length of address */
f7deaa1a 1334 int fd; /* File descriptor */
1335 char s[256]; /* String addresss */
a4d04587 1336
1337
1338 cupsdLogMessage(CUPSD_LOG_DEBUG, "launchd_checkin: pid=%d", (int)getpid());
1339
1340 /*
1341 * Check-in with launchd...
1342 */
1343
1344 ld_msg = launch_data_new_string(LAUNCH_KEY_CHECKIN);
1345 if ((ld_resp = launch_msg(ld_msg)) == NULL)
1346 {
bd7854cb 1347 cupsdLogMessage(CUPSD_LOG_ERROR,
a4d04587 1348 "launchd_checkin: launch_msg(\"" LAUNCH_KEY_CHECKIN
1349 "\") IPC failure");
1350 exit(EXIT_FAILURE);
749b1e90 1351 return; /* anti-compiler-warning */
a4d04587 1352 }
bd7854cb 1353
a4d04587 1354 if (launch_data_get_type(ld_resp) == LAUNCH_DATA_ERRNO)
1355 {
1356 errno = launch_data_get_errno(ld_resp);
1357 cupsdLogMessage(CUPSD_LOG_ERROR, "launchd_checkin: Check-in failed: %s",
1358 strerror(errno));
1359 exit(EXIT_FAILURE);
749b1e90 1360 return; /* anti-compiler-warning */
a4d04587 1361 }
1362
a4d04587 1363 /*
1364 * Get the sockets dictionary...
1365 */
1366
749b1e90
MS
1367 if ((ld_sockets = launch_data_dict_lookup(ld_resp, LAUNCH_JOBKEY_SOCKETS))
1368 == NULL)
a4d04587 1369 {
1370 cupsdLogMessage(CUPSD_LOG_ERROR,
1371 "launchd_checkin: No sockets found to answer requests on!");
1372 exit(EXIT_FAILURE);
749b1e90 1373 return; /* anti-compiler-warning */
a4d04587 1374 }
bd7854cb 1375
a4d04587 1376 /*
1377 * Get the array of listener sockets...
1378 */
1379
749b1e90 1380 if ((ld_array = launch_data_dict_lookup(ld_sockets, "Listeners")) == NULL)
a4d04587 1381 {
1382 cupsdLogMessage(CUPSD_LOG_ERROR,
1383 "launchd_checkin: No sockets found to answer requests on!");
1384 exit(EXIT_FAILURE);
749b1e90 1385 return; /* anti-compiler-warning */
a4d04587 1386 }
1387
1388 /*
1389 * Add listening fd(s) to the Listener array...
1390 */
1391
1392 if (launch_data_get_type(ld_array) == LAUNCH_DATA_ARRAY)
1393 {
f7deaa1a 1394 count = launch_data_array_get_count(ld_array);
bd7854cb 1395
1396 for (i = 0; i < count; i ++)
a4d04587 1397 {
1398 /*
f7deaa1a 1399 * Get the launchd file descriptor and address...
a4d04587 1400 */
bd7854cb 1401
749b1e90 1402 if ((tmp = launch_data_array_get_index(ld_array, i)) != NULL)
bd7854cb 1403 {
749b1e90
MS
1404 fd = launch_data_get_fd(tmp);
1405 addrlen = sizeof(addr);
bd7854cb 1406
749b1e90
MS
1407 if (getsockname(fd, (struct sockaddr *)&addr, &addrlen))
1408 {
1409 cupsdLogMessage(CUPSD_LOG_ERROR,
1410 "launchd_checkin: Unable to get local address - %s",
1411 strerror(errno));
1412 continue;
1413 }
bd7854cb 1414
749b1e90
MS
1415 /*
1416 * Try to match the launchd socket address to one of the listeners...
1417 */
f7deaa1a 1418
749b1e90
MS
1419 for (lis = (cupsd_listener_t *)cupsArrayFirst(Listeners);
1420 lis;
1421 lis = (cupsd_listener_t *)cupsArrayNext(Listeners))
1422 if (httpAddrEqual(&lis->address, &addr))
1423 break;
a4d04587 1424
749b1e90
MS
1425 /*
1426 * Add a new listener If there's no match...
1427 */
f7deaa1a 1428
749b1e90
MS
1429 if (lis)
1430 {
ef55b745 1431 cupsdLogMessage(CUPSD_LOG_DEBUG,
749b1e90
MS
1432 "launchd_checkin: Matched existing listener %s with fd %d...",
1433 httpAddrString(&(lis->address), s, sizeof(s)), fd);
1434 }
1435 else
1436 {
ef55b745 1437 cupsdLogMessage(CUPSD_LOG_DEBUG,
749b1e90
MS
1438 "launchd_checkin: Adding new listener %s with fd %d...",
1439 httpAddrString(&addr, s, sizeof(s)), fd);
f7deaa1a 1440
749b1e90
MS
1441 if ((lis = calloc(1, sizeof(cupsd_listener_t))) == NULL)
1442 {
1443 cupsdLogMessage(CUPSD_LOG_ERROR,
5a662dc0
MS
1444 "launchd_checkin: Unable to allocate listener - "
1445 "%s.", strerror(errno));
749b1e90
MS
1446 exit(EXIT_FAILURE);
1447 }
f7deaa1a 1448
749b1e90 1449 cupsArrayAdd(Listeners, lis);
bd7854cb 1450
749b1e90
MS
1451 memcpy(&lis->address, &addr, sizeof(lis->address));
1452 }
1453
1454 lis->fd = fd;
f7deaa1a 1455
a4d04587 1456# ifdef HAVE_SSL
a469f8a5 1457 if (httpAddrPort(&(lis->address)) == 443)
749b1e90 1458 lis->encryption = HTTP_ENCRYPT_ALWAYS;
a4d04587 1459# endif /* HAVE_SSL */
749b1e90 1460 }
a4d04587 1461 }
1462 }
1463
a4d04587 1464 launch_data_free(ld_msg);
1465 launch_data_free(ld_resp);
1466}
1467
1468
f7deaa1a 1469/*
1470 * 'launchd_checkout()' - Update the launchd KeepAlive file as needed.
1471 */
1472
1473static void
1474launchd_checkout(void)
1475{
1476 int fd; /* File descriptor */
1477
1478
1479 /*
1480 * Create or remove the launchd KeepAlive file based on whether
ef55b745 1481 * there are active jobs, polling, browsing for remote printers or
f7deaa1a 1482 * shared printers to advertise...
1483 */
1484
a2326b5b
MS
1485 if (cupsArrayCount(ActiveJobs) ||
1486 (Browsing && BrowseLocalProtocols && cupsArrayCount(Printers)))
f7deaa1a 1487 {
1488 cupsdLogMessage(CUPSD_LOG_DEBUG,
5a662dc0
MS
1489 "Creating launchd keepalive file \"" CUPS_KEEPALIVE
1490 "\"...");
f7deaa1a 1491
1492 if ((fd = open(CUPS_KEEPALIVE, O_RDONLY | O_CREAT | O_EXCL, S_IRUSR)) >= 0)
1493 close(fd);
1494 }
1495 else
1496 {
1497 cupsdLogMessage(CUPSD_LOG_DEBUG,
5a662dc0
MS
1498 "Removing launchd keepalive file \"" CUPS_KEEPALIVE
1499 "\"...");
f7deaa1a 1500
1501 unlink(CUPS_KEEPALIVE);
1502 }
1503}
a4d04587 1504#endif /* HAVE_LAUNCHD */
1505
1506
ef416fc2 1507/*
1508 * 'parent_handler()' - Catch USR1/CHLD signals...
1509 */
1510
1511static void
1512parent_handler(int sig) /* I - Signal */
1513{
1514 /*
1515 * Store the signal we got from the OS and return...
1516 */
1517
1518 parent_signal = sig;
1519}
1520
1521
1522/*
1523 * 'process_children()' - Process all dead children...
1524 */
1525
1526static void
1527process_children(void)
1528{
1529 int status; /* Exit status of child */
b9faaae1
MS
1530 int pid, /* Process ID of child */
1531 job_id; /* Job ID of child */
ef416fc2 1532 cupsd_job_t *job; /* Current job */
1533 int i; /* Looping var */
e00b005a 1534 char name[1024]; /* Process name */
dcb445bc 1535 const char *type; /* Type of program */
ef416fc2 1536
1537
1538 cupsdLogMessage(CUPSD_LOG_DEBUG2, "process_children()");
1539
1540 /*
1541 * Reset the dead_children flag...
1542 */
1543
1544 dead_children = 0;
1545
1546 /*
1547 * Collect the exit status of some children...
1548 */
1549
1550#ifdef HAVE_WAITPID
1551 while ((pid = waitpid(-1, &status, WNOHANG)) > 0)
1552#elif defined(HAVE_WAIT3)
1553 while ((pid = wait3(&status, WNOHANG, NULL)) > 0)
1554#else
1555 if ((pid = wait(&status)) > 0)
1556#endif /* HAVE_WAITPID */
1557 {
ef416fc2 1558 /*
745129be 1559 * Collect the name of the process that finished...
ef416fc2 1560 */
1561
b9faaae1 1562 cupsdFinishProcess(pid, name, sizeof(name), &job_id);
e00b005a 1563
ef416fc2 1564 /*
1565 * Delete certificates for CGI processes...
1566 */
1567
1568 if (pid)
1569 cupsdDeleteCert(pid);
1570
1571 /*
b9faaae1 1572 * Handle completed job filters...
ef416fc2 1573 */
1574
dcb445bc
MS
1575 if (job_id > 0)
1576 job = cupsdFindJob(job_id);
1577 else
1578 job = NULL;
1579
1580 if (job)
b9faaae1
MS
1581 {
1582 for (i = 0; job->filters[i]; i ++)
1583 if (job->filters[i] == pid)
1584 break;
1585
1586 if (job->filters[i] || job->backend == pid)
ef416fc2 1587 {
b9faaae1
MS
1588 /*
1589 * OK, this process has gone away; what's left?
1590 */
1591
1592 if (job->filters[i])
dcb445bc 1593 {
b9faaae1 1594 job->filters[i] = -pid;
dcb445bc
MS
1595 type = "Filter";
1596 }
b9faaae1 1597 else
dcb445bc 1598 {
b9faaae1 1599 job->backend = -pid;
dcb445bc
MS
1600 type = "Backend";
1601 }
ef416fc2 1602
b9faaae1 1603 if (status && status != SIGTERM && status != SIGKILL &&
f3c17241 1604 status != SIGPIPE)
ef416fc2 1605 {
1606 /*
b9faaae1
MS
1607 * An error occurred; save the exit status so we know to stop
1608 * the printer or cancel the job when all of the filters finish...
1609 *
1610 * A negative status indicates that the backend failed and the
1611 * printer needs to be stopped.
f3c17241
MS
1612 *
1613 * In order to preserve the most serious status, we always log
1614 * when a process dies due to a signal (e.g. SIGABRT, SIGSEGV,
1615 * and SIGBUS) and prefer to log the backend exit status over a
1616 * filter's.
ef416fc2 1617 */
1618
f3c17241
MS
1619 int old_status = abs(job->status);
1620
1621 if (WIFSIGNALED(status) || /* This process crashed, or */
1622 !job->status || /* No process had a status, or */
1623 (!job->filters[i] && WIFEXITED(old_status)))
1624 { /* Backend and filter didn't crash */
1625 if (job->filters[i])
1626 job->status = status; /* Filter failed */
1627 else
1628 job->status = -status; /* Backend failed */
1629 }
ef416fc2 1630
f11a948a 1631 if (job->state_value == IPP_JOB_PROCESSING &&
dcb445bc
MS
1632 job->status_level > CUPSD_LOG_ERROR &&
1633 (job->filters[i] || !WIFEXITED(status)))
ef416fc2 1634 {
f11a948a
MS
1635 char message[1024]; /* New printer-state-message */
1636
1637
b9faaae1 1638 job->status_level = CUPSD_LOG_ERROR;
ef416fc2 1639
dcb445bc 1640 snprintf(message, sizeof(message), "%s failed", type);
f11a948a 1641
e07d4801
MS
1642 if (job->printer)
1643 {
f11a948a
MS
1644 strlcpy(job->printer->state_message, message,
1645 sizeof(job->printer->state_message));
e07d4801
MS
1646 }
1647
1648 if (!job->attrs)
1649 cupsdLoadJob(job);
e00b005a 1650
e07d4801 1651 if (!job->printer_message && job->attrs)
e00b005a 1652 {
b9faaae1
MS
1653 if ((job->printer_message =
1654 ippFindAttribute(job->attrs, "job-printer-state-message",
1655 IPP_TAG_TEXT)) == NULL)
1656 job->printer_message = ippAddString(job->attrs, IPP_TAG_JOB,
1657 IPP_TAG_TEXT,
1658 "job-printer-state-message",
5a662dc0 1659 NULL, NULL);
e00b005a 1660 }
b9faaae1 1661
e07d4801
MS
1662 if (job->printer_message)
1663 cupsdSetString(&(job->printer_message->values[0].string.text),
f11a948a 1664 message);
ef416fc2 1665 }
b9faaae1 1666 }
ef416fc2 1667
b9faaae1
MS
1668 /*
1669 * If this is not the last file in a job, see if all of the
1670 * filters are done, and if so move to the next file.
1671 */
ef416fc2 1672
5a662dc0 1673 if (job->current_file < job->num_files && job->printer)
b9faaae1
MS
1674 {
1675 for (i = 0; job->filters[i] < 0; i ++);
ef416fc2 1676
82f97232
MS
1677 if (!job->filters[i] &&
1678 (!job->printer->pc || !job->printer->pc->single_file ||
1679 job->backend <= 0))
b9faaae1
MS
1680 {
1681 /*
1682 * Process the next file...
1683 */
ef416fc2 1684
b9faaae1 1685 cupsdContinueJob(job);
ef416fc2 1686 }
ef416fc2 1687 }
5a662dc0 1688 else if (job->state_value >= IPP_JOB_CANCELED)
7a0cbd5e
MS
1689 {
1690 /*
1691 * Remove the job from the active list if there are no processes still
1692 * running for it...
1693 */
1694
1695 for (i = 0; job->filters[i] < 0; i++);
1696
1697 if (!job->filters[i] && job->backend <= 0)
1698 cupsArrayRemove(ActiveJobs, job);
1699 }
ef416fc2 1700 }
b9faaae1 1701 }
3dfe78b3
MS
1702
1703 /*
745129be
MS
1704 * Show the exit status as needed, ignoring SIGTERM and SIGKILL errors
1705 * since they come when we kill/end a process...
3dfe78b3
MS
1706 */
1707
745129be
MS
1708 if (status == SIGTERM || status == SIGKILL)
1709 {
dcb445bc
MS
1710 cupsdLogJob(job, CUPSD_LOG_DEBUG,
1711 "PID %d (%s) was terminated normally with signal %d.", pid,
1712 name, status);
745129be 1713 }
5a6b583a
MS
1714 else if (status == SIGPIPE)
1715 {
dcb445bc
MS
1716 cupsdLogJob(job, CUPSD_LOG_DEBUG,
1717 "PID %d (%s) did not catch or ignore signal %d.", pid, name,
1718 status);
5a6b583a 1719 }
745129be 1720 else if (status)
3dfe78b3
MS
1721 {
1722 if (WIFEXITED(status))
88f9aafc
MS
1723 {
1724 int code = WEXITSTATUS(status); /* Exit code */
1725
1726 if (code > 100)
dcb445bc
MS
1727 cupsdLogJob(job, CUPSD_LOG_DEBUG,
1728 "PID %d (%s) stopped with status %d (%s)", pid, name,
1729 code, strerror(code - 100));
88f9aafc 1730 else
dcb445bc
MS
1731 cupsdLogJob(job, CUPSD_LOG_DEBUG,
1732 "PID %d (%s) stopped with status %d.", pid, name, code);
88f9aafc 1733 }
3dfe78b3 1734 else
dcb445bc
MS
1735 cupsdLogJob(job, CUPSD_LOG_DEBUG, "PID %d (%s) crashed on signal %d.",
1736 pid, name, WTERMSIG(status));
3dfe78b3
MS
1737
1738 if (LogLevel < CUPSD_LOG_DEBUG)
dcb445bc
MS
1739 cupsdLogJob(job, CUPSD_LOG_INFO,
1740 "Hint: Try setting the LogLevel to \"debug\" to find out "
1741 "more.");
3dfe78b3
MS
1742 }
1743 else
dcb445bc
MS
1744 cupsdLogJob(job, CUPSD_LOG_DEBUG, "PID %d (%s) exited with no errors.",
1745 pid, name);
ef416fc2 1746 }
dfd5680b
MS
1747
1748 /*
1749 * If wait*() is interrupted by a signal, tell main() to call us again...
1750 */
1751
1752 if (pid < 0 && errno == EINTR)
1753 dead_children = 1;
ef416fc2 1754}
1755
1756
ef416fc2 1757/*
1758 * 'select_timeout()' - Calculate the select timeout value.
1759 *
1760 */
1761
1762static long /* O - Number of seconds */
bd7854cb 1763select_timeout(int fds) /* I - Number of descriptors returned */
ef416fc2 1764{
ef416fc2 1765 long timeout; /* Timeout for select */
1766 time_t now; /* Current time */
1767 cupsd_client_t *con; /* Client information */
ef416fc2 1768 cupsd_job_t *job; /* Job information */
1769 cupsd_subscription_t *sub; /* Subscription information */
1770 const char *why; /* Debugging aid */
1771
1772
82cc1f9a
MS
1773 cupsdLogMessage(CUPSD_LOG_DEBUG2, "select_timeout: JobHistoryUpdate=%ld",
1774 (long)JobHistoryUpdate);
1775
ef416fc2 1776 /*
1777 * Check to see if any of the clients have pending data to be
1778 * processed; if so, the timeout should be 0...
1779 */
1780
996acce8 1781 // TODO: Use httpGetReady()
bd7854cb 1782 for (con = (cupsd_client_t *)cupsArrayFirst(Clients);
1783 con;
1784 con = (cupsd_client_t *)cupsArrayNext(Clients))
996acce8 1785 if (con->http->used > 0)
ef416fc2 1786 return (0);
1787
1788 /*
f7deaa1a 1789 * If select has been active in the last second (fds > 0) or we have
ef416fc2 1790 * many resources in use then don't bother trying to optimize the
1791 * timeout, just make it 1 second.
1792 */
1793
f7deaa1a 1794 if (fds > 0 || cupsArrayCount(Clients) > 50)
ef416fc2 1795 return (1);
1796
1797 /*
1798 * Otherwise, check all of the possible events that we need to wake for...
1799 */
1800
1801 now = time(NULL);
1802 timeout = now + 86400; /* 86400 == 1 day */
1803 why = "do nothing";
1804
e6013cfa
MS
1805#ifdef __APPLE__
1806 /*
1807 * When going to sleep, wake up to cancel jobs that don't complete in time.
1808 */
1809
1810 if (SleepJobs > 0 && SleepJobs < timeout)
1811 {
1812 timeout = SleepJobs;
1813 why = "cancel jobs before sleeping";
1814 }
1815#endif /* __APPLE__ */
1816
76cd9e37
MS
1817 /*
1818 * Check whether we are accepting new connections...
1819 */
1820
1821 if (ListeningPaused > 0 && cupsArrayCount(Clients) < MaxClients &&
1822 ListeningPaused < timeout)
1823 {
1824 if (ListeningPaused <= now)
1825 timeout = now;
1826 else
1827 timeout = ListeningPaused;
1828
1829 why = "resume listening";
1830 }
1831
ef416fc2 1832 /*
1833 * Check the activity and close old clients...
1834 */
1835
996acce8 1836 // TODO: Use httpGetActivity()
bd7854cb 1837 for (con = (cupsd_client_t *)cupsArrayFirst(Clients);
1838 con;
1839 con = (cupsd_client_t *)cupsArrayNext(Clients))
996acce8 1840 if ((con->http->activity + Timeout) < timeout)
ef416fc2 1841 {
996acce8 1842 timeout = con->http->activity + Timeout;
ef416fc2 1843 why = "timeout a client connection";
1844 }
1845
3dfe78b3 1846 /*
bf3816c7 1847 * Write out changes to configuration and state files...
3dfe78b3
MS
1848 */
1849
1850 if (DirtyCleanTime && timeout > DirtyCleanTime)
1851 {
1852 timeout = DirtyCleanTime;
1853 why = "write dirty config/state files";
1854 }
1855
ef416fc2 1856 /*
82cc1f9a 1857 * Check for any job activity...
ef416fc2 1858 */
1859
82cc1f9a
MS
1860 if (JobHistoryUpdate && timeout > JobHistoryUpdate)
1861 {
1862 timeout = JobHistoryUpdate;
1863 why = "update job history";
1864 }
1865
238c3832
MS
1866 for (job = (cupsd_job_t *)cupsArrayFirst(ActiveJobs);
1867 job;
1868 job = (cupsd_job_t *)cupsArrayNext(ActiveJobs))
ef416fc2 1869 {
82cc1f9a
MS
1870 if (job->cancel_time && job->cancel_time < timeout)
1871 {
1872 timeout = job->cancel_time;
1873 why = "cancel stuck jobs";
1874 }
1875
238c3832
MS
1876 if (job->kill_time && job->kill_time < timeout)
1877 {
1878 timeout = job->kill_time;
1879 why = "kill unresponsive jobs";
1880 }
82cc1f9a
MS
1881
1882 if (job->state_value == IPP_JOB_HELD && job->hold_until < timeout)
238c3832
MS
1883 {
1884 timeout = job->hold_until;
1885 why = "release held jobs";
1886 }
82cc1f9a
MS
1887
1888 if (job->state_value == IPP_JOB_PENDING && timeout > (now + 10))
238c3832
MS
1889 {
1890 timeout = now + 10;
1891 why = "start pending jobs";
1892 break;
1893 }
ef416fc2 1894 }
1895
1896#ifdef HAVE_MALLINFO
1897 /*
1898 * Log memory usage every minute...
1899 */
1900
1901 if (LogLevel >= CUPSD_LOG_DEBUG && (mallinfo_time + 60) < timeout)
1902 {
1903 timeout = mallinfo_time + 60;
1904 why = "display memory usage";
1905 }
1906#endif /* HAVE_MALLINFO */
1907
ef416fc2 1908 /*
1909 * Expire subscriptions as needed...
1910 */
1911
1912 for (sub = (cupsd_subscription_t *)cupsArrayFirst(Subscriptions);
1913 sub;
1914 sub = (cupsd_subscription_t *)cupsArrayNext(Subscriptions))
bd7854cb 1915 if (!sub->job && sub->expire && sub->expire < timeout)
ef416fc2 1916 {
1917 timeout = sub->expire;
1918 why = "expire subscription";
1919 }
1920
1921 /*
dcb445bc
MS
1922 * Adjust from absolute to relative time. We add 1 second to the timeout since
1923 * events occur after the timeout expires, and limit the timeout to 86400
1924 * seconds (1 day) to avoid select() timeout limits present on some operating
ef416fc2 1925 * systems...
1926 */
1927
1928 timeout = timeout - now + 1;
1929
1930 if (timeout < 1)
1931 timeout = 1;
1932 else if (timeout > 86400)
1933 timeout = 86400;
1934
1935 /*
1936 * Log and return the timeout value...
1937 */
1938
f7deaa1a 1939 cupsdLogMessage(CUPSD_LOG_DEBUG2, "select_timeout(%d): %ld seconds to %s",
1940 fds, timeout, why);
ef416fc2 1941
1942 return (timeout);
1943}
1944
1945
e6013cfa
MS
1946/*
1947 * 'sigchld_handler()' - Handle 'child' signals from old processes.
1948 */
1949
1950static void
1951sigchld_handler(int sig) /* I - Signal number */
1952{
1953 (void)sig;
1954
1955 /*
1956 * Flag that we have dead children...
1957 */
1958
1959 dead_children = 1;
1960
1961 /*
1962 * Reset the signal handler as needed...
1963 */
1964
1965#if !defined(HAVE_SIGSET) && !defined(HAVE_SIGACTION)
1966 signal(SIGCLD, sigchld_handler);
1967#endif /* !HAVE_SIGSET && !HAVE_SIGACTION */
1968}
1969
1970
1971/*
1972 * 'sighup_handler()' - Handle 'hangup' signals to reconfigure the scheduler.
1973 */
1974
1975static void
1976sighup_handler(int sig) /* I - Signal number */
1977{
1978 (void)sig;
1979
1980 NeedReload = RELOAD_ALL;
1981 ReloadTime = time(NULL);
1982
1983#if !defined(HAVE_SIGSET) && !defined(HAVE_SIGACTION)
1984 signal(SIGHUP, sighup_handler);
1985#endif /* !HAVE_SIGSET && !HAVE_SIGACTION */
1986}
1987
1988
1989/*
1990 * 'sigterm_handler()' - Handle 'terminate' signals that stop the scheduler.
1991 */
1992
1993static void
1994sigterm_handler(int sig) /* I - Signal number */
1995{
1996 (void)sig; /* remove compiler warnings... */
1997
1998 /*
1999 * Flag that we should stop and return...
2000 */
2001
2002 stop_scheduler = 1;
2003}
2004
2005
ef416fc2 2006/*
2007 * 'usage()' - Show scheduler usage.
2008 */
2009
2010static void
a4d04587 2011usage(int status) /* O - Exit status */
ef416fc2 2012{
0837b7e8
MS
2013 FILE *fp = status ? stderr : stdout; /* Output file */
2014
2015
2016 _cupsLangPuts(fp, _("Usage: cupsd [options]"));
2017 _cupsLangPuts(fp, _("Options:"));
f3c17241 2018 _cupsLangPuts(fp, _(" -c cupsd.conf Set cupsd.conf file to use."));
84315f46
MS
2019 _cupsLangPuts(fp, _(" -f Run in the foreground."));
2020 _cupsLangPuts(fp, _(" -F Run in the foreground but "
2021 "detach from console."));
2022 _cupsLangPuts(fp, _(" -h Show this usage message."));
2023 _cupsLangPuts(fp, _(" -l Run cupsd from launchd(8)."));
2024 _cupsLangPuts(fp, _(" -t Test the configuration "
0837b7e8 2025 "file."));
0837b7e8 2026
a4d04587 2027 exit(status);
ef416fc2 2028}
2029
2030
2031/*
f2d18633 2032 * End of "$Id$".
ef416fc2 2033 */