[comp.os.minix] Cron for MINIX

schro@nixpbe (02/10/88)

echo x - README
gres '^X' '' > README << '/'

Subject: cron for MINIX

This is cron.c posted to comp.sources.wanted by S. R. Sampson
modified for MINIX by W. F. Schroeder.

1. unshar this file.
2. move time.h to the include directory ( /usr/include )
3. run make
4. move cron to /usr/bin
5. make directory /usr/act
6. write the following line to /usr/act/crontab
       * * * * * date >/dev/tty0
7. start cron
       cron &

Cron will immediately create /usr/act/cronerr which is a log file for
error messages. The file /usr/act/crontab will be processed at every
full minute.
----------------------------------------------------------------------------

Remarks:
Increase the stack size of /bin/sh if you get an "out of string space" 
error during step 1. Don't forget to reduce it after completion!

Cron needs time.h, ctime() and localtime() which are not in the standard 
library (at least not on MINIX 1.1), so I wrote simple versions. 
They know nothing about leap seconds or daylight savings time. 
Localtime() should better be called gmtime() since it assumes 
that time() returns the local time.

Fopen() does NOT create the file when you try to open it for appending.
This should be corrected in future MINIX versions.
I added a few lines just to make sure the file already exists.
The other solution is to throw away the old file and start a new one.
Set APPEND to 1 to keep the old file.

For debugging purposes I added some code to write additional information to
the log file. Every time a commands is executed or a process terminates
the current time, pid and command is recorded.
Sample:
Mon Feb  8 22:38:01 1988   275 date >/dev/tty0
Mon Feb  8 22:38:02 1988   275 terminated
Mon Feb  8 22:39:02 1988   277 date >/dev/tty0
Mon Feb  8 22:39:03 1988   277 terminated

Set LOG to 0 to log error messages only.
----------------------------------------------------------------------------


May the force be with you!

W.F.Schroeder

+---------------+------------------------+-----------------------------+
| W.F.Schroeder |  Workstation Software  | Paper-: Nixdorf Computer AG |
+---------------+------------------------+ mail  : Pontanusstr. 55     |
| UUCP: {uunet,mcvax}!unido!nixpbe!schro |         D-4790 Paderborn    |
| Phone: nat-5251-14-6694                |         Fed. Rep of Germany |
+----------------------------------------+-----------------------------+
/

echo x - makefile
gres '^X' '' > makefile << '/'
XCC=cc
XCFLAGS=-F
X
Xcron: cron.s ctime.s localtime.s
X	$(CC) $(CFLAGS) -o cron cron.s ctime.s localtime.s
X	chmem =1024 cron
X
Xcron.s: cron.c
X
Xctime.s: ctime.c
X
Xlocaltime.s: localtime.c
/
echo x - time.h
gres '^X' '' > time.h << '/'
X/* time.h --- 880204  --- 1830 */
X
X#ifndef	TIME_H
X#define	TIME_H
X
Xstruct tm {
X	int	tm_sec;
X	int	tm_min;
X	int	tm_hour;
X	int	tm_mday;
X	int	tm_mon;
X	int	tm_year;
X	int	tm_wday;
X	int	tm_yday;
X	int	tm_isdst;
X	};
X
X     char *ctime();
Xstruct tm *localtime();
X
X#endif
X
X/* @@@ */
/
echo x - ctime.c
gres '^X' '' > ctime.c << '/'
X/* ctime.c --- 880206 --- 2230 */
X/* copyright 1988 by W.F.Schroeder */
X
X#include <time.h>
X
Xstatic char *dow[] =
X{ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
X
Xstatic char *moy[] =
X{ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
X  "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
X
Xstatic char string[26];
X
X/* ctime: */
Xchar *ctime(tp)
Xlong *tp;	/* time in seconds since jan.1st.1970 */
X{
X	register char *p;
X	register struct tm *tmp;
X	register char *q;
X	int i;
X
X	tmp= localtime(tp);
X	p= string;
X	q= dow[tmp->tm_wday];
X	i=3;
X	while( i-- )
X		*p++ = *q++;
X	*p++ = ' ';
X	q= moy[tmp->tm_mon];
X	i=3;
X	while( i-- )
X		*p++ = *q++;
X	*p++ = ' ';
X	i= tmp->tm_mday/10 +'0';
X	*p++ = i=='0'?' ':i;
X	*p++ = tmp->tm_mday%10 +'0';
X	*p++ = ' ';
X	*p++ = tmp->tm_hour/10 +'0';
X	*p++ = tmp->tm_hour%10 +'0';
X	*p++ = ':';
X	*p++ = tmp->tm_min/10 +'0';
X	*p++ = tmp->tm_min%10 +'0';
X	*p++ = ':';
X	*p++ = tmp->tm_sec/10 +'0';
X	*p++ = tmp->tm_sec%10 +'0';
X	*p++ = ' ';
X	*p++ = '1';
X	*p++ = '9';
X	*p++ = tmp->tm_year/10 +'0';
X	*p++ = tmp->tm_year%10 +'0';
X	*p++ = '\n';
X	*p++ = '\0';
X	return(string);
X} /* ctime */
X
X/* @@@ */
X
/
echo x - localtime.c
gres '^X' '' > localtime.c << '/'
X/* localtime.c --- 880206 --- 2300 */
X/* copyright 1988 by W.F.Schroeder */
X
X#include <time.h>
X
X#define JDBASE	2440588L
X
Xstatic struct tm ti;
X
X/* jdate: convert julian day to calender date */
Xstatic jdate(j,dp,mp,yp)
Xlong j;
Xint *dp,*mp,*yp;
X{
X	long d,m,y;
X
X	j= j-1721119L;
X	y= (4*j-1)/146097L;
X	j= 4*j-1-146097*y;
X	d= j/4;
X
X	j= (4*d+3)/1461;
X	d= 4*d+3-1461*j;
X	d= (d+4)/4;
X
X	m= (5*d-3)/153;
X	d= 5*d-3-153*m;
X	d= (d+5)/5;
X
X	y= 100*y+j;
X	if( m<10 )
X		m= m+3;
X	else
X	{
X		m= m-9;
X		y= y+1;
X	}
X	*dp= d;
X	*mp= m;
X	*yp= y;
X	return;
X} /* jdate */
X
X/* localtime: */
Xstruct tm *localtime(tp)
Xlong *tp;
X{
X	long t;
X
X	t= *tp;
X	ti.tm_sec= t%60;
X	t= t/60;
X	ti.tm_min= t%60;
X	t= t/60;
X	ti.tm_hour= t%24;
X	t= t/24+JDBASE;
X	jdate(t, &ti.tm_mday, &ti.tm_mon, &ti.tm_year);
X	ti.tm_mon--;
X	ti.tm_year-= 1900;
X	ti.tm_wday= (t+1)%7;
X	ti.tm_yday=
X	ti.tm_isdst= -1;
X	return(&ti);
X} /* localtime */
X
X/* @@@ */
/
echo x - cron.c
gres '^X' '' > cron.c << '/'
X/* cron.c --- 880208 --- 22:30 */
X
X/*
X *	Written by S. R. Sampson
X * 		ihnp4!killer!sampson
X *
X *	Modified for MINIX by W. F. Schroeder
X *		{uunet,mcvax}!unido!nixpbe!schro
X *
X *	cron.c
X *
X *	Public Domain (p) No Rights Reserved
X *
X *	This program operates as a daemon, waking up every minute
X *	to execute the CRONTAB table.  Logging errors to CRONERR.
X *
X *	Put in MINIX /etc/rc as: /usr/bin/cron &
X *
X *	Some notes are included below concerning the cron table
X *	file format.  In this version the cron table file is left
X *	on disk and not moved to core, and  the command character
X *	to signify a newline '%' is not used.
X *
X *	Some cron table entry examples:
X *
X *	Print the date on console every minute:
X *		* * * * * date >/dev/tty0
X *
X *	Print the date on console on the hour, every hour:
X *		0 * * * * date >/dev/tty0
X *
X *	Backup the files at 4:30 am every day except Sat and Sun:
X *		30 4 * * 1-5 backup /dev/st0 / +BdNqV=CronBackup
X *
X *	Backup the files every other day at 7:30 pm:
X *		30 19 * * 1,3,5 backup /dev/st0 / +BdNqV=CronBackup
X *
X */
X
X#define APPEND	0	/* append to old CRONERR  */
X#define LOG	1	/* write log info to CRONERR */
X
X#include <stdio.h>
X#include <time.h>
X#include <signal.h>
X
XFILE *fopen();	/* should be in stdio.h */
X
X#define FALSE	0
X#define TRUE	1
X
X#define CRONTAB "/usr/act/crontab"
X#define CRONERR "/usr/act/cronerr"
X
X#define MAXLINE	132
X#define SIZE	64
X
Xint	eof;
Xchar	min[SIZE], hour[SIZE], day[SIZE],
X	month[SIZE], wday[SIZE], command[SIZE];
Xchar	*tokv[] = { min, hour, day, month, wday };
Xint	fd;	/* file descriptor */
XFILE	*fp;	/* file pointer */
XFILE	*err;
X
X
X/*
X *	This is the basics, ready for bells and whistles
X *
X */
X
Xvoid main()
X{
X	void wakeup(), dummy(), killer();
X	long ti;	/* current time */
X	long thistime, lastime;
X
X	/*
X	 *	fopen() will create the file if it does not exist.
X	 *	Any other errors will be output to stderr at startup.
X	 */
X
X#if APPEND
X	/* MINIX 1.1 fopen() does NOT automatically create the file !?? WFS */
X	if( access(CRONERR,0) )
X	{	/* CRONERR not found */
X		if( (fd= creat(CRONERR,0644)) < 0 )
X		{
X			perror("cron");
X			exit(1);
X		}
X		close(fd);
X	}
X	if ((err = fopen(CRONERR, "a")) == (FILE *)0)
X#else
X	if ((err = fopen(CRONERR, "w")) == (FILE *)0)
X#endif
X	{
X		perror("cron");
X		exit(1);
X	}
X
X	close(0); close(1); close(2);	/* close all I/O paths */
X
X	/* main loop */
X	time(&ti);
X	lastime= ti/60L;
X	for (;;)
X	{
X		/* check for work every minute */
X		time(&ti);
X		thistime= ti/60L;
X		if( thistime > lastime )
X		{
X			lastime= thistime;
X			wakeup();
X		}
X
X		/* clean up zombie children */
X		killer();
X
X		/* we've got nothin' to do */
X		sleep(5);
X	}
X} /* main */
X
X
X/*
X *	Cron does not wait() for child to die.  Therefore children
X *	become zombies when they complete.  In order to clear out the
X *	"floating dead babies" the parent must execute wait() every
X *	now and then.  (Thanks to Fred Buck for enlightenment
X *	See Doctor Dobbs #126 April 87, p. 152)
X */
X
Xvoid dummy()
X{
X}
X
Xvoid killer()
X{
X	long ti;
X	int s;
X
X	/* clean up zombie children */
X	for(;;)
X	{
X		signal(SIGALRM, dummy);
X		alarm(1);
X		if( (s= wait((int *)0)) == -1 )
X			break;
X#if LOG
X		time(&ti);
X		fprintf(err,"%24.24s %5d terminated\n",
X			ctime(&ti), s );
X		fflush(err);
X#endif
X	}
X}
X
Xvoid wakeup()
X{
X	register struct tm *tm;
X	struct tm *localtime();
X	char *ctime();
X	long cur_time;
X
X	time(&cur_time);		/* get the current time */
X	tm = localtime(&cur_time);	/* break it down */
X
X	if (access(CRONTAB, 4) == -1)
X	{
X		fprintf(err,"%24.24s Can't read %s\n",
X			ctime(&cur_time), CRONTAB );
X		fflush(err);
X		return;
X	}
X	else
X		fp = fopen(CRONTAB, "r");
X
X	eof = FALSE;
X	while (!eof)
X	{
X		if (getline() && match(min,tm->tm_min) &&
X		   match(hour,tm->tm_hour) && match(day,tm->tm_mday) &&
X		   match(month,tm->tm_mon) && match(wday,tm->tm_wday))
X		{
X
X		   /*
X		    *  Execute command in the shell
X		    */
X
X		   if (fork() == 0)
X		   {
X#if LOG
X			time(&cur_time);
X			fprintf(err,"%24.24s %5d %s",
X				ctime(&cur_time), getpid(), command );
X			fflush(err);
X#endif
X			execl("/bin/sh", "sh", "-c", command, (char *)0);
X			time(&cur_time);
X			fprintf(err,"%24.24s Can't access /bin/sh\n",
X				ctime(&cur_time));
X			fflush(err);
X
X			fclose(err);
X			fclose(fp);
X
X			exit(1);
X		   }
X		   killer();
X		}
X	}
X
X	fclose(fp);
X}
X
X
X/*
X *	A line consists of six fields.  The first five are:
X *
X *		minute:         0-59
X *		hour:           0-23
X *		day:            1-31
X *		month:          1-12
X *		weekday:        0-6 (Sunday = 0)
X *
X *	The fields are seperated by spaces or tabs, with the
X *	first field left justified (no leading spaces or tabs).
X *	See below for optional field syntax.
X *
X *	The last field is the command field.  This command will
X *	be executed by the shell just as if typed from a console.
X */
X
Xint getline()
X{
X	register char *p;
X	register int   i;
X	char    buffer[MAXLINE], *scanner();
X
X	if (fgets(buffer, sizeof buffer, fp) == (char *)0)  {
X		eof = TRUE;
X		return(FALSE);
X	}
X
X	for (p = buffer, i = 0; i < 5; i++)  {
X		if ((p = scanner(tokv[i], p)) == (char *)0)
X			return(FALSE);
X	}
X
X	strcpy(command, p);     /* scoop the command */
X	return(TRUE);
X}
X
X
Xchar *scanner(token, offset)
Xregister char   *token;		/* target buffer to receive scanned token */
Xregister char   *offset;	/* place holder into source buffer */
X{
X	while ((*offset != ' ') && (*offset != '\t') && *offset)
X		*token++ = *offset++;
X
X	/*
X	 *      Check for possible error condition
X	 */
X         
X	if (!*offset)
X		return ((char *)0);
X
X	*token = '\0';
X        
X	while ((*offset == ' ') || (*offset == '\t'))
X		offset++;
X
X	return (offset);
X}
X
X
X/*
X *	This routine will match the left string with the right number.
X *
X *	The string can contain the following syntax:
X *
X *	*		This will return TRUE for any number
X *	x,y [,z, ...]	This will return TRUE for any number given.
X *	x-y		This will return TRUE for any number within
X *			the range of x thru y.
X */
X
Xint match(left, right)
Xregister char   *left;
Xregister int    right;
X{
X	register int	n;	register char	c;
X
X	n = 0;
X	if (!strcmp(left, "*"))
X		return(TRUE);
X
X	while ((c = *left++) && (c >= '0') && (c <= '9'))
X		n  =  (n * 10) + c - '0';
X
X	switch (c)  {
X		case '\0':
X			return (right == n);
X
X		case ',':
X			if (right == n)
X				return(TRUE);
X			do {
X			      n = 0;
X			      while ((c = *left++) && (c >= '0') && (c <= '9'))
X					n = (n * 10) + c - '0';
X			      if (right == n)
X					return(TRUE);
X			} while (c == ',');
X			return(FALSE);
X
X		case '-':
X			if (right < n)
X				return(FALSE);
X
X			n = 0;
X			while ((c = *left++) && (c >= '0') && (c <= '9'))
X				n = (n * 10) + c - '0';
X
X			return(right <= n);
X	}
X}
X
X/* EOF */
X
/