root/anacron/runjob.c

/* [previous][next][first][last][top][bottom][index][help]  */

DEFINITIONS

This source file includes following definitions.
  1. temp_file
  2. file_size
  3. username
  4. xputenv
  5. setup_env
  6. run_job
  7. xwrite
  8. xwait
  9. launch_mailer
  10. tend_mailer
  11. launch_job
  12. tend_job
  13. tend_children

   1 /*
   2     Anacron - run commands periodically
   3     Copyright (C) 1998  Itai Tzur <itzur@actcom.co.il>
   4     Copyright (C) 1999  Sean 'Shaleh' Perry <shaleh@debian.org>
   5 
   6     This program is free software; you can redistribute it and/or modify
   7     it under the terms of the GNU General Public License as published by
   8     the Free Software Foundation; either version 2 of the License, or
   9     (at your option) any later version.
  10 
  11     This program is distributed in the hope that it will be useful,
  12     but WITHOUT ANY WARRANTY; without even the implied warranty of
  13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  14     GNU General Public License for more details.
  15 
  16     You should have received a copy of the GNU General Public License along
  17     with this program; if not, write to the Free Software Foundation, Inc.,
  18     51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  19  
  20     The GNU General Public License can also be found in the file
  21     `COPYING' that comes with the Anacron source distribution.
  22 */
  23 
  24 
  25 #include <errno.h>
  26 #include <unistd.h>
  27 #include <stdlib.h>
  28 #include <sys/stat.h>
  29 #include <pwd.h>
  30 #include <sys/types.h>
  31 #include <sys/wait.h>
  32 #include <fcntl.h>
  33 #include <signal.h>
  34 #include <stdio.h>
  35 #include <string.h>
  36 #include <limits.h>
  37 #include "global.h"
  38 
  39 #include <langinfo.h>
  40 
  41 static int
  42 temp_file(job_rec *jr)
     /* [previous][next][first][last][top][bottom][index][help]  */
  43 /* Open a temporary file and return its file descriptor */
  44 {
  45     char *dir;
  46     char template[PATH_MAX+1];
  47     int fdin = -1;
  48     int fdout;
  49     int len;
  50 
  51     dir = getenv("TMPDIR");
  52     if (dir == NULL || *dir == '\0')
  53         dir = P_tmpdir;
  54 
  55     len = snprintf(template, sizeof(template), "%s/$anacronXXXXXX", dir);
  56     if (len >= sizeof(template))
  57         die_e("TMPDIR too long");
  58 
  59     fdout = mkstemp(template);
  60     if (fdout == -1) die_e("Can't open temporary file for writing");
  61 
  62     fdin = open(template, O_RDONLY, S_IRUSR | S_IWUSR);
  63     if (fdin == -1) die_e("Can't open temporary file for reading");
  64 
  65     if (unlink(template)) die_e("Can't unlink temporary file");
  66 
  67     fcntl(fdout, F_SETFD, FD_CLOEXEC);    /* set close-on-exec flag */
  68     fcntl(fdin, F_SETFD, FD_CLOEXEC);    /* set close-on-exec flag */
  69 
  70     jr->input_fd = fdin;
  71     jr->output_fd = fdout;
  72 
  73     return fdout;
  74 }
  75 
  76 static off_t
  77 file_size(int fd)
     /* [previous][next][first][last][top][bottom][index][help]  */
  78 /* Return the size of temporary file fd */
  79 {
  80     struct stat st;
  81 
  82     if (fstat(fd, &st)) die_e("Can't fstat temporary file");
  83     return st.st_size;
  84 }
  85 
  86 static char *
  87 username(void)
     /* [previous][next][first][last][top][bottom][index][help]  */
  88 {
  89     struct passwd *ps;
  90     static char *user;
  91 
  92     if (user)
  93         return user;
  94 
  95     ps = getpwuid(geteuid());
  96     if (ps == NULL || ps->pw_name == NULL) die_e("getpwuid() error");
  97 
  98     user = strdup(ps->pw_name);
  99     if (user == NULL) die_e("memory allocation error");
 100 
 101     return user;
 102 }
 103 
 104 static void
 105 xputenv(const char *s)
     /* [previous][next][first][last][top][bottom][index][help]  */
 106 {
 107     char *name = NULL, *val = NULL;
 108     char *eq_ptr;
 109     const char *errmsg;
 110     size_t eq_index;
 111 
 112     if (s == NULL) {
 113         die_e("Invalid environment string");
 114     }
 115 
 116     eq_ptr = strchr(s, '=');
 117     if (eq_ptr == NULL) {
 118         die_e("Invalid environment string");
 119     }
 120 
 121     eq_index = (size_t) (eq_ptr - s);
 122 
 123     name = malloc((eq_index + 1) * sizeof(char));
 124     if (name == NULL) {
 125         die_e("Not enough memory to set the environment");
 126     }
 127 
 128     val = malloc((strlen(s) - eq_index) * sizeof(char));
 129     if (val == NULL) {
 130         die_e("Not enough memory to set the environment");
 131     }
 132 
 133     strncpy(name, s, eq_index);
 134     name[eq_index] = '\0';
 135     strcpy(val, s + eq_index + 1);
 136 
 137     if (setenv(name, val, 1)) {
 138         die_e("Can't set the environment");
 139     }
 140 
 141     free(name);
 142     free(val);
 143     return;
 144 
 145 }
 146 
 147 static void
 148 setup_env(const job_rec *jr)
     /* [previous][next][first][last][top][bottom][index][help]  */
 149 /* Setup the environment for the job according to /etc/anacrontab */
 150 {
 151     env_rec *er;
 152 
 153     er = first_env_rec;
 154     if (er == NULL || jr->prev_env_rec == NULL) return;
 155     xputenv(er->assign);
 156     while (er != jr->prev_env_rec)
 157     {
 158         er = er->next;
 159         xputenv(er->assign);
 160     }
 161 }
 162 
 163 static void
 164 run_job(const job_rec *jr)
     /* [previous][next][first][last][top][bottom][index][help]  */
 165 /* This is called to start the job, after the fork */
 166 {
 167     /* setup stdout and stderr */
 168     xclose(1);
 169     xclose(2);
 170     if (dup2(jr->output_fd, 1) != 1 || dup2(jr->output_fd, 2) != 2)
 171         die_e("dup2() error");     /* dup2 also clears close-on-exec flag */
 172     in_background = 0;  /* now, errors will be mailed to the user */
 173     if (chdir("/")) die_e("Can't chdir to '/'");
 174 
 175     if (sigprocmask(SIG_SETMASK, &old_sigmask, NULL))
 176         die_e("sigprocmask error");
 177     xcloselog();
 178     execl("/bin/sh", "/bin/sh", "-c", jr->command, (char *)NULL);
 179     die_e("execl() error");
 180 }
 181 
 182 static void
 183 xwrite(int fd, const char *string)
     /* [previous][next][first][last][top][bottom][index][help]  */
 184 /* Write (using write()) the string "string" to temporary file "fd".
 185  * Don't return on failure */
 186 {
 187     if (write(fd, string, strlen(string)) == -1)
 188         die_e("Can't write to temporary file");
 189 }
 190 
 191 static int
 192 xwait(pid_t pid , int *status)
     /* [previous][next][first][last][top][bottom][index][help]  */
 193 /* Check if child process "pid" has finished.  If it has, return 1 and its
 194  * exit status in "*status".  If not, return 0.
 195  */
 196 {
 197     pid_t r;
 198 
 199     r = waitpid(pid, status, WNOHANG);
 200     if (r == -1) die_e("waitpid() error");
 201     if (r == 0) return 0;
 202     return 1;
 203 }
 204 
 205 static void
 206 launch_mailer(job_rec *jr)
     /* [previous][next][first][last][top][bottom][index][help]  */
 207 {
 208     pid_t pid;
 209     struct stat buf;
 210 
 211     if (jr->mailto == NULL)
 212     {
 213         explain("Empty MAILTO set, not mailing output");
 214         return;
 215     }
 216 
 217     /* Check that we have a way of sending mail. */
 218     if(stat(SENDMAIL, &buf))
 219     {
 220         complain("Can't find sendmail at %s, not mailing output", SENDMAIL);
 221         return;
 222     }
 223 
 224     pid = xfork();
 225     if (pid == 0)
 226     {
 227         /* child */
 228         in_background = 1;
 229         /* set stdin to the job's output */
 230         xclose(STDIN_FILENO);
 231         if (dup2(jr->input_fd, STDIN_FILENO) != 0) die_e("Can't dup2()");
 232         if (lseek(STDIN_FILENO, 0, SEEK_SET) != 0) die_e("Can't lseek()");
 233         if (sigprocmask(SIG_SETMASK, &old_sigmask, NULL))
 234             die_e("sigprocmask error");
 235         xcloselog();
 236 
 237         /* Ensure stdout/stderr are sane before exec-ing sendmail */
 238         xclose(STDOUT_FILENO); xopen(STDOUT_FILENO, "/dev/null", O_WRONLY);
 239         xclose(STDERR_FILENO); xopen(STDERR_FILENO, "/dev/null", O_WRONLY);
 240         xclose(jr->output_fd);
 241 
 242         /* Ensure stdin is not appendable ... ? */
 243         /* fdflags = fcntl(0, F_GETFL); fdflags &= ~O_APPEND; */
 244         /* fcntl(0, F_SETFL, fdflags ); */
 245 
 246         /* Here, I basically mirrored the way /usr/sbin/sendmail is called
 247          * by cron on a Debian system, except for the "-oem" and "-or0s"
 248          * options, which don't seem to be appropriate here.
 249          * Hopefully, this will keep all the MTAs happy. */
 250         execl(SENDMAIL, SENDMAIL, "-FAnacron", "-odi",
 251               jr->mailto, (char *)NULL);
 252         die_e("Can't exec " SENDMAIL);
 253     }
 254     /* parent */
 255     /* record mailer pid */
 256     jr->mailer_pid = pid;
 257     running_mailers++;
 258 }
 259 
 260 static void
 261 tend_mailer(job_rec *jr, int status)
     /* [previous][next][first][last][top][bottom][index][help]  */
 262 {
 263     if (WIFEXITED(status) && WEXITSTATUS(status) != 0)
 264         complain("Tried to mail output of job `%s', "
 265                  "but mailer process (" SENDMAIL ") exited with status %d",
 266                  jr->ident, WEXITSTATUS(status));
 267     else if (!WIFEXITED(status) && WIFSIGNALED(status))
 268         complain("Tried to mail output of job `%s', "
 269                  "but mailer process (" SENDMAIL ") got signal %d",
 270                  jr->ident, WTERMSIG(status));
 271     else if (!WIFEXITED(status) && !WIFSIGNALED(status))
 272         complain("Tried to mail output of job `%s', "
 273                  "but mailer process (" SENDMAIL ") terminated abnormally"
 274                  , jr->ident);
 275 
 276     jr->mailer_pid = 0;
 277     running_mailers--;
 278 }
 279 
 280 void
 281 launch_job(job_rec *jr)
     /* [previous][next][first][last][top][bottom][index][help]  */
 282 {
 283     pid_t pid;
 284     int fd;
 285     char hostname[512];
 286     char *mailto;
 287     char *mailfrom;
 288 
 289     /* get hostname */
 290     if (gethostname(hostname, 512)) {
 291       strcpy (hostname,"unknown machine");
 292     }
 293 
 294     setup_env(jr);
 295 
 296     /* Get the destination email address if set, or current user otherwise */
 297     mailto = getenv("MAILTO");
 298 
 299     if (mailto == NULL)
 300         mailto = username();
 301 
 302     /* Get the source email address if set, or current user otherwise */
 303     mailfrom = getenv("MAILFROM");
 304     if (mailfrom == NULL)
 305         mailfrom = username();
 306 
 307     /* create temporary file for stdout and stderr of the job */
 308     temp_file(jr); fd = jr->output_fd;
 309     /* write mail header */
 310     xwrite(fd, "From: ");
 311     xwrite(fd, "Anacron <");
 312     xwrite(fd, mailfrom);
 313     xwrite(fd, ">\n");
 314     xwrite(fd, "To: ");
 315     xwrite(fd, mailto);
 316     xwrite(fd, "\n");
 317     xwrite(fd, "MIME-Version: 1.0\n");
 318     xwrite(fd, "Content-Type: text/plain; charset=\"");
 319     xwrite(fd, nl_langinfo(CODESET));
 320     xwrite(fd, "\"\n");
 321     xwrite(fd, "Subject: Anacron job '");
 322     xwrite(fd, jr->ident);
 323     xwrite(fd, "' on ");
 324     xwrite(fd, hostname);
 325     xwrite(fd, "\n\n");
 326 
 327     if (*mailto == '\0')
 328         jr->mailto = NULL;
 329     else
 330         /* ugly but works without strdup() */
 331         jr->mailto = mailto;
 332 
 333     jr->mail_header_size = file_size(fd);
 334 
 335     pid = xfork();
 336     if (pid == 0)
 337     {
 338         /* child */
 339         in_background = 1;
 340         run_job(jr);
 341         /* execution never gets here */
 342     }
 343     /* parent */
 344     explain("Job `%s' started", jr->ident);
 345     jr->job_pid = pid;
 346     running_jobs++;
 347 }
 348 
 349 static void
 350 tend_job(job_rec *jr, int status)
     /* [previous][next][first][last][top][bottom][index][help]  */
 351 /* Take care of a finished job */
 352 {
 353     int mail_output;
 354     const char *m;
 355 
 356     update_timestamp(jr);
 357     unlock(jr);
 358     if (file_size(jr->output_fd) > jr->mail_header_size) mail_output = 1;
 359     else mail_output = 0;
 360 
 361     m = mail_output ? " (produced output)" : "";
 362     if (WIFEXITED(status) && WEXITSTATUS(status) == 0)
 363         explain("Job `%s' terminated%s", jr->ident, m);
 364     else if (WIFEXITED(status))
 365         explain("Job `%s' terminated (exit status: %d)%s",
 366                 jr->ident, WEXITSTATUS(status), m);
 367     else if (WIFSIGNALED(status))
 368         complain("Job `%s' terminated due to signal %d%s",
 369                  jr->ident, WTERMSIG(status), m);
 370     else /* is this possible? */
 371         complain("Job `%s' terminated abnormally%s", jr->ident, m);
 372 
 373     jr->job_pid = 0;
 374     running_jobs--;
 375     if (mail_output) launch_mailer(jr);
 376     xclose(jr->output_fd);
 377     xclose(jr->input_fd);
 378 }
 379 
 380 void
 381 tend_children(void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 382 /* This is called whenever we get a SIGCHLD.
 383  * Takes care of zombie children.
 384  */
 385 {
 386     int j;
 387     int status;
 388 
 389     j = 0;
 390     while (j < njobs)
 391     {
 392         if (job_array[j]->mailer_pid != 0 &&
 393             xwait(job_array[j]->mailer_pid, &status))
 394             tend_mailer(job_array[j], status);
 395         if (job_array[j]->job_pid != 0 &&
 396             xwait(job_array[j]->job_pid, &status))
 397             tend_job(job_array[j], status);
 398         j++;
 399     }
 400 }

/* [previous][next][first][last][top][bottom][index][help]  */