/* wydawca - automatic release submission daemon Copyright (C) 2009, 2010 Sergey Poznyakoff This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "wydawca.h" #include "mail.h" #define STATE_FINISHED 0x01 #define STATE_QUEUED 0x02 #define STATE_ACTIVE 0x04 struct job { struct job *next, *prev; int state; const struct spool *spool; uid_t uid; pid_t pid; time_t timestamp; int exit_status; }; struct job *queue; size_t jobmax; size_t jobcnt; static struct spool fake_spool = { "all spools" }; static int wakeup; RETSIGTYPE queue_signal (int sig) { wakeup = 1; signal (sig, queue_signal); } void set_timer (time_t interval) { wakeup = 0; if (interval) alarm (interval); } struct job * job_locate (const struct spool *spool, uid_t uid) { struct job *p; for (p = queue; p; p = p->next) if (p->spool == spool && p->uid == uid) break; return p; } size_t job_active_count () { struct job *job; size_t count = 0; for (job = queue; job; job = job->next) if (job->state & STATE_ACTIVE) count++; return count; } int wydawca_scanner (struct job *job) { int rc; initstats(); timer_start ("wydawca"); if (job->spool == &fake_spool) rc = scan_all_spools (1, &job->uid); else { spool_create_timers (); rc = scan_spool (job->spool, 1, &job->uid); } timer_stop ("wydawca"); logstats (); mail_finish (); return rc; } int job_start (struct job *job) { pid_t pid; if (jobmax && jobcnt == jobmax) { logmsg (LOG_NOTICE, "maximum number of processes active"); return 1; } if (debug_level) logmsg (LOG_DEBUG, _("starting job: %s, %lu"), job->spool->tag, (unsigned long)job->uid); if (single_process) { if (wydawca_scanner (job)) job->state = STATE_QUEUED; else job->state = STATE_FINISHED; wakeup = 1; return 0; } pid = fork (); if (pid == 0) { int i; signal (SIGHUP, SIG_DFL); signal (SIGTERM, SIG_DFL); signal (SIGQUIT, SIG_DFL); signal (SIGINT, SIG_DFL); signal (SIGCHLD, SIG_DFL); signal (SIGALRM, SIG_DFL); alarm (0); for (i = getdtablesize (); i > 2; i--) close (i); exit (wydawca_scanner (job) ? WYDAWCA_EX_AGAIN : 0); } else if (pid == -1) { logmsg (LOG_CRIT, "fork: %s", strerror (errno)); return -1; } else { job->state = STATE_ACTIVE; job->pid = pid; jobcnt++; } return 0; } void job_remove (struct job *job) { struct job *p; if (debug_level) logmsg (LOG_DEBUG, _("removing job: %s, %lu"), job->spool->tag, (unsigned long)job->uid); p = job->prev; if (p) p->next = job->next; else queue = job->next; p = job->next; if (p) p->prev = job->prev; } void job_insert (struct job *job, struct job *elt) { struct job *p; job->prev = elt; if (!elt) { if (queue) queue->prev = job; job->next = queue; queue = job; return; } p = elt->next; elt->next = job; if (p) p->prev = job; } void schedule_job (const struct spool *spool, uid_t uid) { struct job *job; if (!spool) spool = &fake_spool; if (debug_level) logmsg (LOG_DEBUG, _("scheduling job: %s, %lu"), spool->tag, (unsigned long)uid); job = job_locate (spool, uid); if (!job) { job = xzalloc (sizeof (*job)); job->spool = spool; job->uid = uid; job->pid = -1; time (&job->timestamp); job_insert (job, NULL); } job->state |= STATE_QUEUED; job_start (job); } static void print_status (struct job *job, int expect_term) { struct passwd *pw = getpwuid (job->uid); int status = job->exit_status; if (WIFEXITED (status)) { int exit_code = WEXITSTATUS (status); if (exit_code == 0) { if (debug_level) logmsg (LOG_DEBUG, _("%lu (%s, %s) exited successfully"), (unsigned long) job->pid, job->spool->tag, pw->pw_name); } else if (exit_code == WYDAWCA_EX_AGAIN) { if (debug_level) logmsg (LOG_DEBUG, _("%lu (%s, %s) reported tempfail"), (unsigned long) job->pid, job->spool->tag, pw->pw_name); } else logmsg (LOG_ERR, _("%lu (%s, %s) failed with status %d"), (unsigned long) job->pid, job->spool->tag, pw->pw_name, exit_code); } else if (WIFSIGNALED (status)) { int prio; if (expect_term && WTERMSIG (status) == SIGTERM) { if (!debug_level) return; prio = LOG_DEBUG; } else prio = LOG_ERR; logmsg (prio, _("%lu (%s, %s) terminated on signal %d"), (unsigned long)job->pid, job->spool->tag, pw->pw_name, WTERMSIG (status)); } else if (WIFSTOPPED (status)) logmsg (LOG_NOTICE, _("%lu (%s, %s) stopped on signal %d"), (unsigned long)job->pid, job->spool->tag, pw->pw_name, WSTOPSIG (status)); #ifdef WCOREDUMP else if (WCOREDUMP (status)) logmsg (LOG_NOTICE, _("%lu (%s, %s) dumped core"), (unsigned long)job->pid, job->spool->tag, pw->pw_name); #endif else logmsg (LOG_ERR, _("%lu (%s, %s) terminated with unrecognized status"), (unsigned long)job->pid, job->spool->tag, pw->pw_name); } void job_queue_runner () { int status; struct job *job; time_t now = time (NULL); time_t min_interval = 0; if (!wakeup) return; wakeup = 0; for (;;) { pid_t pid = waitpid ((pid_t)-1, &status, WNOHANG); if (pid <= 0) break; for (job = queue; job; job = job->next) { if ((job->state & STATE_ACTIVE) && job->pid == pid) { job->state &= ~STATE_ACTIVE; job->state |= STATE_FINISHED; job->exit_status = status; jobcnt--; } } } for (job = queue; job;) { struct job *next = job->next; if (job->state & STATE_FINISHED) { print_status (job, 0); if ((job->state &= ~STATE_FINISHED) == 0) { if (WIFEXITED (job->exit_status) && WEXITSTATUS (job->exit_status) == WYDAWCA_EX_AGAIN) { time_t interval = lock_timeout; if (interval == 0) interval = lock_expire_time; /* Re-queue the job */ job->state = STATE_QUEUED; job->timestamp = now + interval; } else job_remove (job); } } if (job->state == STATE_QUEUED) { if (job->timestamp >= now) { if (job_start (job)) pause (); /* FIXME */ now = time (NULL); } else { time_t interval = job->timestamp - now; if (min_interval == 0 || interval < min_interval) min_interval = interval; } } job = next; } if (debug_level > 1) logmsg (LOG_DEBUG, _("computed interval: %lu"), min_interval); set_timer (min_interval); } void job_init () { signal (SIGCHLD, queue_signal); signal (SIGALRM, queue_signal); }