/* * Main loop for the CUPS scheduler. * * Copyright © 2007-2019 by Apple Inc. * Copyright © 1997-2007 by Easy Software Products, all rights reserved. * * Licensed under Apache License v2.0. See the file "LICENSE" for more * information. */ /* * Include necessary headers... */ #define _MAIN_C_ #include "cupsd.h" #include #ifdef __APPLE__ # include # include #endif /* __APPLE__ */ #ifdef HAVE_ASL_H # include #elif defined(HAVE_SYSTEMD_SD_JOURNAL_H) # define SD_JOURNAL_SUPPRESS_LOCATION # include #endif /* HAVE_ASL_H */ #include #include #ifdef HAVE_LAUNCH_H # include #endif /* HAVE_LAUNCH_H */ #ifdef HAVE_SYSTEMD # include #endif /* HAVE_SYSTEMD */ #ifdef HAVE_ONDEMAND # define CUPS_KEEPALIVE CUPS_CACHEDIR "/org.cups.cupsd" /* Name of the KeepAlive file */ #endif /* HAVE_ONDEMAND */ #if defined(HAVE_MALLOC_H) && defined(HAVE_MALLINFO) # include #endif /* HAVE_MALLOC_H && HAVE_MALLINFO */ #ifdef HAVE_NOTIFY_H # include #endif /* HAVE_NOTIFY_H */ #ifdef HAVE_DBUS # include #endif /* HAVE_DBUS */ #ifdef HAVE_SYS_PARAM_H # include #endif /* HAVE_SYS_PARAM_H */ /* * Local functions... */ static void parent_handler(int sig); static void process_children(void); static void sigchld_handler(int sig); static void sighup_handler(int sig); static void sigterm_handler(int sig); static long select_timeout(int fds); static void service_checkin(void); static void service_checkout(int shutdown); static void usage(int status) _CUPS_NORETURN; /* * Local globals... */ static int parent_signal = 0; /* Set to signal number from child */ static int holdcount = 0; /* Number of times "hold" was called */ #if defined(HAVE_SIGACTION) && !defined(HAVE_SIGSET) static sigset_t holdmask; /* Old POSIX signal mask */ #endif /* HAVE_SIGACTION && !HAVE_SIGSET */ static int dead_children = 0; /* Dead children? */ static int stop_scheduler = 0; /* Should the scheduler stop? */ static time_t local_timeout = 0; /* Next local printer timeout */ /* * 'main()' - Main entry for the CUPS scheduler. */ int /* O - Exit status */ main(int argc, /* I - Number of command-line args */ char *argv[]) /* I - Command-line arguments */ { int i; /* Looping var */ char *opt; /* Option character */ int close_all = 1, /* Close all file descriptors? */ disconnect = 1, /* Disconnect from controlling terminal? */ fg = 0, /* Run in foreground? */ run_as_child = 0, /* Running as child process? */ print_profile = 0; /* Print the sandbox profile to stdout? */ int fds; /* Number of ready descriptors */ cupsd_client_t *con; /* Current client */ cupsd_job_t *job; /* Current job */ cupsd_listener_t *lis; /* Current listener */ time_t current_time, /* Current time */ activity, /* Client activity timer */ senddoc_time, /* Send-Document time */ expire_time, /* Subscription expire time */ report_time, /* Malloc/client/job report time */ event_time; /* Last event notification time */ long timeout; /* Timeout for cupsdDoSelect() */ struct rlimit limit; /* Runtime limit */ #if defined(HAVE_SIGACTION) && !defined(HAVE_SIGSET) struct sigaction action; /* Actions for POSIX signals */ #endif /* HAVE_SIGACTION && !HAVE_SIGSET */ #ifdef __APPLE__ int use_sysman = 1; /* Use system management functions? */ #else time_t netif_time = 0; /* Time since last network update */ #endif /* __APPLE__ */ #if defined(HAVE_ONDEMAND) int service_idle_exit = 0; /* Idle exit on select timeout? */ #endif /* HAVE_ONDEMAND */ #ifdef HAVE_GETEUID /* * Check for setuid invocation, which we do not support! */ if (getuid() != geteuid()) { fputs("cupsd: Cannot run as a setuid program.\n", stderr); return (1); } #endif /* HAVE_GETEUID */ /* * Check for command-line arguments... */ fg = 0; for (i = 1; i < argc; i ++) { if (!strcmp(argv[i], "--help")) usage(0); else if (argv[i][0] == '-') { for (opt = argv[i] + 1; *opt != '\0'; opt ++) { switch (*opt) { case 'C' : /* Run as child with config file */ run_as_child = 1; fg = 1; close_all = 0; case 'c' : /* Configuration file */ i ++; if (i >= argc) { _cupsLangPuts(stderr, _("cupsd: Expected config filename " "after \"-c\" option.")); usage(1); } if (argv[i][0] == '/') { /* * Absolute directory... */ cupsdSetString(&ConfigurationFile, argv[i]); } else { /* * Relative directory... */ char *current; /* Current directory */ /* * Allocate a buffer for the current working directory to * reduce run-time stack usage; this approximates the * behavior of some implementations of getcwd() when they * are passed a NULL pointer. */ if ((current = malloc(1024)) == NULL) { _cupsLangPuts(stderr, _("cupsd: Unable to get current directory.")); return (1); } if (!getcwd(current, 1024)) { _cupsLangPuts(stderr, _("cupsd: Unable to get current directory.")); free(current); return (1); } cupsdSetStringf(&ConfigurationFile, "%s/%s", current, argv[i]); free(current); } break; case 'f' : /* Run in foreground... */ fg = 1; disconnect = 0; close_all = 0; break; case 'F' : /* Run in foreground, but disconnect from terminal... */ fg = 1; close_all = 0; break; case 'h' : /* Show usage/help */ usage(0); break; case 'l' : /* Started by launchd/systemd/upstart... */ #ifdef HAVE_ONDEMAND OnDemand = 1; fg = 1; close_all = 0; disconnect = 0; #else _cupsLangPuts(stderr, _("cupsd: On-demand support not compiled " "in, running in normal mode.")); fg = 0; disconnect = 1; close_all = 1; #endif /* HAVE_ONDEMAND */ break; case 'p' : /* Stop immediately for profiling */ fputs("cupsd: -p (startup profiling) is for internal testing " "use only!\n", stderr); stop_scheduler = 1; fg = 1; disconnect = 0; close_all = 0; break; case 'P' : /* Disable security profiles */ fputs("cupsd: -P (disable sandboxing) is for internal testing use only.\n", stderr); UseSandboxing = 0; break; case 's' : /* Set cups-files.conf location */ i ++; if (i >= argc) { _cupsLangPuts(stderr, _("cupsd: Expected cups-files.conf " "filename after \"-s\" option.")); usage(1); } if (argv[i][0] != '/') { /* * Relative filename not allowed... */ _cupsLangPuts(stderr, _("cupsd: Relative cups-files.conf " "filename not allowed.")); usage(1); } cupsdSetString(&CupsFilesFile, argv[i]); break; #ifdef __APPLE__ case 'S' : /* Disable system management functions */ fputs("cupsd: -S (disable system management) for internal " "testing use only!\n", stderr); use_sysman = 0; break; #endif /* __APPLE__ */ case 't' : /* Test the cupsd.conf file... */ TestConfigFile = 1; fg = 1; disconnect = 0; close_all = 0; break; case 'T' : /* Print security profile */ print_profile = 1; fg = 1; disconnect = 0; close_all = 0; break; default : /* Unknown option */ _cupsLangPrintf(stderr, _("cupsd: Unknown option \"%c\" - " "aborting."), *opt); usage(1); break; } } } else { _cupsLangPrintf(stderr, _("cupsd: Unknown argument \"%s\" - aborting."), argv[i]); usage(1); } } if (!ConfigurationFile) cupsdSetString(&ConfigurationFile, CUPS_SERVERROOT "/cupsd.conf"); if (!CupsFilesFile) { char *filename, /* Copy of cupsd.conf filename */ *slash; /* Final slash in cupsd.conf filename */ size_t len; /* Size of buffer */ len = strlen(ConfigurationFile) + 15; if ((filename = malloc(len)) == NULL) { _cupsLangPrintf(stderr, _("cupsd: Unable to get path to " "cups-files.conf file.")); return (1); } strlcpy(filename, ConfigurationFile, len); if ((slash = strrchr(filename, '/')) == NULL) { free(filename); _cupsLangPrintf(stderr, _("cupsd: Unable to get path to " "cups-files.conf file.")); return (1); } strlcpy(slash, "/cups-files.conf", len - (size_t)(slash - filename)); cupsdSetString(&CupsFilesFile, filename); free(filename); } if (disconnect) { /* * Make sure we aren't tying up any filesystems... */ chdir("/"); /* * Disconnect from the controlling terminal... */ setsid(); } if (close_all) { /* * Close all open files... */ getrlimit(RLIMIT_NOFILE, &limit); for (i = 0; i < (int)limit.rlim_cur && i < 1024; i ++) close(i); /* * Redirect stdin/out/err to /dev/null... */ if ((i = open("/dev/null", O_RDONLY)) != 0) { dup2(i, 0); close(i); } if ((i = open("/dev/null", O_WRONLY)) != 1) { dup2(i, 1); close(i); } if ((i = open("/dev/null", O_WRONLY)) != 2) { dup2(i, 2); close(i); } } else LogStderr = cupsFileStderr(); /* * Run in the background as needed... */ if (!fg) { /* * Setup signal handlers for the parent... */ #ifdef HAVE_SIGSET /* Use System V signals over POSIX to avoid bugs */ sigset(SIGUSR1, parent_handler); sigset(SIGCHLD, parent_handler); sigset(SIGHUP, SIG_IGN); #elif defined(HAVE_SIGACTION) memset(&action, 0, sizeof(action)); sigemptyset(&action.sa_mask); sigaddset(&action.sa_mask, SIGUSR1); action.sa_handler = parent_handler; sigaction(SIGUSR1, &action, NULL); sigaction(SIGCHLD, &action, NULL); sigemptyset(&action.sa_mask); action.sa_handler = SIG_IGN; sigaction(SIGHUP, &action, NULL); #else signal(SIGUSR1, parent_handler); signal(SIGCLD, parent_handler); signal(SIGHUP, SIG_IGN); #endif /* HAVE_SIGSET */ if (fork() > 0) { /* * OK, wait for the child to startup and send us SIGUSR1 or to crash * and the OS send us SIGCHLD... We also need to ignore SIGHUP which * might be sent by the init script to restart the scheduler... */ for (; parent_signal == 0;) sleep(1); if (parent_signal == SIGUSR1) return (0); if (wait(&i) < 0) { perror("cupsd"); return (1); } else if (WIFEXITED(i)) { fprintf(stderr, "cupsd: Child exited with status %d\n", WEXITSTATUS(i)); return (2); } else { fprintf(stderr, "cupsd: Child exited on signal %d\n", WTERMSIG(i)); return (3); } } #if defined(__OpenBSD__) && OpenBSD < 201211 /* * Call _thread_sys_closefrom() so the child process doesn't reset the * parent's file descriptors to be blocking. This is a workaround for a * limitation of userland libpthread on older versions of OpenBSD. */ _thread_sys_closefrom(0); #endif /* __OpenBSD__ && OpenBSD < 201211 */ /* * Since many system libraries create fork-unsafe data on execution of a * program, we need to re-execute the background cupsd with the "-C" and "-s" * options to avoid problems. Unfortunately, we also have to assume that * argv[0] contains the name of the cupsd executable - there is no portable * way to get the real pathname... */ execlp(argv[0], argv[0], "-C", ConfigurationFile, "-s", CupsFilesFile, (char *)0); exit(errno); } /* * Let the system know we are busy while we bring up cupsd... */ cupsdSetBusyState(1); /* * Set the timezone info... */ tzset(); #ifdef LC_TIME setlocale(LC_TIME, ""); #endif /* LC_TIME */ #ifdef HAVE_DBUS_THREADS_INIT /* * Enable threading support for D-BUS... */ dbus_threads_init_default(); #endif /* HAVE_DBUS_THREADS_INIT */ /* * Set the maximum number of files... */ getrlimit(RLIMIT_NOFILE, &limit); #if !defined(HAVE_POLL) && !defined(HAVE_EPOLL) && !defined(HAVE_KQUEUE) if (limit.rlim_max > FD_SETSIZE) MaxFDs = FD_SETSIZE; else #endif /* !HAVE_POLL && !HAVE_EPOLL && !HAVE_KQUEUE */ #ifdef RLIM_INFINITY if (limit.rlim_max == RLIM_INFINITY) MaxFDs = 16384; else #endif /* RLIM_INFINITY */ MaxFDs = limit.rlim_max; limit.rlim_cur = (rlim_t)MaxFDs; setrlimit(RLIMIT_NOFILE, &limit); cupsdStartSelect(); /* * Read configuration... */ if (!cupsdReadConfiguration()) return (1); else if (TestConfigFile) { printf("\"%s\" is OK.\n", CupsFilesFile); printf("\"%s\" is OK.\n", ConfigurationFile); return (0); } else if (print_profile) { cups_file_t *fp; /* File pointer */ const char *profile = cupsdCreateProfile(42, 0); /* Profile */ char line[1024]; /* Line from file */ if ((fp = cupsFileOpen(profile, "r")) == NULL) { printf("Unable to open profile file \"%s\": %s\n", profile ? profile : "(null)", strerror(errno)); return (1); } while (cupsFileGets(fp, line, sizeof(line))) puts(line); cupsFileClose(fp); return (0); } /* * Clean out old temp files and printer cache data. */ if (!strncmp(TempDir, RequestRoot, strlen(RequestRoot))) cupsdCleanFiles(TempDir, NULL); cupsdCleanFiles(CacheDir, "*.ipp"); /* * If we were started on demand by launchd or systemd get the listen sockets * file descriptors... */ service_checkin(); service_checkout(0); /* * Startup the server... */ httpInitialize(); cupsdStartServer(); /* * Catch hangup and child signals and ignore broken pipes... */ #ifdef HAVE_SIGSET /* Use System V signals over POSIX to avoid bugs */ sigset(SIGCHLD, sigchld_handler); sigset(SIGHUP, sighup_handler); sigset(SIGPIPE, SIG_IGN); sigset(SIGTERM, sigterm_handler); #elif defined(HAVE_SIGACTION) memset(&action, 0, sizeof(action)); sigemptyset(&action.sa_mask); sigaddset(&action.sa_mask, SIGTERM); sigaddset(&action.sa_mask, SIGCHLD); action.sa_handler = sigchld_handler; sigaction(SIGCHLD, &action, NULL); sigemptyset(&action.sa_mask); sigaddset(&action.sa_mask, SIGHUP); action.sa_handler = sighup_handler; sigaction(SIGHUP, &action, NULL); sigemptyset(&action.sa_mask); action.sa_handler = SIG_IGN; sigaction(SIGPIPE, &action, NULL); sigemptyset(&action.sa_mask); sigaddset(&action.sa_mask, SIGTERM); sigaddset(&action.sa_mask, SIGCHLD); action.sa_handler = sigterm_handler; sigaction(SIGTERM, &action, NULL); #else signal(SIGCLD, sigchld_handler); /* No, SIGCLD isn't a typo... */ signal(SIGHUP, sighup_handler); signal(SIGPIPE, SIG_IGN); signal(SIGTERM, sigterm_handler); #endif /* HAVE_SIGSET */ /* * Initialize authentication certificates... */ cupsdInitCerts(); /* * If we are running in the background, signal the parent process that * we are up and running... */ if (!fg || run_as_child) { /* * Send a signal to the parent process, but only if the parent is * not PID 1 (init). This avoids accidentally shutting down the * system on OpenBSD if you CTRL-C the server before it is up... */ i = getppid(); /* Save parent PID to avoid race condition */ if (i != 1) kill(i, SIGUSR1); } #ifdef __APPLE__ /* * Start power management framework... */ if (use_sysman) cupsdStartSystemMonitor(); #endif /* __APPLE__ */ /* * Send server-started event... */ #ifdef HAVE_ONDEMAND if (OnDemand) cupsdAddEvent(CUPSD_EVENT_SERVER_STARTED, NULL, NULL, "Scheduler started on demand."); else #endif /* HAVE_ONDEMAND */ if (fg) cupsdAddEvent(CUPSD_EVENT_SERVER_STARTED, NULL, NULL, "Scheduler started in foreground."); else cupsdAddEvent(CUPSD_EVENT_SERVER_STARTED, NULL, NULL, "Scheduler started in background."); cupsdSetBusyState(0); /* * Start any pending print jobs... */ cupsdCheckJobs(); /* * Loop forever... */ current_time = time(NULL); event_time = current_time; expire_time = current_time; local_timeout = 0; fds = 1; report_time = 0; senddoc_time = current_time; while (!stop_scheduler) { /* * Check if there are dead children to handle... */ if (dead_children) process_children(); /* * Check if we need to load the server configuration file... */ if (NeedReload) { /* * Close any idle clients... */ if (cupsArrayCount(Clients) > 0) { for (con = (cupsd_client_t *)cupsArrayFirst(Clients); con; con = (cupsd_client_t *)cupsArrayNext(Clients)) if (httpGetState(con->http) == HTTP_WAITING) cupsdCloseClient(con); else con->http->keep_alive = HTTP_KEEPALIVE_OFF; cupsdPauseListening(); } /* * Restart if all clients are closed and all jobs finished, or * if the reload timeout has elapsed... */ if ((cupsArrayCount(Clients) == 0 && (cupsArrayCount(PrintingJobs) == 0 || NeedReload != RELOAD_ALL)) || (time(NULL) - ReloadTime) >= ReloadTimeout) { /* * Shutdown the server... */ #ifdef HAVE_ONDEMAND if (OnDemand) { # ifndef HAVE_SYSTEMD /* Issue #5640: systemd doesn't actually support launch-on-demand services, need to fake it */ stop_scheduler = 1; # endif /* HAVE_SYSTEMD */ break; } #endif /* HAVE_ONDEMAND */ DoingShutdown = 1; cupsdStopServer(); /* * Read configuration... */ if (!cupsdReadConfiguration()) { #ifdef HAVE_SYSTEMD_SD_JOURNAL_H sd_journal_print(LOG_ERR, "Unable to read configuration file \"%s\" - exiting.", ConfigurationFile); #else syslog(LOG_LPR, "Unable to read configuration file \'%s\' - exiting.", ConfigurationFile); #endif /* HAVE_SYSTEMD_SD_JOURNAL_H */ break; } /* * Startup the server... */ DoingShutdown = 0; cupsdStartServer(); /* * Send a server-restarted event... */ cupsdAddEvent(CUPSD_EVENT_SERVER_RESTARTED, NULL, NULL, "Scheduler restarted."); } } /* * Check for available input or ready output. If cupsdDoSelect() * returns 0 or -1, something bad happened and we should exit * immediately. * * Note that we at least have one listening socket open at all * times. */ if ((timeout = select_timeout(fds)) > 1 && LastEvent) timeout = 1; #ifdef HAVE_ONDEMAND /* * If no other work is scheduled and we're being controlled by launchd, * systemd, etc. then timeout after 'IdleExitTimeout' seconds of * inactivity... */ if (timeout == 86400 && OnDemand && IdleExitTimeout && # ifdef HAVE_SYSTEMD !WebInterface && # endif /* HAVE_SYSTEMD */ !cupsArrayCount(ActiveJobs)) { cupsd_printer_t *p = NULL; /* Current printer */ if (Browsing && BrowseLocalProtocols) { for (p = (cupsd_printer_t *)cupsArrayFirst(Printers); p; p = (cupsd_printer_t *)cupsArrayNext(Printers)) if (p->shared) break; } if (!p) { timeout = IdleExitTimeout; service_idle_exit = 1; } } else service_idle_exit = 0; #endif /* HAVE_ONDEMAND */ if ((fds = cupsdDoSelect(timeout)) < 0) { /* * Got an error from select! */ #if defined(HAVE_DNSSD) || defined(HAVE_AVAHI) cupsd_printer_t *p; /* Current printer */ #endif /* HAVE_DNSSD || HAVE_AVAHI */ if (errno == EINTR) /* Just interrupted by a signal */ continue; /* * Log all sorts of debug info to help track down the problem. */ cupsdLogMessage(CUPSD_LOG_EMERG, "cupsdDoSelect() failed - %s!", strerror(errno)); for (i = 0, con = (cupsd_client_t *)cupsArrayFirst(Clients); con; i ++, con = (cupsd_client_t *)cupsArrayNext(Clients)) cupsdLogMessage(CUPSD_LOG_EMERG, "Clients[%d] = %d, file = %d, state = %d", i, con->number, con->file, httpGetState(con->http)); for (i = 0, lis = (cupsd_listener_t *)cupsArrayFirst(Listeners); lis; i ++, lis = (cupsd_listener_t *)cupsArrayNext(Listeners)) cupsdLogMessage(CUPSD_LOG_EMERG, "Listeners[%d] = %d", i, lis->fd); cupsdLogMessage(CUPSD_LOG_EMERG, "CGIPipes[0] = %d", CGIPipes[0]); #ifdef __APPLE__ cupsdLogMessage(CUPSD_LOG_EMERG, "SysEventPipes[0] = %d", SysEventPipes[0]); #endif /* __APPLE__ */ for (job = (cupsd_job_t *)cupsArrayFirst(ActiveJobs); job; job = (cupsd_job_t *)cupsArrayNext(ActiveJobs)) cupsdLogMessage(CUPSD_LOG_EMERG, "Jobs[%d] = %d < [%d %d] > [%d %d]", job->id, job->status_buffer ? job->status_buffer->fd : -1, job->print_pipes[0], job->print_pipes[1], job->back_pipes[0], job->back_pipes[1]); #if defined(HAVE_DNSSD) || defined(HAVE_AVAHI) for (p = (cupsd_printer_t *)cupsArrayFirst(Printers); p; p = (cupsd_printer_t *)cupsArrayNext(Printers)) cupsdLogMessage(CUPSD_LOG_EMERG, "printer[%s] reg_name=\"%s\"", p->name, p->reg_name ? p->reg_name : "(null)"); #endif /* HAVE_DNSSD || HAVE_AVAHI */ break; } current_time = time(NULL); /* * Write dirty config/state files... */ if (DirtyCleanTime && current_time >= DirtyCleanTime) cupsdCleanDirty(); #ifdef __APPLE__ /* * If we are going to sleep and still have pending jobs, stop them after * a period of time... */ if (SleepJobs > 0 && current_time >= SleepJobs && cupsArrayCount(PrintingJobs) > 0) { SleepJobs = 0; cupsdStopAllJobs(CUPSD_JOB_DEFAULT, 5); } #endif /* __APPLE__ */ #ifndef __APPLE__ /* * Update the network interfaces once a minute... */ if ((current_time - netif_time) >= 60) { netif_time = current_time; NetIFUpdate = 1; } #endif /* !__APPLE__ */ #ifdef HAVE_ONDEMAND /* * If no other work was scheduled and we're being controlled by launchd, * systemd, or upstart then timeout after 'LaunchdTimeout' seconds of * inactivity... */ if (!fds && service_idle_exit) { cupsdLogMessage(CUPSD_LOG_INFO, "Printer sharing is off and there are no jobs pending, " "will restart on demand."); stop_scheduler = 1; break; } #endif /* HAVE_ONDEMAND */ /* * Resume listening for new connections as needed... */ if (ListeningPaused && ListeningPaused <= current_time && cupsArrayCount(Clients) < MaxClients) cupsdResumeListening(); /* * Expire subscriptions and unload completed jobs as needed... */ if (current_time > expire_time) { cupsdExpireSubscriptions(NULL, NULL); cupsdUnloadCompletedJobs(); expire_time = current_time; } /* * Delete stale local printers... */ if (current_time >= local_timeout) { cupsdDeleteTemporaryPrinters(0); local_timeout = 0; } #ifndef HAVE_AUTHORIZATION_H /* * Update the root certificate once every 5 minutes if we have client * connections... */ if ((current_time - RootCertTime) >= RootCertDuration && RootCertDuration && !RunUser && cupsArrayCount(Clients)) { /* * Update the root certificate... */ cupsdDeleteCert(0); cupsdAddCert(0, "root", cupsdDefaultAuthType()); } #endif /* !HAVE_AUTHORIZATION_H */ /* * Clean job history... */ if (JobHistoryUpdate && current_time >= JobHistoryUpdate) cupsdCleanJobs(); /* * Update any pending multi-file documents... */ if ((current_time - senddoc_time) >= 10) { cupsdCheckJobs(); senddoc_time = current_time; } /* * Check for new data on the client sockets... */ for (con = (cupsd_client_t *)cupsArrayFirst(Clients); con; con = (cupsd_client_t *)cupsArrayNext(Clients)) { /* * Process pending data in the input buffer... */ if (httpGetReady(con->http)) { cupsdReadClient(con); continue; } /* * Check the activity and close old clients... */ activity = current_time - Timeout; if (httpGetActivity(con->http) < activity && !con->pipe_pid) { cupsdLogMessage(CUPSD_LOG_DEBUG, "Closing client %d after %d seconds of inactivity.", con->number, Timeout); cupsdCloseClient(con); continue; } } /* * Log statistics at most once a minute when in debug mode... */ if ((current_time - report_time) >= 60 && LogLevel >= CUPSD_LOG_DEBUG) { size_t string_count, /* String count */ alloc_bytes, /* Allocated string bytes */ total_bytes; /* Total string bytes */ #ifdef HAVE_MALLINFO struct mallinfo mem; /* Malloc information */ mem = mallinfo(); cupsdLogMessage(CUPSD_LOG_DEBUG, "Report: malloc-arena=%lu", mem.arena); cupsdLogMessage(CUPSD_LOG_DEBUG, "Report: malloc-used=%lu", mem.usmblks + mem.uordblks); cupsdLogMessage(CUPSD_LOG_DEBUG, "Report: malloc-free=%lu", mem.fsmblks + mem.fordblks); #endif /* HAVE_MALLINFO */ cupsdLogMessage(CUPSD_LOG_DEBUG, "Report: clients=%d", cupsArrayCount(Clients)); cupsdLogMessage(CUPSD_LOG_DEBUG, "Report: jobs=%d", cupsArrayCount(Jobs)); cupsdLogMessage(CUPSD_LOG_DEBUG, "Report: jobs-active=%d", cupsArrayCount(ActiveJobs)); cupsdLogMessage(CUPSD_LOG_DEBUG, "Report: printers=%d", cupsArrayCount(Printers)); string_count = _cupsStrStatistics(&alloc_bytes, &total_bytes); cupsdLogMessage(CUPSD_LOG_DEBUG, "Report: stringpool-string-count=" CUPS_LLFMT, CUPS_LLCAST string_count); cupsdLogMessage(CUPSD_LOG_DEBUG, "Report: stringpool-alloc-bytes=" CUPS_LLFMT, CUPS_LLCAST alloc_bytes); cupsdLogMessage(CUPSD_LOG_DEBUG, "Report: stringpool-total-bytes=" CUPS_LLFMT, CUPS_LLCAST total_bytes); report_time = current_time; } /* * Handle OS-specific event notification for any events that have * accumulated. Don't send these more than once a second... */ if (LastEvent && (current_time - event_time) >= 1) { #ifdef HAVE_NOTIFY_POST if (LastEvent & (CUPSD_EVENT_PRINTER_ADDED | CUPSD_EVENT_PRINTER_DELETED | CUPSD_EVENT_PRINTER_MODIFIED)) { cupsdLogMessage(CUPSD_LOG_DEBUG2, "notify_post(\"com.apple.printerListChange\")"); notify_post("com.apple.printerListChange"); } if (LastEvent & CUPSD_EVENT_PRINTER_STATE_CHANGED) { cupsdLogMessage(CUPSD_LOG_DEBUG2, "notify_post(\"com.apple.printerHistoryChange\")"); notify_post("com.apple.printerHistoryChange"); } if (LastEvent & (CUPSD_EVENT_JOB_STATE_CHANGED | CUPSD_EVENT_JOB_CONFIG_CHANGED | CUPSD_EVENT_JOB_PROGRESS)) { cupsdLogMessage(CUPSD_LOG_DEBUG2, "notify_post(\"com.apple.jobChange\")"); notify_post("com.apple.jobChange"); } #endif /* HAVE_NOTIFY_POST */ /* * Reset the accumulated events... */ LastEvent = CUPSD_EVENT_NONE; event_time = current_time; } } /* * Log a message based on what happened... */ if (stop_scheduler) { cupsdLogMessage(CUPSD_LOG_INFO, "Scheduler shutting down normally."); cupsdAddEvent(CUPSD_EVENT_SERVER_STOPPED, NULL, NULL, "Scheduler shutting down normally."); } else { cupsdLogMessage(CUPSD_LOG_ERROR, "Scheduler shutting down due to program error."); cupsdAddEvent(CUPSD_EVENT_SERVER_STOPPED, NULL, NULL, "Scheduler shutting down due to program error."); } /* * Close all network clients... */ DoingShutdown = 1; cupsdStopServer(); /* * Update the KeepAlive/PID file as needed... */ service_checkout(1); /* * Stop all jobs... */ cupsdFreeAllJobs(); /* * Delete all temporary printers... */ cupsdDeleteTemporaryPrinters(1); #ifdef __APPLE__ /* * Stop monitoring system event monitoring... */ if (use_sysman) cupsdStopSystemMonitor(); #endif /* __APPLE__ */ cupsdStopSelect(); return (!stop_scheduler); } /* * 'cupsdAddString()' - Copy and add a string to an array. */ int /* O - 1 on success, 0 on failure */ cupsdAddString(cups_array_t **a, /* IO - String array */ const char *s) /* I - String to copy and add */ { if (!*a) *a = cupsArrayNew3((cups_array_func_t)strcmp, NULL, (cups_ahash_func_t)NULL, 0, (cups_acopy_func_t)strdup, (cups_afree_func_t)free); return (cupsArrayAdd(*a, (char *)s)); } /* * 'cupsdCheckProcess()' - Tell the main loop to check for dead children. */ void cupsdCheckProcess(void) { /* * Flag that we have dead children... */ dead_children = 1; } /* * 'cupsdClearString()' - Clear a string. */ void cupsdClearString(char **s) /* O - String value */ { if (s && *s) { free(*s); *s = NULL; } } /* * 'cupsdFreeStrings()' - Free an array of strings. */ void cupsdFreeStrings(cups_array_t **a) /* IO - String array */ { if (*a) { cupsArrayDelete(*a); *a = NULL; } } /* * 'cupsdHoldSignals()' - Hold child and termination signals. */ void cupsdHoldSignals(void) { #if defined(HAVE_SIGACTION) && !defined(HAVE_SIGSET) sigset_t newmask; /* New POSIX signal mask */ #endif /* HAVE_SIGACTION && !HAVE_SIGSET */ holdcount ++; if (holdcount > 1) return; #ifdef HAVE_SIGSET sighold(SIGTERM); sighold(SIGCHLD); #elif defined(HAVE_SIGACTION) sigemptyset(&newmask); sigaddset(&newmask, SIGTERM); sigaddset(&newmask, SIGCHLD); sigprocmask(SIG_BLOCK, &newmask, &holdmask); #endif /* HAVE_SIGSET */ } /* * 'cupsdReleaseSignals()' - Release signals for delivery. */ void cupsdReleaseSignals(void) { holdcount --; if (holdcount > 0) return; #ifdef HAVE_SIGSET sigrelse(SIGTERM); sigrelse(SIGCHLD); #elif defined(HAVE_SIGACTION) sigprocmask(SIG_SETMASK, &holdmask, NULL); #endif /* HAVE_SIGSET */ } /* * 'cupsdSetString()' - Set a string value. */ void cupsdSetString(char **s, /* O - New string */ const char *v) /* I - String value */ { if (!s || *s == v) return; if (*s) free(*s); if (v) *s = strdup(v); else *s = NULL; } /* * 'cupsdSetStringf()' - Set a formatted string value. */ void cupsdSetStringf(char **s, /* O - New string */ const char *f, /* I - Printf-style format string */ ...) /* I - Additional args as needed */ { char v[65536 + 64]; /* Formatting string value */ va_list ap; /* Argument pointer */ char *olds; /* Old string */ if (!s) return; olds = *s; if (f) { va_start(ap, f); vsnprintf(v, sizeof(v), f, ap); va_end(ap); *s = strdup(v); } else *s = NULL; if (olds) free(olds); } /* * 'parent_handler()' - Catch USR1/CHLD signals... */ static void parent_handler(int sig) /* I - Signal */ { /* * Store the signal we got from the OS and return... */ parent_signal = sig; } /* * 'process_children()' - Process all dead children... */ static void process_children(void) { int status; /* Exit status of child */ int pid, /* Process ID of child */ job_id; /* Job ID of child */ cupsd_job_t *job; /* Current job */ int i; /* Looping var */ char name[1024]; /* Process name */ const char *type; /* Type of program */ cupsdLogMessage(CUPSD_LOG_DEBUG2, "process_children()"); /* * Reset the dead_children flag... */ dead_children = 0; /* * Collect the exit status of some children... */ #ifdef HAVE_WAITPID while ((pid = waitpid(-1, &status, WNOHANG)) > 0) #elif defined(HAVE_WAIT3) while ((pid = wait3(&status, WNOHANG, NULL)) > 0) #else if ((pid = wait(&status)) > 0) #endif /* HAVE_WAITPID */ { /* * Collect the name of the process that finished... */ cupsdFinishProcess(pid, name, sizeof(name), &job_id); /* * Delete certificates for CGI processes... */ if (pid) cupsdDeleteCert(pid); /* * Handle completed job filters... */ if (job_id > 0) job = cupsdFindJob(job_id); else job = NULL; if (job) { for (i = 0; job->filters[i]; i ++) if (job->filters[i] == pid) break; if (job->filters[i] || job->backend == pid) { /* * OK, this process has gone away; what's left? */ if (job->filters[i]) { job->filters[i] = -pid; type = "Filter"; } else { job->backend = -pid; type = "Backend"; } if (status && status != SIGTERM && status != SIGKILL && status != SIGPIPE) { /* * An error occurred; save the exit status so we know to stop * the printer or cancel the job when all of the filters finish... * * A negative status indicates that the backend failed and the * printer needs to be stopped. * * In order to preserve the most serious status, we always log * when a process dies due to a signal (e.g. SIGABRT, SIGSEGV, * and SIGBUS) and prefer to log the backend exit status over a * filter's. */ int old_status = abs(job->status); if (WIFSIGNALED(status) || /* This process crashed, or */ !job->status || /* No process had a status, or */ (!job->filters[i] && WIFEXITED(old_status))) { /* Backend and filter didn't crash */ if (job->filters[i]) { job->status = status; /* Filter failed */ } else { job->status = -status; /* Backend failed */ if (job->current_file < job->num_files) cupsdSetJobState(job, IPP_JOB_ABORTED, CUPSD_JOB_FORCE, "Canceling multi-file job due to backend failure."); } } if (job->state_value == IPP_JOB_PROCESSING && job->status_level > CUPSD_LOG_ERROR && (job->filters[i] || !WIFEXITED(status))) { char message[1024]; /* New printer-state-message */ job->status_level = CUPSD_LOG_ERROR; snprintf(message, sizeof(message), "%s failed", type); if (job->printer) { strlcpy(job->printer->state_message, message, sizeof(job->printer->state_message)); } if (!job->attrs) cupsdLoadJob(job); if (!job->printer_message && job->attrs) { if ((job->printer_message = ippFindAttribute(job->attrs, "job-printer-state-message", IPP_TAG_TEXT)) == NULL) job->printer_message = ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_TEXT, "job-printer-state-message", NULL, NULL); } if (job->printer_message) ippSetString(job->attrs, &job->printer_message, 0, message); } } /* * If this is not the last file in a job, see if all of the * filters are done, and if so move to the next file. */ if (job->state_value >= IPP_JOB_CANCELED) { /* * Remove the job from the active list if there are no processes still * running for it... */ for (i = 0; job->filters[i] < 0; i++); if (!job->filters[i] && job->backend <= 0) cupsArrayRemove(ActiveJobs, job); } else if (job->current_file < job->num_files && job->printer) { for (i = 0; job->filters[i] < 0; i ++); if (!job->filters[i] && (!job->printer->pc || !job->printer->pc->single_file || job->backend <= 0)) { /* * Process the next file... */ cupsdContinueJob(job); } } } } /* * Show the exit status as needed, ignoring SIGTERM and SIGKILL errors * since they come when we kill/end a process... */ if (status == SIGTERM || status == SIGKILL) { cupsdLogJob(job, CUPSD_LOG_DEBUG, "PID %d (%s) was terminated normally with signal %d.", pid, name, status); } else if (status == SIGPIPE) { cupsdLogJob(job, CUPSD_LOG_DEBUG, "PID %d (%s) did not catch or ignore signal %d.", pid, name, status); } else if (status) { if (WIFEXITED(status)) { int code = WEXITSTATUS(status); /* Exit code */ if (code > 100) cupsdLogJob(job, CUPSD_LOG_DEBUG, "PID %d (%s) stopped with status %d (%s)", pid, name, code, strerror(code - 100)); else cupsdLogJob(job, CUPSD_LOG_DEBUG, "PID %d (%s) stopped with status %d.", pid, name, code); } else cupsdLogJob(job, CUPSD_LOG_DEBUG, "PID %d (%s) crashed on signal %d.", pid, name, WTERMSIG(status)); if (LogLevel < CUPSD_LOG_DEBUG) cupsdLogJob(job, CUPSD_LOG_INFO, "Hint: Try setting the LogLevel to \"debug\" to find out " "more."); } else cupsdLogJob(job, CUPSD_LOG_DEBUG, "PID %d (%s) exited with no errors.", pid, name); } /* * If wait*() is interrupted by a signal, tell main() to call us again... */ if (pid < 0 && errno == EINTR) dead_children = 1; } /* * 'select_timeout()' - Calculate the select timeout value. * */ static long /* O - Number of seconds */ select_timeout(int fds) /* I - Number of descriptors returned */ { long timeout; /* Timeout for select */ time_t now; /* Current time */ cupsd_client_t *con; /* Client information */ cupsd_job_t *job; /* Job information */ const char *why; /* Debugging aid */ cupsdLogMessage(CUPSD_LOG_DEBUG2, "select_timeout: JobHistoryUpdate=%ld", (long)JobHistoryUpdate); /* * Check to see if any of the clients have pending data to be * processed; if so, the timeout should be 0... */ for (con = (cupsd_client_t *)cupsArrayFirst(Clients); con; con = (cupsd_client_t *)cupsArrayNext(Clients)) if (httpGetReady(con->http)) return (0); /* * If select has been active in the last second (fds > 0) or we have * many resources in use then don't bother trying to optimize the * timeout, just make it 1 second. */ if (fds > 0 || cupsArrayCount(Clients) > 50) return (1); /* * Otherwise, check all of the possible events that we need to wake for... */ now = time(NULL); timeout = now + 86400; /* 86400 == 1 day */ why = "do nothing"; #ifdef __APPLE__ /* * When going to sleep, wake up to abort jobs that don't complete in time. */ if (SleepJobs > 0 && SleepJobs < timeout) { timeout = SleepJobs; why = "abort jobs before sleeping"; } #endif /* __APPLE__ */ /* * Check whether we are accepting new connections... */ if (ListeningPaused > 0 && cupsArrayCount(Clients) < MaxClients && ListeningPaused < timeout) { if (ListeningPaused <= now) timeout = now; else timeout = ListeningPaused; why = "resume listening"; } /* * Check the activity and close old clients... */ for (con = (cupsd_client_t *)cupsArrayFirst(Clients); con; con = (cupsd_client_t *)cupsArrayNext(Clients)) if ((httpGetActivity(con->http) + Timeout) < timeout) { timeout = httpGetActivity(con->http) + Timeout; why = "timeout a client connection"; } /* * Write out changes to configuration and state files... */ if (DirtyCleanTime && timeout > DirtyCleanTime) { timeout = DirtyCleanTime; why = "write dirty config/state files"; } /* * Check for any job activity... */ for (job = (cupsd_job_t *)cupsArrayFirst(ActiveJobs); job; job = (cupsd_job_t *)cupsArrayNext(ActiveJobs)) { if (job->cancel_time && job->cancel_time < timeout) { timeout = job->cancel_time; why = "cancel stuck jobs"; } if (job->kill_time && job->kill_time < timeout) { timeout = job->kill_time; why = "kill unresponsive jobs"; } if (job->state_value == IPP_JOB_HELD && job->hold_until < timeout) { timeout = job->hold_until; why = "release held jobs"; } if (job->state_value == IPP_JOB_PENDING && timeout > (now + 10)) { timeout = now + 10; why = "start pending jobs"; break; } } /* * Adjust from absolute to relative time. We add 1 second to the timeout since * events occur after the timeout expires, and limit the timeout to 86400 * seconds (1 day) to avoid select() timeout limits present on some operating * systems... */ timeout = timeout - now + 1; if (timeout < 1) timeout = 1; else if (timeout > 86400) timeout = 86400; /* * Log and return the timeout value... */ cupsdLogMessage(CUPSD_LOG_DEBUG2, "select_timeout(%d): %ld seconds to %s", fds, timeout, why); return (timeout); } /* * 'sigchld_handler()' - Handle 'child' signals from old processes. */ static void sigchld_handler(int sig) /* I - Signal number */ { (void)sig; /* * Flag that we have dead children... */ dead_children = 1; /* * Reset the signal handler as needed... */ #if !defined(HAVE_SIGSET) && !defined(HAVE_SIGACTION) signal(SIGCLD, sigchld_handler); #endif /* !HAVE_SIGSET && !HAVE_SIGACTION */ } /* * 'sighup_handler()' - Handle 'hangup' signals to reconfigure the scheduler. */ static void sighup_handler(int sig) /* I - Signal number */ { (void)sig; NeedReload = RELOAD_ALL; ReloadTime = time(NULL); #if !defined(HAVE_SIGSET) && !defined(HAVE_SIGACTION) signal(SIGHUP, sighup_handler); #endif /* !HAVE_SIGSET && !HAVE_SIGACTION */ } /* * 'sigterm_handler()' - Handle 'terminate' signals that stop the scheduler. */ static void sigterm_handler(int sig) /* I - Signal number */ { (void)sig; /* remove compiler warnings... */ /* * Flag that we should stop and return... */ stop_scheduler = 1; } #ifdef HAVE_ONDEMAND /* * 'service_add_listener()' - Bind an open fd as a Listener. */ static void service_add_listener(int fd, /* I - Socket file descriptor */ int idx) /* I - Listener number, for logging */ { cupsd_listener_t *lis; /* Listeners array */ http_addr_t addr; /* Address variable */ socklen_t addrlen; /* Length of address */ char s[256]; /* String addresss */ addrlen = sizeof(addr); if (getsockname(fd, (struct sockaddr *)&addr, &addrlen)) { cupsdLogMessage(CUPSD_LOG_ERROR, "service_add_listener: Unable to get local address for listener #%d: %s", idx + 1, strerror(errno)); return; } cupsdLogMessage(CUPSD_LOG_DEBUG, "service_add_listener: Listener #%d at fd %d, \"%s\".", idx + 1, fd, httpAddrString(&addr, s, sizeof(s))); /* * Try to match the on-demand socket address to one of the listeners... */ for (lis = (cupsd_listener_t *)cupsArrayFirst(Listeners); lis; lis = (cupsd_listener_t *)cupsArrayNext(Listeners)) if (httpAddrEqual(&lis->address, &addr)) break; /* * Add a new listener If there's no match... */ if (lis) { cupsdLogMessage(CUPSD_LOG_DEBUG, "service_add_listener: Matched existing listener #%d to %s.", idx + 1, httpAddrString(&(lis->address), s, sizeof(s))); } else { cupsdLogMessage(CUPSD_LOG_DEBUG, "service_add_listener: Adding new listener #%d for %s.", idx + 1, httpAddrString(&addr, s, sizeof(s))); if ((lis = calloc(1, sizeof(cupsd_listener_t))) == NULL) { cupsdLogMessage(CUPSD_LOG_ERROR, "service_add_listener: Unable to allocate listener: %s.", strerror(errno)); exit(EXIT_FAILURE); return; } cupsArrayAdd(Listeners, lis); memcpy(&lis->address, &addr, sizeof(lis->address)); } lis->fd = fd; lis->on_demand = 1; # ifdef HAVE_SSL if (httpAddrPort(&(lis->address)) == 443) lis->encryption = HTTP_ENCRYPT_ALWAYS; # endif /* HAVE_SSL */ } #endif /* HAVE_ONDEMAND */ /* * 'service_checkin()' - Check-in with launchd and collect the listening fds. */ static void service_checkin(void) { cupsdLogMessage(CUPSD_LOG_DEBUG, "service_checkin: pid=%d", (int)getpid()); #ifdef HAVE_LAUNCHD if (OnDemand) { int error; /* Check-in error, if any */ size_t i, /* Looping var */ count; /* Number of listeners */ int *ld_sockets; /* Listener sockets */ # ifdef __APPLE__ /* * Force "user initiated" priority for the main thread... */ pthread_set_qos_class_self_np(QOS_CLASS_USER_INITIATED, 0); # endif /* __APPLE__ */ /* * Check-in with launchd... */ if ((error = launch_activate_socket("Listeners", &ld_sockets, &count)) != 0) { cupsdLogMessage(CUPSD_LOG_ERROR, "service_checkin: Unable to get listener sockets: %s", strerror(error)); exit(EXIT_FAILURE); return; /* anti-compiler-warning */ } /* * Try to match the launchd sockets to the cupsd listeners... */ cupsdLogMessage(CUPSD_LOG_DEBUG, "service_checkin: %d listeners.", (int)count); for (i = 0; i < count; i ++) service_add_listener(ld_sockets[i], (int)i); free(ld_sockets); # ifdef __APPLE__ xpc_transaction_begin(); # endif /* __APPLE__ */ } #elif defined(HAVE_SYSTEMD) if (OnDemand) { int i, /* Looping var */ count; /* Number of listeners */ /* * Check-in with systemd... */ if ((count = sd_listen_fds(0)) < 0) { cupsdLogMessage(CUPSD_LOG_ERROR, "service_checkin: Unable to get listener sockets: %s", strerror(-count)); exit(EXIT_FAILURE); return; /* anti-compiler-warning */ } /* * Try to match the systemd sockets to the cupsd listeners... */ cupsdLogMessage(CUPSD_LOG_DEBUG, "service_checkin: %d listeners.", count); for (i = 0; i < count; i ++) service_add_listener(SD_LISTEN_FDS_START + i, i); } #elif defined(HAVE_UPSTART) if (OnDemand) { const char *e; /* Environment var */ int fd; /* File descriptor */ if (!(e = getenv("UPSTART_EVENTS"))) { cupsdLogMessage(CUPSD_LOG_ERROR, "service_checkin: We did not get started via Upstart."); exit(EXIT_FAILURE); return; } if (strcasecmp(e, "socket")) { cupsdLogMessage(CUPSD_LOG_ERROR, "service_checkin: We did not get triggered via an Upstart socket event."); exit(EXIT_FAILURE); return; } if ((e = getenv("UPSTART_FDS")) == NULL) { cupsdLogMessage(CUPSD_LOG_ERROR, "service_checkin: Unable to get listener sockets from UPSTART_FDS."); exit(EXIT_FAILURE); return; } cupsdLogMessage(CUPSD_LOG_DEBUG, "service_checkin: UPSTART_FDS=%s", e); fd = (int)strtol(e, NULL, 10); if (fd < 0) { cupsdLogMessage(CUPSD_LOG_ERROR, "service_checkin: Could not parse UPSTART_FDS: %s", strerror(errno)); exit(EXIT_FAILURE); return; } /* * Upstart only supportst a single on-demand socket file descriptor... */ service_add_listener(fd, 0); } #endif /* HAVE_LAUNCHD */ } /* * 'service_checkout()' - Update the KeepAlive/PID file as needed. */ static void service_checkout(int shutdown) /* I - Shutting down? */ { cups_file_t *fp; /* File */ char pidfile[1024]; /* PID/KeepAlive file */ /* * When running on-demand, use the KeepAlive file, otherwise write a PID file * to StateDir... */ #ifdef HAVE_ONDEMAND if (OnDemand) { int shared_printers = 0; /* Do we have shared printers? */ strlcpy(pidfile, CUPS_KEEPALIVE, sizeof(pidfile)); /* * If printer sharing is on see if there are any actual shared printers... */ if (Browsing && BrowseLocalProtocols) { cupsd_printer_t *p = NULL; /* Current printer */ for (p = (cupsd_printer_t *)cupsArrayFirst(Printers); p; p = (cupsd_printer_t *)cupsArrayNext(Printers)) { if (p->shared) break; } shared_printers = (p != NULL); } if (cupsArrayCount(ActiveJobs) || /* Active jobs */ WebInterface || /* Web interface enabled */ NeedReload || /* Doing a reload */ shared_printers) /* Printers being shared */ { /* * Create or remove the "keep-alive" file based on whether there are active * jobs or shared printers to advertise... */ shutdown = 0; } } else #endif /* HAVE_ONDEMAND */ snprintf(pidfile, sizeof(pidfile), "%s/cupsd.pid", StateDir); if (shutdown) { cupsdLogMessage(CUPSD_LOG_DEBUG, "Removing KeepAlive/PID file \"%s\".", pidfile); unlink(pidfile); } else { cupsdLogMessage(CUPSD_LOG_DEBUG, "Creating KeepAlive/PID file \"%s\".", pidfile); if ((fp = cupsFileOpen(pidfile, "w")) != NULL) { /* * Save the PID in the file... */ cupsFilePrintf(fp, "%d\n", (int)getpid()); cupsFileClose(fp); } else cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to create KeepAlive/PID file \"%s\": %s", pidfile, strerror(errno)); } # ifdef __APPLE__ if (OnDemand && shutdown) xpc_transaction_end(); # endif /* __APPLE__ */ } /* * 'usage()' - Show scheduler usage. */ static void usage(int status) /* O - Exit status */ { FILE *fp = status ? stderr : stdout; /* Output file */ _cupsLangPuts(fp, _("Usage: cupsd [options]")); _cupsLangPuts(fp, _("Options:")); _cupsLangPuts(fp, _("-c cupsd.conf Set cupsd.conf file to use.")); _cupsLangPuts(fp, _("-f Run in the foreground.")); _cupsLangPuts(fp, _("-F Run in the foreground but detach from console.")); _cupsLangPuts(fp, _("-h Show this usage message.")); #ifdef HAVE_ONDEMAND _cupsLangPuts(fp, _("-l Run cupsd on demand.")); #endif /* HAVE_ONDEMAND */ _cupsLangPuts(fp, _("-s cups-files.conf Set cups-files.conf file to use.")); _cupsLangPuts(fp, _("-t Test the configuration file.")); exit(status); }