#define _MAIN_C_
#include "cupsd.h"
#include <sys/resource.h>
#include <syslog.h>
#include <grp.h>
#if defined(HAVE_MALLOC_H) && defined(HAVE_MALLINFO)
# include <malloc.h>
#endif
#ifdef __APPLE__
#include <mach/mach.h>
#include <mach/mach_error.h>
#include <servers/bootstrap.h>
#ifdef HAVE_NOTIFY_H
#include <notify.h>
#endif
static kern_return_t registerBootstrapService();
static kern_return_t destroyBootstrapService();
static mach_port_t server_priv_port;
static char service_name[] = "/usr/sbin/cupsd";
#endif
static void parent_handler(int sig);
static void sigchld_handler(int sig);
static void sighup_handler(int sig);
static void sigterm_handler(int sig);
static void usage(void);
static int parent_signal = 0;
static int holdcount = 0;
#if defined(HAVE_SIGACTION) && !defined(HAVE_SIGSET)
static sigset_t holdmask;
#endif
int
main(int argc,
char *argv[])
{
int i;
char *opt;
int fg;
fd_set *input,
*output;
client_t *con;
job_t *job,
*next;
listener_t *lis;
time_t activity;
time_t browse_time;
time_t senddoc_time;
#ifdef HAVE_MALLINFO
time_t mallinfo_time;
#endif
struct timeval timeout;
struct rlimit limit;
#if defined(HAVE_SIGACTION) && !defined(HAVE_SIGSET)
struct sigaction action;
#endif
#ifdef __sgi
cups_file_t *fp;
#endif
#ifdef __APPLE__
int debug = 0;
#endif
fg = 0;
for (i = 1; i < argc; i ++)
if (argv[i][0] == '-')
for (opt = argv[i] + 1; *opt != '\0'; opt ++)
switch (*opt)
{
case 'c' :
i ++;
if (i >= argc)
usage();
if (argv[i][0] == '/')
{
SetString(&ConfigurationFile, argv[i]);
}
else
{
char current[1024];
getcwd(current, sizeof(current));
SetStringf(&ConfigurationFile, "%s/%s", current, argv[i]);
}
break;
case 'f' :
fg = 1;
break;
case 'F' :
fg = -1;
break;
#ifdef __APPLE__
case 'd' :
debug = 1;
break;
#endif
default :
fprintf(stderr, "cupsd: Unknown option \'%c\' - aborting!\n", *opt);
usage();
break;
}
else
{
fprintf(stderr, "cupsd: Unknown argument \'%s\' - aborting!\n", argv[i]);
usage();
}
if (!ConfigurationFile)
SetString(&ConfigurationFile, CUPS_SERVERROOT "/cupsd.conf");
if (!fg)
{
#ifdef HAVE_SIGSET
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
if (fork() > 0)
{
for (; parent_signal == 0;)
sleep(1);
if (parent_signal == SIGUSR1)
return (0);
if (wait(&i) < 0)
{
perror("cupsd");
i = 1;
}
else if (i >= 256)
fprintf(stderr, "cupsd: Child exited with status %d!\n", i / 256);
else
fprintf(stderr, "cupsd: Child exited on signal %d!\n", i);
return (i);
}
}
if (fg < 1)
{
chdir("/");
#ifndef DEBUG
getrlimit(RLIMIT_CORE, &limit);
limit.rlim_cur = 0;
setrlimit(RLIMIT_CORE, &limit);
close(0);
close(1);
close(2);
setsid();
#endif
}
if (getenv("TZ") != NULL)
SetStringf(&TZ, "TZ=%s", getenv("TZ"));
else
SetString(&TZ, "");
tzset();
#ifdef LC_TIME
setlocale(LC_TIME, "");
#endif
getrlimit(RLIMIT_NOFILE, &limit);
if (limit.rlim_max > CUPS_MAX_FDS)
MaxFDs = CUPS_MAX_FDS;
else
MaxFDs = limit.rlim_max;
limit.rlim_cur = MaxFDs;
setrlimit(RLIMIT_NOFILE, &limit);
SetSize = (MaxFDs + 7) / 8;
InputSet = (fd_set *)calloc(1, SetSize);
OutputSet = (fd_set *)calloc(1, SetSize);
input = (fd_set *)calloc(1, SetSize);
output = (fd_set *)calloc(1, SetSize);
if (InputSet == NULL || OutputSet == NULL || input == NULL || output == NULL)
{
syslog(LOG_LPR, "Unable to allocate memory for select() sets - exiting!");
return (1);
}
if (!ReadConfiguration())
{
syslog(LOG_LPR, "Unable to read configuration file \'%s\' - exiting!",
ConfigurationFile);
return (1);
}
#ifdef HAVE_SIGSET
if (RunAsUser)
sigset(SIGHUP, sigterm_handler);
else
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, SIGHUP);
if (RunAsUser)
action.sa_handler = sigterm_handler;
else
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
if (RunAsUser)
signal(SIGHUP, sigterm_handler);
else
signal(SIGHUP, sighup_handler);
signal(SIGPIPE, SIG_IGN);
signal(SIGTERM, sigterm_handler);
#endif
#ifdef __sgi
if ((fp = cupsFileOpen("/var/spool/lp/SCHEDLOCK", "w")) == NULL)
{
syslog(LOG_LPR, "Unable to create fake lpsched lock file "
"\"/var/spool/lp/SCHEDLOCK\"\' - %s!",
strerror(errno));
}
else
{
fchmod(cupsFileNumber(fp), 0644);
fchown(cupsFileNumber(fp), User, Group);
cupsFileClose(fp);
}
#endif
InitCerts();
if (!fg)
kill(getppid(), SIGUSR1);
#ifdef __APPLE__
if (!debug)
registerBootstrapService();
#endif
if (RunAsUser)
{
setgid(Group);
setgroups(1, &Group);
setuid(User);
}
CheckJobs();
browse_time = time(NULL);
senddoc_time = time(NULL);
#ifdef HAVE_MALLINFO
mallinfo_time = 0;
#endif
for (;;)
{
if (NeedReload)
{
if (NumClients > 0)
{
for (i = NumClients, con = Clients; i > 0; i --, con ++)
if (con->http.state == HTTP_WAITING)
{
CloseClient(con);
con --;
}
else
con->http.keep_alive = HTTP_KEEPALIVE_OFF;
PauseListening();
}
else if (!ReadConfiguration())
{
syslog(LOG_LPR, "Unable to read configuration file \'%s\' - exiting!",
ConfigurationFile);
break;
}
}
memcpy(input, InputSet, SetSize);
memcpy(output, OutputSet, SetSize);
for (i = NumClients, con = Clients; i > 0; i --, con ++)
if (con->http.used > 0)
break;
if (i)
{
timeout.tv_sec = 0;
timeout.tv_usec = 0;
}
else
{
timeout.tv_sec = 1;
timeout.tv_usec = 0;
}
if ((i = select(MaxFDs, input, output, NULL, &timeout)) < 0)
{
char s[16384],
*sptr;
int slen;
if (errno == EINTR)
continue;
LogMessage(L_EMERG, "select() failed - %s!", strerror(errno));
strcpy(s, "InputSet =");
slen = 10;
sptr = s + 10;
for (i = 0; i < MaxFDs; i ++)
if (FD_ISSET(i, InputSet))
{
snprintf(sptr, sizeof(s) - slen, " %d", i);
slen += strlen(sptr);
sptr += strlen(sptr);
}
LogMessage(L_EMERG, s);
strcpy(s, "OutputSet =");
slen = 11;
sptr = s + 11;
for (i = 0; i < MaxFDs; i ++)
if (FD_ISSET(i, OutputSet))
{
snprintf(sptr, sizeof(s) - slen, " %d", i);
slen += strlen(sptr);
sptr += strlen(sptr);
}
LogMessage(L_EMERG, s);
for (i = 0, con = Clients; i < NumClients; i ++, con ++)
LogMessage(L_EMERG, "Clients[%d] = %d, file = %d, state = %d",
i, con->http.fd, con->file, con->http.state);
for (i = 0, lis = Listeners; i < NumListeners; i ++, lis ++)
LogMessage(L_EMERG, "Listeners[%d] = %d", i, lis->fd);
LogMessage(L_EMERG, "BrowseSocket = %d", BrowseSocket);
for (job = Jobs; job != NULL; job = job->next)
LogMessage(L_EMERG, "Jobs[%d] = %d", job->id, job->pipe);
break;
}
for (i = NumListeners, lis = Listeners; i > 0; i --, lis ++)
if (FD_ISSET(lis->fd, input))
AcceptClient(lis);
for (i = NumClients, con = Clients; i > 0; i --, con ++)
{
if (FD_ISSET(con->http.fd, input) || con->http.used)
if (!ReadClient(con))
{
con --;
continue;
}
if (FD_ISSET(con->http.fd, output) &&
(!con->pipe_pid || FD_ISSET(con->file, input)))
if (!WriteClient(con))
{
con --;
continue;
}
activity = time(NULL) - Timeout;
if (con->http.activity < activity && !con->pipe_pid)
{
LogMessage(L_DEBUG, "Closing client %d after %d seconds of inactivity...",
con->http.fd, Timeout);
CloseClient(con);
con --;
continue;
}
}
for (job = Jobs; job != NULL; job = next)
{
next = job->next;
if (job->pipe && FD_ISSET(job->pipe, input))
{
FD_CLR(job->pipe, input);
UpdateJob(job);
}
}
if (CGIPipes[0] >= 0 && FD_ISSET(CGIPipes[0], input))
UpdateCGI();
if (Browsing && BrowseProtocols)
{
if (BrowseSocket >= 0 && FD_ISSET(BrowseSocket, input))
UpdateCUPSBrowse();
if (PollPipe >= 0 && FD_ISSET(PollPipe, input))
UpdatePolling();
#ifdef HAVE_LIBSLP
if ((BrowseProtocols & BROWSE_SLP) && BrowseSLPRefresh <= time(NULL))
UpdateSLPBrowse();
#endif
if (time(NULL) > browse_time)
{
SendBrowseList();
browse_time = time(NULL);
}
}
if ((time(NULL) - senddoc_time) >= 10)
{
CheckJobs();
senddoc_time = time(NULL);
}
#ifdef HAVE_MALLINFO
if ((time(NULL) - mallinfo_time) >= 60 && LogLevel >= L_DEBUG)
{
struct mallinfo mem;
mem = mallinfo();
LogMessage(L_DEBUG, "mallinfo: arena = %d, used = %d, free = %d\n",
mem.arena, mem.usmblks + mem.uordblks,
mem.fsmblks + mem.fordblks);
mallinfo_time = time(NULL);
}
#endif
if ((time(NULL) - RootCertTime) >= RootCertDuration && RootCertDuration)
{
DeleteCert(0);
AddCert(0, "root");
}
}
DeleteAllCerts();
CloseAllClients();
StopListening();
#ifdef __APPLE__
sleep(1);
#endif
return (1);
}
void
CatchChildSignals(void)
{
#if defined(HAVE_SIGACTION) && !defined(HAVE_SIGSET)
struct sigaction action;
#endif
#ifdef HAVE_SIGSET
sigset(SIGCHLD, sigchld_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);
#else
signal(SIGCLD, sigchld_handler);
#endif
}
void
ClearString(char **s)
{
if (s && *s)
{
free(*s);
*s = NULL;
}
}
void
HoldSignals(void)
{
#if defined(HAVE_SIGACTION) && !defined(HAVE_SIGSET)
sigset_t newmask;
#endif
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
}
void
IgnoreChildSignals(void)
{
#if defined(HAVE_SIGACTION) && !defined(HAVE_SIGSET)
struct sigaction action;
#endif
#ifdef HAVE_SIGSET
sigset(SIGCHLD, SIG_DFL);
#elif defined(HAVE_SIGACTION)
memset(&action, 0, sizeof(action));
sigemptyset(&action.sa_mask);
sigaddset(&action.sa_mask, SIGCHLD);
action.sa_handler = SIG_DFL;
sigaction(SIGCHLD, &action, NULL);
#else
signal(SIGCLD, SIG_DFL);
#endif
}
void
ReleaseSignals(void)
{
holdcount --;
if (holdcount > 0)
return;
#ifdef HAVE_SIGSET
sigrelse(SIGTERM);
sigrelse(SIGCHLD);
#elif defined(HAVE_SIGACTION)
sigprocmask(SIG_SETMASK, &holdmask, NULL);
#endif
}
void
SetString(char **s,
const char *v)
{
if (!s || *s == v)
return;
if (*s)
free(*s);
if (v)
*s = strdup(v);
else
*s = NULL;
}
void
SetStringf(char **s,
const char *f,
...)
{
char v[1024];
va_list ap;
char *olds;
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);
}
static void
parent_handler(int sig)
{
parent_signal = sig;
}
static void
sigchld_handler(int sig)
{
int olderrno;
int status;
int pid;
job_t *job;
int i;
(void)sig;
SignalCount ++;
olderrno = errno;
#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
{
DEBUG_printf(("sigchld_handler: pid = %d, status = %d\n", pid, status));
if (status == SIGTERM)
status = 0;
if (status)
{
if (status < 256)
LogMessage(L_ERROR, "PID %d crashed on signal %d!", pid, status);
else
LogMessage(L_ERROR, "PID %d stopped with status %d!", pid,
status / 256);
if (LogLevel < L_DEBUG)
LogMessage(L_INFO, "Hint: Try setting the LogLevel to \"debug\" to find out more.");
}
else
LogMessage(L_DEBUG2, "PID %d exited with no errors.", pid);
if (pid)
DeleteCert(pid);
for (job = Jobs; job != NULL; job = job->next)
if (job->state != NULL &&
job->state->values[0].integer == IPP_JOB_PROCESSING)
{
for (i = 0; job->procs[i]; i ++)
if (job->procs[i] == pid)
break;
if (job->procs[i])
{
job->procs[i] = -pid;
if (status && job->status >= 0)
{
if (!job->procs[i + 1])
job->status = -status;
else
job->status = status;
}
break;
}
}
}
errno = olderrno;
#ifdef HAVE_SIGSET
sigset(SIGCHLD, sigchld_handler);
#elif !defined(HAVE_SIGACTION)
signal(SIGCLD, sigchld_handler);
#endif
SignalCount --;
}
static void
sighup_handler(int sig)
{
(void)sig;
NeedReload = RELOAD_ALL;
#ifdef HAVE_SIGSET
sigset(SIGHUP, sighup_handler);
#elif !defined(HAVE_SIGACTION)
signal(SIGHUP, sighup_handler);
#endif
}
static void
sigterm_handler(int sig)
{
#ifdef __sgi
struct stat statbuf;
#endif
(void)sig;
#ifdef __APPLE__
destroyBootstrapService();
#endif
SignalCount ++;
LogMessage(L_ERROR, "Scheduler shutting down due to SIGTERM.");
StopServer();
StopAllJobs();
#ifdef HAVE_NOTIFY_POST
notify_post("com.apple.printerListChange");
#endif
#ifdef __sgi
if (!stat("/var/spool/lp/FIFO", &statbuf))
if (!S_ISFIFO(statbuf.st_mode))
unlink("/var/spool/lp/SCHEDLOCK");
#endif
exit(1);
}
static void
usage(void)
{
#ifdef __APPLE__
fputs("Usage: cupsd [-c config-file] [-f] [-F] [-d]\n", stderr);
#else
fputs("Usage: cupsd [-c config-file] [-f] [-F]\n", stderr);
#endif
exit(1);
}
#ifdef __APPLE__
static kern_return_t registerBootstrapService()
{
kern_return_t status;
mach_port_t service_send_port, service_rcv_port;
status = bootstrap_check_in(bootstrap_port, (char*)service_name, &service_rcv_port);
if (status == KERN_SUCCESS)
{
server_priv_port = bootstrap_port;
}
else if (status == BOOTSTRAP_UNKNOWN_SERVICE)
{
status = bootstrap_create_server(bootstrap_port, "/usr/sbin/cupsd -f", getuid(),
FALSE , &server_priv_port);
if (status != KERN_SUCCESS)
return status;
status = bootstrap_create_service(server_priv_port, (char*)service_name, &service_send_port);
if (status != KERN_SUCCESS)
{
mach_port_deallocate(mach_task_self(), server_priv_port);
return status;
}
status = bootstrap_check_in(server_priv_port, (char*)service_name, &service_rcv_port);
if (status != KERN_SUCCESS)
{
mach_port_deallocate(mach_task_self(), server_priv_port);
mach_port_deallocate(mach_task_self(), service_send_port);
return status;
}
}
mach_port_destroy(mach_task_self(), service_rcv_port);
return status;
}
static kern_return_t destroyBootstrapService()
{
return bootstrap_register(server_priv_port, (char*)service_name, MACH_PORT_NULL);
}
#endif