/****************************************************************************
 *                                                                          *
 *                              A M I C R O N                               *
 *                                                                          *
 *                         Version 4.2 - 03-Jan-91                          *
 *                                                                          *
 *                              by P. Sainio                                *
 *                                                                          *
 ****************************************************************************

	This is my version of AmiCron, I hope you like it... It's still
in public domain so if you have any ideas let us know about them.

 *
 *	amicron.c	(Version 2.5(a&L) by <CB>)
 *
 *	Public Domain (p) No Rights Reserved
 *
 *	This program operates as a daemon, waking up every minute
 *	to execute the CRONTAB table.  
 *
 *	Put in startup-sequence like:
 *		run newcli con:0/140/160/50/CronTask s:startcron
 *	The startcron file needs to contains just one line:
 *		amicron
 *
 *	Some notes are included below concerning the cron table
 *	file format.  In this version the cron table file is left
 *	on disk and not moved to core, and  the command character
 *	to signify a newline '%' is not used.
 *
 *	Some cron table entry examples:
 *
 *	Print the date in the crontask window every minute:
 *		* * * * * date
 *
 *	Print the date in the crontask window on the hour, every hour:
 *		0 * * * * date
 *
 *	Run uupc at 4:30 am every day except Sat and Sun:
 *		30 4 * * 1-5 uupc -siscuva
 *
 *	Backup the files every other day at 7:30 pm:
 *		30 19 * * 1,3,5 sdbackup -s LAST dh0: incbkup_1:
 *
 */

/* 
 * Public Domain (p) by S. R. Sampson
 * Version 2.3, October 1987
 * Amiga port by Rick Schaeffer October 1987
 *
 * Rick Schaeffer          UUCP:  seismo!uunet!iscuva!ricks!ricks
 * E. 13611 26th Ave.      Phone: (509)928-3533
 * Spokane, WA  99216
 *
 * Modified path for CronTab & CronErr to suit Amiga enviroment better
 * Version 2.31  <CB> 15.12.87 
 *
 * Fixed bug with CronTab entries specifying an event by date (month)
 * i.e. "* * 24 12 * say Merry christmas", now works!
 * Version 2.32 <CB> 25.12.87
 *
 * Removed "CronErr", an obvious Unix "feature"
 * Version 2.33 <CB> 31.12.87 
 *
 * Additional support for Lattice 4.0, changed to complete sleep (no more  
 * 5 I/O ints. per second), added command line parameter for CronTab path.
 * Added feature to align Cron to start on 01 seconds, including "loss"
 * due to lengthy command calls or extensive I/O.
 * Version 2.4 <CB> 10.01.88 
 *
 * GRRRRRRR. Just when I thought it was safe to release that thing, C=A brings
 * out AmyDos 1.3. So to have the output of programs called by Cron visible, I
 * changed the "Run >nil:" parameter for execute() back to "Run ". This means,                         
 * that you will have to look at the annoying CLI[#] messages, each time some 
 * program is called. I was pleased with the way the old RUN >nil: worked, and
 * will try to convince Andy Finkel to bring it back.
 * In the meantime, this is:
 * Version 2.5 <CB> 27.03.88
 *
 * Changed it to use ARP.  There is two versions, one that detaches itself from
 * self and the other that doesn't
 * Version 4.2 <PS> 03.01.91

                        
______  /          
______\O                    - The Software Brewery - 
      \\              		    
       o            Sparkling, fresh software from W.-Germany
                 
     @@@@@	      Straight from the bar to your Amiga
     |~~~|\        
     | | |/        
     |___|        With our regards to the Software Distillery

Members are (listed alphabetically):
Christian Balzer alias <CB>, Lattice C, user interfaces, beer addict. 
Christof Bonnkirch, Aztec C, telecommunications, money adict.
Heiko Rath alias <HR>, Assembler, ROM-Kernal stuff, Marabou addict. 
Peter Stark alias PS, Lattice C, IO & utilities, WordStar addict.
Ralf Woitinas alias RAF, Assembler, anything, Ray-Tracing addict.
Torsten Wronski alias MM, Assembler, anything, girls addict.

Beverages: Altenmuenster Brauer Bier, Urfraenkisches Landbier, Grohe Bock.

Send exotic drinks, comments, critizism, flames to:

The Software Brewery
Christian Balzer
Im Wingertsberg 45
D-6108 Weiterstadt
West-Germany

Our BBS "AmigaNode" isn't online yet. As soon as it becomes available, 
you'll be the first to know :-).

 *
 * Compilation notes:
 * Program is compiled with Lattice C 5.05 and the makefile is included.
 * I have removed some of the comments, sorry about that.  If you're
 * intrested see the original source.
 *
 * NOTE!!!  There is no errorchecking for crontab-file...
 */

#define MAXLINE	132
#define SIZE	64
#define ABSEXECBASE ((struct ExecBase **)4L)
#define NOCRON "Can't get crontab-file"
#define CRONPORT "AmiCronV4.2"
#define NO_MESS_MEM "No memory for message!"
#define NO_REPLY "Could not create replyport!"
#define ERR_IN_FILE "Error reading crontab-file!"
#define NO_DEVICE "Could not open timer.device!"
#define NO_TIMER_PORT "Could not open timerport!"
#define NO_CRON_PORT "Could not create CronPort!"
#define EXITING "Quiting the AmiCron!"

struct cronmsg {
	struct Message cm_Msg;
	char *newfile;
};

struct iobuf {
	unsigned char *base;
	unsigned char *ptr;
	int rcnt;
	int size;
};

typedef char boolean;

extern struct ArpBase *ArpBase;
extern struct DOSBase *DOSBase;
extern struct IntuitionBase *IntuitionBase;

struct MsgPort *timerport,*cronport,*mycron=NULL,*replyport;
struct iobuf fd;
struct cronmsg *newcron;

struct NewShell nsh;
struct timerequest tr;

char min[SIZE],hour[SIZE],day[SIZE],month[SIZE],wday[SIZE],command[SIZE],eof,
		*tokv[]={min,hour,day,month,wday},
		*com="CronFile",*args,*help="Usage: AmiCron [CronFile]",
		cronfile[256]="S:CronTab";

struct TextAttr myfont = {
	"topaz.font",
	TOPAZ_EIGHTY,0,0 };

struct IntuiText body1={
	0,1,JAM1,10,5,&myfont,NULL,NULL },body2={
	0,1,JAM1,10,14,&myfont,NULL,NULL },no1={
	0,1,JAM1,6,3,&myfont,"Cancel",NULL },no2={
	0,1,JAM1,6,3,&myfont,"Quit",NULL };

static void cleanup(void),alarm(int),wakeup(void),MyExit(void);
static ULONG systime(void);
static boolean getline(void),getfile(void),
					match(register char *,register int);
static char *scanner(register char *,register char *),
				*fgets(char *,int);

void __asm __saveds AmiCron(register __a0 char *cmd,register __d0 long len) {

	ULONG current,signals,getsigs,timesig,messig;
	long count;
	register struct tm *tm;

	count=GADS(cmd,len,help,&args,com);
	if (count<0) {
		body1.IText=args;
		AutoRequest(NULL,&body1,NULL,&no1,0,0,188,50);
		cleanup();
	}

	if (count)
		strcpy(cronfile,args);

	Forbid();

	if (mycron=FindPort(CRONPORT)) {
		Permit();
		if (replyport=CreatePort(0,0)) {
			if (newcron=AllocMem(sizeof(struct cronmsg),
										MEMF_PUBLIC|MEMF_CLEAR)) {
				newcron->cm_Msg.mn_Node.ln_Type=NT_MESSAGE;
				newcron->cm_Msg.mn_ReplyPort=replyport;
				newcron->newfile=cronfile;
				PutMsg(mycron,newcron);
				WaitPort(replyport);
				FreeMem(newcron,sizeof(struct cronmsg));
			}
			else {
				body1.IText=NO_MESS_MEM;
				AutoRequest(NULL,&body1,NULL,&no1,0,0,sizeof(NO_MESS_MEM)*8+44,50);
			}
			DeletePort(replyport);
		}
		else {
			body1.IText=NO_REPLY;
			AutoRequest(NULL,&body1,NULL,&no1,0,0,sizeof(NO_REPLY)*8+44,50);
		}
		cleanup();
	}

	else {
		if (!(cronport=CreatePort(CRONPORT,0))) {
			body1.IText=NO_CRON_PORT;
			AutoRequest(NULL,&body1,NULL,&no1,0,0,sizeof(NO_CRON_PORT)*8+44,50);
			cleanup();
		}
	}
	Permit();

	if (timerport=CreatePort(0,0)) {
		tr.tr_node.io_Message.mn_Node.ln_Type=NT_MESSAGE;
		tr.tr_node.io_Message.mn_Length=sizeof(struct timerequest);
		tr.tr_node.io_Message.mn_ReplyPort=timerport;

		if (!OpenDevice(TIMERNAME,UNIT_VBLANK,&tr,0)) {
			if (getfile()) {
				signals=SIGBREAKF_CTRL_C|(timesig = 1<<timerport->mp_SigBit)|
							(messig = 1<<cronport->mp_SigBit);
				for (;;) {
					wakeup();
					current=systime();
					tm=localtime(&current);
					alarm(60-tm->tm_sec);
					do {
						getsigs=Wait(signals);
						if (getsigs & SIGBREAKF_CTRL_C)
							cleanup();
						if (getsigs & messig) {
							if (newcron=(struct cronmsg *)GetMsg(cronport)) {
								strcpy(cronfile,newcron->newfile);
								ReplyMsg(newcron);
								DosFreeMem(fd.base);
								fd.base=NULL;
								if (!getfile())
									cleanup();
							}
						}
					}
					while (!(getsigs & timesig));
				}
			}
		}
	}
	cleanup();
}

static void wakeup() {

	register struct tm *tm;
	long cur_time;
 	
	cur_time=systime();		/* get the current time */
	tm = localtime(&cur_time);	/* break it down */
 
	/* Now let's see if there is a CronTab file out there <CB> */ 	

	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+1) && match(wday,tm->tm_wday))  {
		/* Weird localtime months ^ range from 0-11 !!! <CB>*/
			nsh.nsh_Pri=(*ABSEXECBASE)->ThisTask->tc_Node.ln_Pri;
			nsh.nsh_Control=BACKGROUND_SHELL;
			nsh.nsh_Input=Input();
			nsh.nsh_Output=Output();
			ASyncRun(command,NULL,(struct ProcessControlBlock *)&nsh);
		}
	}
}
 
 
/*
 *	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 CLI just as if typed from a console.
 */

static boolean getline() {
	register char *p;
	register int   i;
	char buffer[MAXLINE];
 
	if (fgets(buffer,sizeof buffer) == 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);
}
 
 
static char *scanner(register char *token,register char *offset) {
	/*	*token	target buffer to receive scanned token */
	/*	*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.
 */
 
static boolean match(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 ((boolean)(right == n));
 
		case ',':
			if ((boolean)(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((boolean)(right <= n));
	}
}

static boolean getfile(void) {

	BPTR lock,file;
	struct FileInfoBlock *fib;
	boolean succ=FALSE;

	if (lock=Lock(cronfile,ACCESS_READ)) {
		if (fib=AllocMem(sizeof(struct FileInfoBlock),MEMF_PUBLIC)) {
			if (Examine(lock,fib)) {
				if (fd.base=DosAllocMem(fib->fib_Size)) {
					if (file=Open(cronfile,MODE_OLDFILE)) {
						fd.size=Read(file,fd.base,fib->fib_Size);
						if (fd.size=fib->fib_Size)
							fd.ptr=fd.base;
							succ=TRUE;
						Close(file);
					}
				}
			}
			FreeMem(fib,sizeof(struct FileInfoBlock));
		}
		UnLock(lock);
	}
	if (!succ) {

		long length;

		body1.IText=NOCRON;
		body1.NextText=&body2;
		body2.IText=cronfile;
		length=strlen(cronfile);

		AutoRequest(NULL,&body1,NULL,&no1,0,0,((length>sizeof(NOCRON))?
						length : sizeof(NOCRON))*8+44,59);
	}
	return succ;
}

static char *fgets(char *buffer,int len) {

	unsigned char ch;
	int count=0;

	while (len>count) {
		if (fd.rcnt<fd.size) {
			if ((ch=*fd.ptr++)=='\n') {
				buffer[count++]='\0';
				fd.rcnt++;
				return buffer;
			}
			buffer[count++]=ch;
			fd.rcnt++;
		}
		else {
			fd.rcnt=0;
			fd.ptr=fd.base;
			if (count==0)
				return NULL;
			else {
				buffer[count]='\0';
				return buffer;
			}
		}
	}
}

static ULONG systime() {
	tr.tr_node.io_Command=TR_GETSYSTIME;
	DoIO(&tr);
	return (tr.tr_time.tv_secs+(((8*365+2)*24+6)*60*60));
	/* To Greenwich Mean Time  ^^^^^^^^^^^^^^^^^^^^^ */
}

static void alarm(int secs) {
	tr.tr_node.io_Command=TR_ADDREQUEST;
	tr.tr_time.tv_secs=secs;
	tr.tr_time.tv_micro=0;
	SendIO(&tr);
}

static void cleanup() {
	if (cronport) {
		Forbid();
		while (newcron=(struct cronmsg *)GetMsg(cronport))
			ReplyMsg(newcron);
		DeletePort(cronport);
		Permit();
		if (fd.base)
			DosFreeMem(fd.base);
		if (timerport) {
			if (tr.tr_node.io_Device) {
				if (tr.tr_node.io_Command)
					AbortIO(&tr);
				CloseDevice(&tr);
			}
			else {
				body1.IText=NO_DEVICE;
				AutoRequest(NULL,&body1,NULL,&no1,0,0,sizeof(NO_DEVICE)*8+44,50);
			}
			DeletePort(timerport);
		}
		else {
			body1.IText=NO_TIMER_PORT;
			AutoRequest(NULL,&body1,NULL,&no1,0,0,sizeof(NO_TIMER_PORT)*8+44,50);
		}
		body1.IText=EXITING;
		body1.NextText=NULL;
		AutoRequest(NULL,&body1,NULL,&no2,0,0,sizeof(EXITING)*8+44,50);
	}
	MyExit();
}
