/* Anacron - run commands periodically Copyright (C) 1998 Itai Tzur Copyright (C) 1999 Sean 'Shaleh' Perry Copyright (C) 2004 Pascal Hakim 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 #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; char *spooldir; int serialize, force, update_only, now, no_daemon, quiet, testing_only; /* 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; time_t start_sec; /* time anacron started */ static volatile int got_sigalrm, got_sigchld, got_sigusr1; int running_jobs, running_mailers; /* , number of */ int range_start = -1; int range_stop = -1; static void print_version() { printf("Anacron \n" "Copyright (C) 1998 Itai Tzur \n" "Copyright (C) 1999 Sean 'Shaleh' Perry \n" "Copyright (C) 2004 Pascal Hakim \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] [-S spooldir] [job] ...\n" " anacron [-S spooldir] -u [job] ...\n" " anacron [-V|-h]\n" " anacron -T [-t anacrontab]\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" " -T Test an anacrontab\n" " -S Select a different spool directory\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:TS: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 'T': testing_only = 1; break; case 'S': spooldir = 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 && !testing_only) explain("Anacron 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; if (tj - tn > 3600*24) { explain("System time manipulation detected, job `%s' will run immediately", jr->ident); 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; int cwd; struct timeval tv; struct timezone tz; anacrontab = NULL; spooldir = NULL; setlocale(LC_ALL, ""); if (gettimeofday(&tv, &tz) != 0) explain("Can't get exact time, failure."); srandom(getpid()+tv.tv_usec); 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); if (spooldir == NULL) spooldir = strdup(ANACRON_SPOOL_DIR); if ((cwd = open ("./", O_RDONLY)) == -1) { die_e ("Can't save current directory"); } in_background = 0; if (chdir(spooldir)) die_e("Can't chdir to %s", 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 && !testing_only) go_background(); else primary_pid = getpid(); record_start_time(); read_tab(cwd); close(cwd); arrange_jobs(); if (testing_only) { if (complaints) exit (1); exit (0); } 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 job%s run)", njobs, njobs == 1 ? "" : "s"); exit(0); }