home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 October / usenetsourcesnewsgroupsinfomagicoctober1994disk2.iso / unix / volume27 / vixie-cron / part02 / entry.c < prev    next >
C/C++ Source or Header  |  1994-01-16  |  12KB  |  508 lines

  1. /* Copyright 1988,1990,1993,1994 by Paul Vixie
  2.  * All rights reserved
  3.  *
  4.  * Distribute freely, except: don't remove my name from the source or
  5.  * documentation (don't take credit for my work), mark your changes (don't
  6.  * get me blamed for your possible bugs), don't alter or remove this
  7.  * notice.  May be sold if buildable source is provided to buyer.  No
  8.  * warrantee of any kind, express or implied, is included with this
  9.  * software; use at your own risk, responsibility for damages (if any) to
  10.  * anyone resulting from the use of this software rests entirely with the
  11.  * user.
  12.  *
  13.  * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
  14.  * I'll try to keep a version up to date.  I can be reached as follows:
  15.  * Paul Vixie          <paul@vix.com>          uunet!decwrl!vixie!paul
  16.  */
  17.  
  18. #if !defined(lint) && !defined(LINT)
  19. static char rcsid[] = "$Id: entry.c,v 2.12 1994/01/17 03:20:37 vixie Exp $";
  20. #endif
  21.  
  22. /* vix 26jan87 [RCS'd; rest of log is in RCS file]
  23.  * vix 01jan87 [added line-level error recovery]
  24.  * vix 31dec86 [added /step to the from-to range, per bob@acornrc]
  25.  * vix 30dec86 [written]
  26.  */
  27.  
  28.  
  29. #include "cron.h"
  30.  
  31.  
  32. typedef    enum ecode {
  33.     e_none, e_minute, e_hour, e_dom, e_month, e_dow,
  34.     e_cmd, e_timespec, e_username
  35. } ecode_e;
  36.  
  37. static char    get_list __P((bitstr_t *, int, int, char *[], int, FILE *)),
  38.         get_range __P((bitstr_t *, int, int, char *[], int, FILE *)),
  39.         get_number __P((int *, int, char *[], int, FILE *));
  40. static int    set_element __P((bitstr_t *, int, int, int));
  41.  
  42. static char *ecodes[] =
  43.     {
  44.         "no error",
  45.         "bad minute",
  46.         "bad hour",
  47.         "bad day-of-month",
  48.         "bad month",
  49.         "bad day-of-week",
  50.         "bad command",
  51.         "bad time specifier",
  52.         "bad username",
  53.     };
  54.  
  55.  
  56. void
  57. free_entry(e)
  58.     entry    *e;
  59. {
  60.     free(e->cmd);
  61.     env_free(e->envp);
  62.     free(e);
  63. }
  64.  
  65.  
  66. /* return NULL if eof or syntax error occurs;
  67.  * otherwise return a pointer to a new entry.
  68.  */
  69. entry *
  70. load_entry(file, error_func, pw, envp)
  71.     FILE        *file;
  72.     void        (*error_func)();
  73.     struct passwd    *pw;
  74.     char        **envp;
  75. {
  76.     /* this function reads one crontab entry -- the next -- from a file.
  77.      * it skips any leading blank lines, ignores comments, and returns
  78.      * EOF if for any reason the entry can't be read and parsed.
  79.      *
  80.      * the entry is also parsed here.
  81.      *
  82.      * syntax:
  83.      *   user crontab:
  84.      *    minutes hours doms months dows cmd\n
  85.      *   system crontab (/etc/crontab):
  86.      *    minutes hours doms months dows USERNAME cmd\n
  87.      */
  88.  
  89.     ecode_e    ecode = e_none;
  90.     entry    *e;
  91.     int    ch;
  92.     char    cmd[MAX_COMMAND];
  93.     char    envstr[MAX_ENVSTR];
  94.  
  95.     Debug(DPARS, ("load_entry()...about to eat comments\n"))
  96.  
  97.     skip_comments(file);
  98.  
  99.     ch = get_char(file);
  100.     if (ch == EOF)
  101.         return NULL;
  102.  
  103.     /* ch is now the first useful character of a useful line.
  104.      * it may be an @special or it may be the first character
  105.      * of a list of minutes.
  106.      */
  107.  
  108.     e = (entry *) calloc(sizeof(entry), sizeof(char));
  109.  
  110.     if (ch == '@') {
  111.         /* all of these should be flagged and load-limited; i.e.,
  112.          * instead of @hourly meaning "0 * * * *" it should mean
  113.          * "close to the front of every hour but not 'til the
  114.          * system load is low".  Problems are: how do you know
  115.          * what "low" means? (save me from /etc/cron.conf!) and:
  116.          * how to guarantee low variance (how low is low?), which
  117.          * means how to we run roughly every hour -- seems like
  118.          * we need to keep a history or let the first hour set
  119.          * the schedule, which means we aren't load-limited
  120.          * anymore.  too much for my overloaded brain. (vix, jan90)
  121.          * HINT
  122.          */
  123.         ch = get_string(cmd, MAX_COMMAND, file, " \t\n");
  124.         if (!strcmp("reboot", cmd)) {
  125.             e->flags |= WHEN_REBOOT;
  126.         } else if (!strcmp("yearly", cmd) || !strcmp("annually", cmd)){
  127.             bit_set(e->minute, 0);
  128.             bit_set(e->hour, 0);
  129.             bit_set(e->dom, 0);
  130.             bit_set(e->month, 0);
  131.             bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
  132.         } else if (!strcmp("monthly", cmd)) {
  133.             bit_set(e->minute, 0);
  134.             bit_set(e->hour, 0);
  135.             bit_set(e->dom, 0);
  136.             bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
  137.             bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
  138.         } else if (!strcmp("weekly", cmd)) {
  139.             bit_set(e->minute, 0);
  140.             bit_set(e->hour, 0);
  141.             bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1));
  142.             bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
  143.             bit_set(e->dow, 0);
  144.         } else if (!strcmp("daily", cmd) || !strcmp("midnight", cmd)) {
  145.             bit_set(e->minute, 0);
  146.             bit_set(e->hour, 0);
  147.             bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1));
  148.             bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
  149.             bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
  150.         } else if (!strcmp("hourly", cmd)) {
  151.             bit_set(e->minute, 0);
  152.             bit_set(e->hour, (LAST_HOUR-FIRST_HOUR+1));
  153.             bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1));
  154.             bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
  155.             bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
  156.         } else {
  157.             ecode = e_timespec;
  158.             goto eof;
  159.         }
  160.     } else {
  161.         Debug(DPARS, ("load_entry()...about to parse numerics\n"))
  162.  
  163.         ch = get_list(e->minute, FIRST_MINUTE, LAST_MINUTE,
  164.                   PPC_NULL, ch, file);
  165.         if (ch == EOF) {
  166.             ecode = e_minute;
  167.             goto eof;
  168.         }
  169.  
  170.         /* hours
  171.          */
  172.  
  173.         ch = get_list(e->hour, FIRST_HOUR, LAST_HOUR,
  174.                   PPC_NULL, ch, file);
  175.         if (ch == EOF) {
  176.             ecode = e_hour;
  177.             goto eof;
  178.         }
  179.  
  180.         /* DOM (days of month)
  181.          */
  182.  
  183.         if (ch == '*')
  184.             e->flags |= DOM_STAR;
  185.         ch = get_list(e->dom, FIRST_DOM, LAST_DOM,
  186.                   PPC_NULL, ch, file);
  187.         if (ch == EOF) {
  188.             ecode = e_dom;
  189.             goto eof;
  190.         }
  191.  
  192.         /* month
  193.          */
  194.  
  195.         ch = get_list(e->month, FIRST_MONTH, LAST_MONTH,
  196.                   MonthNames, ch, file);
  197.         if (ch == EOF) {
  198.             ecode = e_month;
  199.             goto eof;
  200.         }
  201.  
  202.         /* DOW (days of week)
  203.          */
  204.  
  205.         if (ch == '*')
  206.             e->flags |= DOW_STAR;
  207.         ch = get_list(e->dow, FIRST_DOW, LAST_DOW,
  208.                   DowNames, ch, file);
  209.         if (ch == EOF) {
  210.             ecode = e_dow;
  211.             goto eof;
  212.         }
  213.     }
  214.  
  215.     /* make sundays equivilent */
  216.     if (bit_test(e->dow, 0) || bit_test(e->dow, 7)) {
  217.         bit_set(e->dow, 0);
  218.         bit_set(e->dow, 7);
  219.     }
  220.  
  221.     /* ch is the first character of a command, or a username */
  222.     unget_char(ch, file);
  223.  
  224.     if (!pw) {
  225.         char        *username = cmd;    /* temp buffer */
  226.  
  227.         Debug(DPARS, ("load_entry()...about to parse username\n"))
  228.         ch = get_string(username, MAX_COMMAND, file, " \t");
  229.  
  230.         Debug(DPARS, ("load_entry()...got %s\n",username))
  231.         if (ch == EOF) {
  232.             ecode = e_cmd;
  233.             goto eof;
  234.         }
  235.  
  236.         pw = getpwnam(username);
  237.         if (pw == NULL) {
  238.             ecode = e_username;
  239.             goto eof;
  240.         }
  241.         Debug(DPARS, ("load_entry()...uid %d, gid %d\n",e->uid,e->gid))
  242.     }
  243.  
  244.     e->uid = pw->pw_uid;
  245.     e->gid = pw->pw_gid;
  246.  
  247.     /* copy and fix up environment.  some variables are just defaults and
  248.      * others are overrides.
  249.      */
  250.     e->envp = env_copy(envp);
  251.     if (!env_get("SHELL", e->envp)) {
  252.         sprintf(envstr, "SHELL=%s", _PATH_BSHELL);
  253.         e->envp = env_set(e->envp, envstr);
  254.     }
  255.     if (!env_get("HOME", e->envp)) {
  256.         sprintf(envstr, "HOME=%s", pw->pw_dir);
  257.         e->envp = env_set(e->envp, envstr);
  258.     }
  259.     if (!env_get("PATH", e->envp)) {
  260.         sprintf(envstr, "PATH=%s", _PATH_DEFPATH);
  261.         e->envp = env_set(e->envp, envstr);
  262.     }
  263.     sprintf(envstr, "%s=%s", "LOGNAME", pw->pw_name);
  264.     e->envp = env_set(e->envp, envstr);
  265. #if defined(BSD)
  266.     sprintf(envstr, "%s=%s", "USER", pw->pw_name);
  267.     e->envp = env_set(e->envp, envstr);
  268. #endif
  269.  
  270.     Debug(DPARS, ("load_entry()...about to parse command\n"))
  271.  
  272.     /* Everything up to the next \n or EOF is part of the command...
  273.      * too bad we don't know in advance how long it will be, since we
  274.      * need to malloc a string for it... so, we limit it to MAX_COMMAND.
  275.      * XXX - should use realloc().
  276.      */ 
  277.     ch = get_string(cmd, MAX_COMMAND, file, "\n");
  278.  
  279.     /* a file without a \n before the EOF is rude, so we'll complain...
  280.      */
  281.     if (ch == EOF) {
  282.         ecode = e_cmd;
  283.         goto eof;
  284.     }
  285.  
  286.     /* got the command in the 'cmd' string; save it in *e.
  287.      */
  288.     e->cmd = strdup(cmd);
  289.  
  290.     Debug(DPARS, ("load_entry()...returning successfully\n"))
  291.  
  292.     /* success, fini, return pointer to the entry we just created...
  293.      */
  294.     return e;
  295.  
  296.  eof:
  297.     free(e);
  298.     if (ecode != e_none && error_func)
  299.         (*error_func)(ecodes[(int)ecode]);
  300.     while (ch != EOF && ch != '\n')
  301.         ch = get_char(file);
  302.     return NULL;
  303. }
  304.  
  305.  
  306. static char
  307. get_list(bits, low, high, names, ch, file)
  308.     bitstr_t    *bits;        /* one bit per flag, default=FALSE */
  309.     int        low, high;    /* bounds, impl. offset for bitstr */
  310.     char        *names[];    /* NULL or *[] of names for these elements */
  311.     int        ch;        /* current character being processed */
  312.     FILE        *file;        /* file being read */
  313. {
  314.     register int    done;
  315.  
  316.     /* we know that we point to a non-blank character here;
  317.      * must do a Skip_Blanks before we exit, so that the
  318.      * next call (or the code that picks up the cmd) can
  319.      * assume the same thing.
  320.      */
  321.  
  322.     Debug(DPARS|DEXT, ("get_list()...entered\n"))
  323.  
  324.     /* list = range {"," range}
  325.      */
  326.     
  327.     /* clear the bit string, since the default is 'off'.
  328.      */
  329.     bit_nclear(bits, 0, (high-low+1));
  330.  
  331.     /* process all ranges
  332.      */
  333.     done = FALSE;
  334.     while (!done) {
  335.         ch = get_range(bits, low, high, names, ch, file);
  336.         if (ch == ',')
  337.             ch = get_char(file);
  338.         else
  339.             done = TRUE;
  340.     }
  341.  
  342.     /* exiting.  skip to some blanks, then skip over the blanks.
  343.      */
  344.     Skip_Nonblanks(ch, file)
  345.     Skip_Blanks(ch, file)
  346.  
  347.     Debug(DPARS|DEXT, ("get_list()...exiting w/ %02x\n", ch))
  348.  
  349.     return ch;
  350. }
  351.  
  352.  
  353. static char
  354. get_range(bits, low, high, names, ch, file)
  355.     bitstr_t    *bits;        /* one bit per flag, default=FALSE */
  356.     int        low, high;    /* bounds, impl. offset for bitstr */
  357.     char        *names[];    /* NULL or names of elements */
  358.     int        ch;        /* current character being processed */
  359.     FILE        *file;        /* file being read */
  360. {
  361.     /* range = number | number "-" number [ "/" number ]
  362.      */
  363.  
  364.     register int    i;
  365.     auto int    num1, num2, num3;
  366.  
  367.     Debug(DPARS|DEXT, ("get_range()...entering, exit won't show\n"))
  368.  
  369.     if (ch == '*') {
  370.         /* '*' means "first-last" but can still be modified by /step
  371.          */
  372.         num1 = low;
  373.         num2 = high;
  374.         ch = get_char(file);
  375.         if (ch == EOF)
  376.             return EOF;
  377.     } else {
  378.         if (EOF == (ch = get_number(&num1, low, names, ch, file)))
  379.             return EOF;
  380.  
  381.         if (ch != '-') {
  382.             /* not a range, it's a single number.
  383.              */
  384.             if (EOF == set_element(bits, low, high, num1))
  385.                 return EOF;
  386.             return ch;
  387.         } else {
  388.             /* eat the dash
  389.              */
  390.             ch = get_char(file);
  391.             if (ch == EOF)
  392.                 return EOF;
  393.  
  394.             /* get the number following the dash
  395.              */
  396.             ch = get_number(&num2, low, names, ch, file);
  397.             if (ch == EOF)
  398.                 return EOF;
  399.         }
  400.     }
  401.  
  402.     /* check for step size
  403.      */
  404.     if (ch == '/') {
  405.         /* eat the slash
  406.          */
  407.         ch = get_char(file);
  408.         if (ch == EOF)
  409.             return EOF;
  410.  
  411.         /* get the step size -- note: we don't pass the
  412.          * names here, because the number is not an
  413.          * element id, it's a step size.  'low' is
  414.          * sent as a 0 since there is no offset either.
  415.          */
  416.         ch = get_number(&num3, 0, PPC_NULL, ch, file);
  417.         if (ch == EOF)
  418.             return EOF;
  419.     } else {
  420.         /* no step.  default==1.
  421.          */
  422.         num3 = 1;
  423.     }
  424.  
  425.     /* range. set all elements from num1 to num2, stepping
  426.      * by num3.  (the step is a downward-compatible extension
  427.      * proposed conceptually by bob@acornrc, syntactically
  428.      * designed then implmented by paul vixie).
  429.      */
  430.     for (i = num1;  i <= num2;  i += num3)
  431.         if (EOF == set_element(bits, low, high, i))
  432.             return EOF;
  433.  
  434.     return ch;
  435. }
  436.  
  437.  
  438. static char
  439. get_number(numptr, low, names, ch, file)
  440.     int    *numptr;    /* where does the result go? */
  441.     int    low;        /* offset applied to result if symbolic enum used */
  442.     char    *names[];    /* symbolic names, if any, for enums */
  443.     int    ch;        /* current character */
  444.     FILE    *file;        /* source */
  445. {
  446.     char    temp[MAX_TEMPSTR], *pc;
  447.     int    len, i, all_digits;
  448.  
  449.     /* collect alphanumerics into our fixed-size temp array
  450.      */
  451.     pc = temp;
  452.     len = 0;
  453.     all_digits = TRUE;
  454.     while (isalnum(ch)) {
  455.         if (++len >= MAX_TEMPSTR)
  456.             return EOF;
  457.  
  458.         *pc++ = ch;
  459.  
  460.         if (!isdigit(ch))
  461.             all_digits = FALSE;
  462.  
  463.         ch = get_char(file);
  464.     }
  465.     *pc = '\0';
  466.  
  467.     /* try to find the name in the name list
  468.      */
  469.     if (names) {
  470.         for (i = 0;  names[i] != NULL;  i++) {
  471.             Debug(DPARS|DEXT,
  472.                 ("get_num, compare(%s,%s)\n", names[i], temp))
  473.             if (!strcasecmp(names[i], temp)) {
  474.                 *numptr = i+low;
  475.                 return ch;
  476.             }
  477.         }
  478.     }
  479.  
  480.     /* no name list specified, or there is one and our string isn't
  481.      * in it.  either way: if it's all digits, use its magnitude.
  482.      * otherwise, it's an error.
  483.      */
  484.     if (all_digits) {
  485.         *numptr = atoi(temp);
  486.         return ch;
  487.     }
  488.  
  489.     return EOF;
  490. }
  491.  
  492.  
  493. static int
  494. set_element(bits, low, high, number)
  495.     bitstr_t    *bits;         /* one bit per flag, default=FALSE */
  496.     int        low;
  497.     int        high;
  498.     int        number;
  499. {
  500.     Debug(DPARS|DEXT, ("set_element(?,%d,%d,%d)\n", low, high, number))
  501.  
  502.     if (number < low || number > high)
  503.         return EOF;
  504.  
  505.     bit_set(bits, (number-low));
  506.     return OK;
  507. }
  508.