[comp.os.minix] A Revised Cron Program

sampson@killer.DALLAS.TX.US (Steve Sampson) (01/01/89)

Here's a newer cron that needs to be tested.  If the Library routines are
broke, change them.  (fopen() doesn't create a file when it doesn't exist
for append).  I'd test it myself but I'm waiting for 1.3, Steve.

/*
 *      Create On Utility
 *
 *      Version 1.04, Public Domain (p) January 1989
 *      S. Sampson, with ideas from W. Schroeder, and W. Paul
 *
 *      The cron.c program operates as a daemon, waking up every minute
 *      to execute the CRONTAB table.  Logging errors to CRONERR.
 *      The cron table file is left on disk and not moved to RAM, and
 *      the command character to signify a newline '%' is not used.
 *
 *      Put in Minix /etc/rc as: cron
 *
 *      Some cron table entry examples:
 *
 *      Print the date on console every minute:
 *              * * * * * date >/dev/tty0
 *
 *      Print the date on console on the hour, every hour:
 *              0 * * * * date >/dev/tty0
 *
 *      Backup the files at 4:30 am every day except Sat and Sun:
 *              30 4 * * 1-5 /etc/backup /dev/st1
 *
 *      Backup the files every other day at 7:30 pm:
 *              30 19 * * 1,3,5 /etc/backup /dev/st1
 */

#include <stdio.h>
#include <signal.h>
#include <errno.h>

#define CRONTAB "/usr/act/crontab"
#define CRONERR "/usr/act/cronerr"

#define MAXLINE 132
#define SIZE    64

#ifndef TRUE
#define FALSE   0
#define TRUE    ~FALSE
#endif

#ifndef NULL
#define NULL    0
#endif

#define max(x,y)        (((y) <= (x)) ? (y) : (x))

struct tm      {
       int     tm_sec;
       int     tm_min;
       int     tm_hour;
       int     tm_mday;
       int     tm_mon;
       int     tm_year;
       int     tm_wday;
       int     tm_yday;
       int     tm_isdst;
};

char      *ctime();
struct tm *localtime();

FILE      *fp, *err;
int       eof;
char      min[SIZE], hour[SIZE], day[SIZE],
          month[SIZE], wday[SIZE], command[SIZE];
char      *tokv[] = { min, hour, day, month, wday };

main()
{
        void    wakeup(), killer();
        long    tick;

        if (fork() != 0)
                _exit(0);

        signal(SIGINT,  SIG_IGN);
        signal(SIGHUP,  SIG_IGN);
        signal(SIGQUIT, SIG_IGN);

        /*
         *      fopen() will create the file if it does not exist.
         *      Any other errors will be output to stderr at startup.
         */

        if ((err = fopen(CRONERR, "a")) == (FILE *)NULL)  {
                perror("cron");
                exit(1);
        }

        setbuf(err, (char *)NULL);      /* error I/O is now unbuffered */
        close(0); close(1); close(2);   /* close all I/O paths */

        /* start at the top of the next minute, truncate seconds */

        tick = ((time((long *)0) / 60L) + 1L) * 60L;

        /* here we go... */

        for (;;)  {

                /* do this cycle every minute */

                if (time((long *)0) >= tick)  {
                        wakeup(tick);
                        tick += 60L;
                }

                /* kill zombies for one second */

                signal(SIGALRM, killer);
                alarm(1);
                while (wait((int *)NULL) != -1)
                        ;
                alarm(0);

                sleep(max((unsigned)60, (unsigned)(tick - time((long *)0))));
        }
}


/*
 *      Cron does not wait for child to die.  Therefore children
 *      become zombies when they complete.  In order to clear them
 *      out the parent must execute wait() every now and then.
 */

void killer()
{
        /* edit the cron.s file and remove the pushin-n-popin */
}

void wakeup(tick)
long    tick;
{
        register struct tm *tm = localtime(&tick);

        if (access(CRONTAB, 4) == -1)  {
                fprintf(err, "Can't read %s %s", CRONTAB, ctime(&tick));
                return;
        }  else
                fp = fopen(CRONTAB, "r");

        eof = FALSE;

        while (!eof)  {
                if (getline() && match(min,tm->tm_min)
                              && match(hour,tm->tm_hour)
                              && match(day,tm->tm_mday)
                              && match(month,tm->tm_mon)
                              && match(wday,tm->tm_wday))  {

                   /*
                    *  Execute command in the shell
                    */

                   if (fork() == 0)  {

                        execl("/bin/sh", "sh", "-c", command, (char *)NULL);

                        fprintf(err, "Can't access /bin/sh %s", ctime(&tick));
                        fclose(err);
                        fclose(fp);

                        exit(1);
                   }
                }
        }

        fclose(fp);
}

/*
 *      A line consists of six fields.  The first five are:
 *
 *              minute:         0-59
 *              hour:           0-23
 *              day:            1-31
 *              month:          1-12
 *              weekday:        0-6 (Sunday = 0)
 *
 *      The fields are seperated by spaces or tabs, with the
 *      first field left justified (no leading spaces or tabs).
 *      See below for optional field syntax.
 *
 *      The last field is the command field.  This command will
 *      be executed by the shell just as if typed from a console.
 */

getline()
{
        register char   *p;
        register int    i;
        char            buffer[MAXLINE];
        extern   char   *scanner();

        if (fgets(buffer, sizeof buffer, fp) == (char *)NULL)  {
                eof = TRUE;
                return(FALSE);
        }

        for (p = buffer, i = 0; i < 5; i++)  {
                if ((p = scanner(tokv[i], p)) == (char *)NULL)
                        return(FALSE);
        }

        strcpy(command, p);     /* scoop the command */
        return(TRUE);
}

char *scanner(token, offset)
register char   *token;         /* target buffer to receive scanned token */
register char   *offset;        /* place holder into source buffer */
{
        while ((*offset != ' ') && (*offset != '\t') && *offset)
                *token++ = *offset++;

        /*
         *      Check for possible error condition
         */
         
        if (!*offset)
                return ((char *)NULL);

        *token = '\0';
        
        while ((*offset == ' ') || (*offset == '\t'))
                offset++;

        return (offset);
}

/*
 *      This routine will match the left string with the right number.
 *
 *      The string can contain the following syntax:
 *
 *      *               This will return TRUE for any number
 *      x,y [,z, ...]   This will return TRUE for any number given.
 *      x-y             This will return TRUE for any number within
 *                      the range of x thru y.
 */

match(left, right)
register char   *left;
register int    right;
{
        register int    n;
        register char   c;

        n = 0;
        if (!strcmp(left, "*"))
                return(TRUE);

        while ((c = *left++) && (c >= '0') && (c <= '9'))
                n  =  (n * 10) + c - '0';

        switch (c)  {
                case '\0':
                        return (right == n);

                case ',':
                        if (right == n)
                                return(TRUE);

                        do  {
                              n = 0;
                              while ((c = *left++) && (c >= '0') && (c <= '9'))
                                        n = (n * 10) + c - '0';
                              if (right == n)
                                        return(TRUE);
                        } while (c == ',');

                        return(FALSE);

                case '-':
                        if (right < n)
                                return(FALSE);

                        n = 0;
                        while ((c = *left++) && (c >= '0') && (c <= '9'))
                                n = (n * 10) + c - '0';

                        return(right <= n);
        }
}

/*
 *      ctime.c and localtime as posted to comp.os.minix
 */

int     days_per_month[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
char    *months[] =   { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
                     "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
char    *days[] = { "Thu", "Fri", "Sat", "Sun", "Mon", "Tue", "Wed" };

struct  tm   stm;

long    s_p_min;
long    s_p_hour;
long    s_p_day;
long    s_p_wday;
long    s_p_year;

char    t_buf[26];

char *ctime(pclock)
long    *pclock;
{
        long    tt;

        s_p_min  = 60L;
        s_p_hour = 60L * 60L;
        s_p_day  = 60L * 60L * 24L;
        s_p_year = 60L * 60L * 24L * 365L;

        tt = *pclock;
        cv_time(tt);

        sprintf(t_buf, "%s %s %02d %02d:%02d:%02d %04d\n",
           days[(tt / s_p_day) % 7], months[stm.tm_mon], stm.tm_mday,
           stm.tm_hour, stm.tm_min, stm.tm_sec, stm.tm_year); 

        return(t_buf);
}

struct tm *localtime(pclock)
long    *pclock;
{
        cv_time(*pclock);

        return &stm;
}

cv_time(t)
long    t;
{
        stm.tm_sec  = 0;
        stm.tm_min  = 0;
        stm.tm_hour = 0;
        stm.tm_mday = 1;
        stm.tm_mon  = 0;
        stm.tm_year = 0;
        stm.tm_wday = 0;
        stm.tm_yday = 0;
        stm.tm_isdst= 0;

        while (t >= s_p_year)  {
                if (((stm.tm_year + 2) % 4) == 0)
                        t -= s_p_day;

                stm.tm_year += 1;
                t -= s_p_year;
        }

        if (((stm.tm_year + 2) % 4) == 0)
                days_per_month[1]++;

        stm.tm_year += 1970;

        while ( t >= (days_per_month[stm.tm_mon] * s_p_day))
                t -= days_per_month[stm.tm_mon++] * s_p_day;

        while (t >= s_p_day) {
                t -= s_p_day;
                stm.tm_mday++;
        }

        while (t >= s_p_hour) {
                t -= s_p_hour;
                stm.tm_hour++;
        }

        while (t >= s_p_min) {
                t -= s_p_min;
                stm.tm_min++;
        }

        stm.tm_sec = (int)t;
}

/* EOF */