/* Anacron - run commands periodically Copyright (C) 1998 Itai Tzur Copyright (C) 1999 Sean 'Shaleh' Perry 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 2 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, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA The GNU General Public License can also be found in the file `COPYING' that comes with the Anacron source distribution. */ #include #include #include #include #include #include #include #include #include "global.h" #include "gregor.h" pid_t primary_pid; int day_now; int year, month, day_of_month; /* date anacron started */ char *program_name; char *anacrontab; int serialize, force, update_only, now, no_daemon, quiet; /* command-line options */ char **args; /* vector of "job" command-line arguments */ int nargs; /* number of these */ char *defarg = "*"; int in_background; /* are we in the background? */ int old_umask; /* umask when started */ sigset_t old_sigmask; /* signal mask when started */ job_rec *first_job_rec; env_rec *first_env_rec; static time_t start_sec; /* time anacron started */ static volatile int got_sigalrm, got_sigchld, got_sigusr1; int running_jobs, running_mailers; /* , number of */ static void print_version() { printf("Anacron " RELEASE "\n" "Copyright (C) 1998 Itai Tzur \n" "Copyright (C) 1999 Sean 'Shaleh' Perry \n" "\n" "Mail comments, suggestions and bug reports to ." "\n\n"); } static void print_usage() { printf("Usage: anacron [-s] [-f] [-n] [-d] [-q] [-t anacrontab] [job] ...\n" " anacron -u [job] ...\n" " anacron [-V|-h]\n" "\n" " -s Serialize execution of jobs\n" " -f Force execution of jobs, even before their time\n" " -n Run jobs with no delay, implies -s\n" " -d Don't fork to the background\n" " -q Suppress stderr messages, only applicable with -d\n" " -u Update the timestamps without actually running anything\n" " -t Use this anacrontab\n" " -V Print version information\n" " -h Print this message\n" "\n" "See the manpage for more details.\n" "\n"); } static void parse_opts(int argc, char *argv[]) /* Parse command-line options */ { int opt; quiet = no_daemon = serialize = force = update_only = now = 0; opterr = 0; while ((opt = getopt(argc, argv, "sfundqt:Vh")) != EOF) { switch (opt) { case 's': serialize = 1; break; case 'f': force = 1; break; case 'u': update_only = 1; break; case 'n': now = serialize = 1; break; case 'd': no_daemon = 1; break; case 'q': quiet = 1; break; case 't': anacrontab = strdup(optarg); break; case 'V': print_version(); exit(0); case 'h': print_usage(); exit(0); case '?': fprintf(stderr, "%s: invalid option: %c\n", program_name, optopt); fprintf(stderr, "type: `%s -h' for more information\n", program_name); exit(FAILURE_EXIT); } } if (optind == argc) { /* no arguments. Equivalent to: `*' */ nargs = 1; args = &defarg; } else { nargs = argc - optind; args = argv + optind; } } pid_t xfork() /* Like fork(), only never returns on failure */ { pid_t pid; pid = fork(); if (pid == -1) die_e("Can't fork"); return pid; } int xopen(int fd, const char *file_name, int flags) /* Like open, only it: * a) never returns on failure, and * b) if "fd" is non-negative, expect the file to open * on file-descriptor "fd". */ { int rfd; rfd = open(file_name, flags); if (fd >= 0 && rfd != fd) die_e("Can't open %s on file-descriptor %d", file_name, fd); else if (rfd < 0) die_e("Can't open %s", file_name); return rfd; } void xclose(int fd) /* Like close(), only doesn't return on failure */ { if (close(fd)) die_e("Can't close file descriptor %d", fd); } static void go_background() /* Become a daemon. The foreground process exits successfully. */ { pid_t pid; /* stdin is already closed */ if (fclose(stdout)) die_e("Can't close stdout"); xopen(1, "/dev/null", O_WRONLY); if (fclose(stderr)) die_e("Can't close stderr"); xopen(2, "/dev/null", O_WRONLY); pid = xfork(); if (pid != 0) { /* parent */ exit(0); } else { /* child */ primary_pid = getpid(); if (setsid() == -1) die_e("setsid() error"); in_background = 1; } } void handle_sigalrm() { got_sigalrm = 1; } void handle_sigchld() { got_sigchld = 1; } void handle_sigusr1() { got_sigusr1 = 1; } static void set_signal_handling() /* We only use SIGALRM, SIGCHLD and SIGUSR1, and we unblock them only * in wait_signal(). */ { sigset_t ss; struct sigaction sa; got_sigalrm = got_sigchld = got_sigusr1 = 0; /* block SIGALRM, SIGCHLD and SIGUSR1 */ if (sigemptyset(&ss) || sigaddset(&ss, SIGALRM) || sigaddset(&ss, SIGCHLD) || sigaddset(&ss, SIGUSR1)) die_e("sigset error"); if (sigprocmask(SIG_BLOCK, &ss, NULL)) die_e ("sigprocmask error"); /* setup SIGALRM handler */ sa.sa_handler = handle_sigalrm; sa.sa_mask = ss; sa.sa_flags = 0; if (sigaction(SIGALRM, &sa, NULL)) die_e("sigaction error"); /* setup SIGCHLD handler */ sa.sa_handler = handle_sigchld; sa.sa_mask = ss; sa.sa_flags = SA_NOCLDSTOP; if (sigaction(SIGCHLD, &sa, NULL)) die_e("sigaction error"); /* setup SIGUSR1 handler */ sa.sa_handler = handle_sigusr1; sa.sa_mask = ss; sa.sa_flags = 0; if (sigaction(SIGUSR1, &sa, NULL)) die_e("sigaction error"); } static void wait_signal() /* Return after a signal is caught */ { sigset_t ss; if (sigprocmask(0, NULL, &ss)) die_e("sigprocmask error"); if (sigdelset(&ss, SIGALRM) || sigdelset(&ss, SIGCHLD) || sigdelset(&ss, SIGUSR1)) die_e("sigset error"); sigsuspend(&ss); } static void wait_children() /* Wait until we have no more children (of any kind) */ { while (running_jobs > 0 || running_mailers > 0) { wait_signal(); if (got_sigchld) tend_children(); got_sigchld = 0; if (got_sigusr1) explain("Received SIGUSR1"); got_sigusr1 = 0; } } static void orderly_termination() /* Execution is diverted here, when we get SIGUSR1 */ { explain("Received SIGUSR1"); got_sigusr1 = 0; wait_children(); explain("Exited"); exit(0); } static void xsleep(unsigned int n) /* Sleep for n seconds, servicing SIGCHLDs and SIGUSR1s in the meantime. * If n=0, return immediately. */ { if (n == 0) return; alarm(n); do { wait_signal(); if (got_sigchld) tend_children(); got_sigchld = 0; if (got_sigusr1) orderly_termination(); } while (!got_sigalrm); got_sigalrm = 0; } static void wait_jobs() /* Wait until there are no running jobs, * servicing SIGCHLDs and SIGUSR1s in the meantime. */ { while (running_jobs > 0) { wait_signal(); if (got_sigchld) tend_children(); got_sigchld = 0; if (got_sigusr1) orderly_termination(); } } static void record_start_time() { struct tm *tm_now; start_sec = time(NULL); tm_now = localtime(&start_sec); year = tm_now->tm_year + 1900; month = tm_now->tm_mon + 1; day_of_month = tm_now->tm_mday; day_now = day_num(year, month, day_of_month); if (day_now == -1) die("Invalid date (this is really embarrassing)"); if (!update_only) explain("Anacron " RELEASE " started on %04d-%02d-%02d", year, month, day_of_month); } static int time_till(job_rec *jr) /* Return the number of seconds that we have to wait until it's time * to start job jr. */ { unsigned int tj, tn; if (now) return 0; tn = time(NULL); tj = start_sec + jr->delay * 60; if (tj < tn) return 0; return tj - tn; } static void fake_jobs() { int j; j = 0; while (j < njobs) { fake_job(job_array[j]); explain("Updated timestamp for job `%s' to %04d-%02d-%02d", job_array[j]->ident, year, month, day_of_month); j++; } } static void explain_intentions() { int j; j = 0; while (j < njobs) { if (now) { explain("Will run job `%s'", job_array[j]->ident); } else { explain("Will run job `%s' in %d min.", job_array[j]->ident, job_array[j]->delay); } j++; } if (serialize && njobs > 0) explain("Jobs will be executed sequentially"); } int main(int argc, char *argv[]) { int j; anacrontab = NULL; if((program_name = strrchr(argv[0], '/')) == NULL) program_name = argv[0]; else ++program_name; /* move pointer to char after '/' */ parse_opts(argc, argv); if (anacrontab == NULL) anacrontab = strdup(ANACRONTAB); in_background = 0; if (chdir(SPOOLDIR)) die_e("Can't chdir to " SPOOLDIR); old_umask = umask(0); if (sigprocmask(0, NULL, &old_sigmask)) die_e("sigset error"); if (fclose(stdin)) die_e("Can't close stdin"); xopen(0, "/dev/null", O_RDONLY); if (!no_daemon) go_background(); else primary_pid = getpid(); record_start_time(); read_tab(); arrange_jobs(); if (update_only) { fake_jobs(); exit(0); } explain_intentions(); set_signal_handling(); running_jobs = running_mailers = 0; for(j = 0; j < njobs; ++j) { xsleep(time_till(job_array[j])); if (serialize) wait_jobs(); launch_job(job_array[j]); } wait_children(); explain("Normal exit (%d jobs run)", njobs); exit(0); }