toybox/toys/pending/crontab.c
<<
>>
Prefs
   1/* crontab.c - files used to schedule the execution of programs.
   2 *
   3 * Copyright 2014 Ranjan Kumar <ranjankumar.bth@gmail.com>
   4 *
   5 * http://pubs.opengroup.org/onlinepubs/9699919799/utilities/crontab.html
   6
   7USE_CRONTAB(NEWTOY(crontab, "c:u:elr[!elr]", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_STAYROOT))
   8
   9config CRONTAB
  10  bool "crontab"
  11  default n
  12  depends on TOYBOX_FORK
  13  help
  14    usage: crontab [-u user] FILE
  15                   [-u user] [-e | -l | -r]
  16                   [-c dir]
  17
  18    Files used to schedule the execution of programs.
  19
  20    -c crontab dir
  21    -e edit user's crontab
  22    -l list user's crontab
  23    -r delete user's crontab
  24    -u user
  25    FILE Replace crontab by FILE ('-': stdin)
  26*/
  27#define FOR_crontab
  28#include "toys.h"
  29
  30GLOBALS(
  31  char *user;
  32  char *cdir;
  33)
  34
  35static char *omitspace(char *line)
  36{
  37  while (*line == ' ' || *line == '\t') line++;
  38  return line;
  39}
  40
  41/*
  42 * Names can also be used for the 'month' and 'day of week' fields
  43 * (First three letters of the particular day or month).
  44 */
  45static int getindex(char *src, int size)
  46{
  47  int i;
  48  char days[]={"sun""mon""tue""wed""thu""fri""sat"};
  49  char months[]={"jan""feb""mar""apr""may""jun""jul"
  50    "aug""sep""oct""nov""dec"};
  51  char *field = (size == 12) ? months : days;
  52
  53  // strings are not allowed for min, hour and dom fields.
  54  if (!(size == 7 || size == 12)) return -1;
  55
  56  for (i = 0; field[i]; i += 3) {
  57    if (!strncasecmp(src, &field[i], 3))
  58      return (i/3);
  59  }
  60  return -1;
  61}
  62
  63static long getval(char *num, long low, long high)
  64{
  65  long val = strtol(num, &num, 10);
  66
  67  if (*num || (val < low) || (val > high)) return -1;
  68  return val;
  69}
  70
  71// Validate minute, hour, day of month, month and day of week fields.
  72static int validate_component(int min, int max, char *src)
  73{
  74  int skip = 0;
  75  char *ptr;
  76
  77  if (!src) return 1;
  78  if ((ptr = strchr(src, '/'))) {
  79    *ptr++ = 0;
  80    if ((skip = getval(ptr, min, (min ? max: max-1))) < 0) return 1;
  81  }
  82
  83  if (*src == '-' || *src == ',') return 1;
  84  if (*src == '*') {
  85    if (*(src+1)) return 1;
  86  }
  87  else {
  88    for (;;) {
  89      char *ctoken = strsep(&src, ","), *dtoken;
  90
  91      if (!ctoken) break;
  92      if (!*ctoken) return 1;
  93
  94      // validate start position.
  95      dtoken = strsep(&ctoken, "-");
  96      if (isdigit(*dtoken)) {
  97        if (getval(dtoken, min, (min ? max : max-1)) < 0) return 1;
  98      } else if (getindex(dtoken, max) < 0) return 1;
  99
 100      // validate end position.
 101      if (!ctoken) {
 102        if (skip) return 1; // case 10/20 or 1,2,4/3
 103      }
 104      else if (*ctoken) {// e.g. N-M
 105        if (isdigit(*ctoken)) {
 106          if (getval(ctoken, min, (min ? max : max-1)) < 0) return 1;
 107        } else if (getindex(ctoken, max) < 0) return 1;
 108      } else return 1; // error condition 'N-'
 109    }
 110  }
 111  return 0;
 112}
 113
 114static int parse_crontab(char *fname)
 115{
 116  char *line;
 117  int lno, fd = xopenro(fname);
 118  long plen = 0;
 119
 120  for (lno = 1; (line = get_rawline(fd, &plen, '\n')); lno++,free(line)) {
 121    char *name, *val, *tokens[5] = {0,}, *ptr = line;
 122    int count = 0;
 123
 124    if (line[plen - 1] == '\n') line[--plen] = '\0';
 125    else {
 126      snprintf(toybuf, sizeof(toybuf), "'%d': premature EOF\n", lno);
 127      goto OUT;
 128    }
 129
 130    ptr = omitspace(ptr);
 131    if (!*ptr || *ptr == '#' || *ptr == '@') continue;
 132    while (count<5) {
 133      int len = strcspn(ptr, " \t");
 134
 135      if (ptr[len]) ptr[len++] = '\0';
 136      tokens[count++] = ptr;
 137      ptr += len;
 138      ptr = omitspace(ptr);
 139      if (!*ptr) break;
 140    }
 141    switch (count) {
 142      case 1: // form SHELL=/bin/sh
 143        name = tokens[0];
 144        if ((val = strchr(name, '='))) *val++ = 0;
 145        if (!val || !*val) {
 146          snprintf(toybuf, sizeof(toybuf), "'%d': %s\n", lno, line);
 147          goto OUT;
 148        }
 149        break;
 150      case 2: // form SHELL =/bin/sh or SHELL= /bin/sh
 151        name = tokens[0];
 152        if ((val = strchr(name, '='))) {
 153          *val = 0;
 154          val = tokens[1];
 155        } else {
 156          if (*(tokens[1]) != '=') {
 157            snprintf(toybuf, sizeof(toybuf), "'%d': %s\n", lno, line);
 158            goto OUT;
 159          }
 160          val = tokens[1] + 1;
 161        }
 162        if (!*val) {
 163          snprintf(toybuf, sizeof(toybuf), "'%d': %s\n", lno, line);
 164          goto OUT;
 165        }
 166        break;
 167      case 3: // NAME = VAL
 168        name = tokens[0];
 169        val = tokens[2];
 170        if (*(tokens[1]) != '=') {
 171          snprintf(toybuf, sizeof(toybuf), "'%d': %s\n", lno, line);
 172          goto OUT;
 173        }
 174        break;
 175      default:
 176        if (validate_component(0, 60, tokens[0])) {
 177          snprintf(toybuf, sizeof(toybuf), "'%d': bad minute\n", lno);
 178          goto OUT;
 179        }
 180        if (validate_component(0, 24, tokens[1])) {
 181          snprintf(toybuf, sizeof(toybuf), "'%d': bad hour\n", lno);
 182          goto OUT;
 183        }
 184        if (validate_component(1, 31, tokens[2])) {
 185          snprintf(toybuf, sizeof(toybuf), "'%d': bad day-of-month\n", lno);
 186          goto OUT;
 187        }
 188        if (validate_component(1, 12, tokens[3])) {
 189          snprintf(toybuf, sizeof(toybuf), "'%d': bad month\n", lno);
 190          goto OUT;
 191        }
 192        if (validate_component(0, 7, tokens[4])) {
 193          snprintf(toybuf, sizeof(toybuf), "'%d': bad day-of-week\n", lno);
 194          goto OUT;
 195        }
 196        if (!*ptr) { // don't have any cmd to execute.
 197          snprintf(toybuf, sizeof(toybuf), "'%d': bad command\n", lno);
 198          goto OUT;
 199        }
 200        break;
 201    }
 202  }
 203  xclose(fd);
 204  return 0;
 205OUT:
 206  free(line);
 207  printf("Error at line no %s", toybuf);
 208  xclose(fd);
 209  return 1;
 210}
 211
 212static void do_list(char *name)
 213{
 214  int fdin;
 215
 216  snprintf(toybuf, sizeof(toybuf), "%s%s", TT.cdir, name);
 217  fdin = xopenro(toybuf);
 218  xsendfile(fdin, 1);
 219  xclose(fdin);
 220}
 221
 222static void do_remove(char *name)
 223{
 224  snprintf(toybuf, sizeof(toybuf), "%s%s", TT.cdir, name);
 225  if (unlink(toybuf))
 226    error_exit("No crontab for '%s'", name);
 227}
 228
 229static void update_crontab(char *src, char *dest)
 230{
 231  int fdin, fdout;
 232
 233  snprintf(toybuf, sizeof(toybuf), "%s%s", TT.cdir, dest);
 234  fdout = xcreate(toybuf, O_WRONLY|O_CREAT|O_TRUNC, 0600);
 235  fdin = xopenro(src);
 236  xsendfile(fdin, fdout);
 237  xclose(fdin);
 238
 239  fchown(fdout, getuid(), geteuid());
 240  xclose(fdout);
 241}
 242
 243static void do_replace(char *name)
 244{
 245  char *fname = *toys.optargs ? *toys.optargs : "-";
 246  char tname[] = "/tmp/crontab.XXXXXX";
 247
 248  if ((*fname == '-') && !*(fname+1)) {
 249    int tfd = mkstemp(tname);
 250
 251    if (tfd < 0) perror_exit("mkstemp");
 252    xsendfile(0, tfd);
 253    xclose(tfd);
 254    fname = tname;
 255  }
 256
 257  if (parse_crontab(fname))
 258    error_exit("errors in crontab file '%s', can't install.", fname);
 259  update_crontab(fname, name);
 260  unlink(tname);
 261}
 262
 263static void do_edit(struct passwd *pwd)
 264{
 265  struct stat sb;
 266  time_t mtime = 0;
 267  int srcfd, destfd, status;
 268  pid_t pid, cpid;
 269  char tname[] = "/tmp/crontab.XXXXXX";
 270
 271  if ((destfd = mkstemp(tname)) < 0)
 272    perror_exit("Can't open tmp file");
 273
 274  fchmod(destfd, 0666);
 275  snprintf(toybuf, sizeof(toybuf), "%s%s", TT.cdir, pwd->pw_name);
 276
 277  if (!stat(toybuf, &sb)) { // file exists and have some content.
 278    if (sb.st_size) {
 279      srcfd = xopenro(toybuf);
 280      xsendfile(srcfd, destfd);
 281      xclose(srcfd);
 282    }
 283  } else printf("No crontab for '%s'- using an empty one\n", pwd->pw_name);
 284  xclose(destfd);
 285
 286  if (!stat(tname, &sb)) mtime = sb.st_mtime;
 287
 288RETRY:
 289  if (!(pid = xfork())) {
 290    char *prog = pwd->pw_shell;
 291
 292    xsetuser(pwd);
 293    if (pwd->pw_uid) {
 294      if (setenv("USER", pwd->pw_name, 1)) _exit(1);
 295      if (setenv("LOGNAME", pwd->pw_name, 1)) _exit(1);
 296    }
 297    if (setenv("HOME", pwd->pw_dir, 1)) _exit(1);
 298    if (setenv("SHELL",((!prog || !*prog) ? "/bin/sh" : prog), 1)) _exit(1);
 299
 300    if (!(prog = getenv("VISUAL"))) {
 301      if (!(prog = getenv("EDITOR")))
 302        prog = "vi";
 303    }
 304    execlp(prog, prog, tname, (char *) NULL);
 305    perror_exit("can't execute '%s'", prog);
 306  }
 307
 308  // Parent Process.
 309  do {
 310    cpid = waitpid(pid, &status, 0);
 311  } while ((cpid == -1) && (errno == EINTR));
 312
 313  if (!stat(tname, &sb) && (mtime == sb.st_mtime)) {
 314    printf("%s: no changes made to crontab\n", toys.which->name);
 315    unlink(tname);
 316    return;
 317  }
 318  printf("%s: installing new crontab\n", toys.which->name);
 319  if (parse_crontab(tname)) {
 320    fprintf(stderr, "errors in crontab file, can't install.\n"
 321        "Do you want to retry the same edit? ");
 322    if (!yesno(0)) {
 323      error_msg("edits left in '%s'", tname);
 324      return;
 325    }
 326    goto RETRY;
 327  }
 328  // parsing of crontab success; update the crontab.
 329  update_crontab(tname, pwd->pw_name);
 330  unlink(tname);
 331}
 332
 333void crontab_main(void)
 334{
 335  struct passwd *pwd = NULL;
 336  long FLAG_elr = toys.optflags & (FLAG_e|FLAG_l|FLAG_r);
 337
 338  if (TT.cdir && (TT.cdir[strlen(TT.cdir)-1] != '/'))
 339    TT.cdir = xmprintf("%s/", TT.cdir);
 340  if (!TT.cdir) TT.cdir = xstrdup("/var/spool/cron/crontabs/");
 341
 342  if (toys.optflags & FLAG_u) {
 343    if (getuid()) error_exit("must be privileged to use -u");
 344    pwd = xgetpwnam(TT.user);
 345  } else pwd = xgetpwuid(getuid());
 346
 347  if (!toys.optc) {
 348    if (!FLAG_elr) {
 349      if (toys.optflags & FLAG_u) 
 350        help_exit("file name must be specified for replace");
 351      do_replace(pwd->pw_name);
 352    }
 353    else if (toys.optflags & FLAG_e) do_edit(pwd);
 354    else if (toys.optflags & FLAG_l) do_list(pwd->pw_name);
 355    else if (toys.optflags & FLAG_r) do_remove(pwd->pw_name);
 356  } else {
 357    if (FLAG_elr) help_exit("no arguments permitted after this option");
 358    do_replace(pwd->pw_name);
 359  }
 360  if (!(toys.optflags & FLAG_c)) free(TT.cdir);
 361}
 362