
/*
 *  DCRON.C  V2
 *
 *  - loads s:crontab or file crontab specified by -f
 *  - checks the datestamp on s:crontab every 60 seconds and reloads the file
 *    into memory if changed.
 *  - every 60 seconds scans the memory-resident image for things to do
 *  - checks for date changes and doesn't reexecute if changes are
 *    backwards in time.
 *
 *  DCRON [-d] [-f crontab] logfile
 */

#include <exec/types.h>
#include <devices/timer.h>
#include <libraries/dos.h>
#include <libraries/dosextens.h>
#include <stdio.h>
#include "protos.h"

typedef struct Node	NODE;
typedef struct List	LIST;
typedef struct DateStamp DATESTAMP;
typedef struct timerequest IOT;
typedef struct MsgPort	PORT;
typedef struct Process	PROC;
typedef struct FileInfoBlock FIB;


#define CRONTAB "s:crontab"
#define SIGS	(SIGBREAKF_CTRL_C|SIGBREAKF_CTRL_D|SIGBREAKF_CTRL_E|SIGBREAKF_CTRL_F)

typedef struct {
    short min;
    short hour;
    short day;
    short month;
    short dow;
} DateAry;

typedef struct {
    NODE    Node;
    UBYTE   BitMap[8][8];   /*	min, hour, day, month, dow */
    char    *Command;
} Command;

extern	char *malloc();

DATESTAMP   LastDate;	    /*	Date as of last execution	*/
DATESTAMP   ModDate;	    /*	Check if system time  modified	*/
DATESTAMP   TabDate;	    /*	Check if crontab modified	*/

LIST	    CmdList;	    /*	list of commands		*/

IOT	Iot;
PORT	*TPort; 	    /*	Collector plate for IO requests 	*/
short	FatalError;	    /*	Uh oh, can't recover                    */
char	*LogFile;
char	*CronFile = CRONTAB;
char	XDebug;
long	NilFH;		    /*	NIL: file handle			*/
short	CronFileExists = 0; /*	-1, 0, 1				*/

void	logmessage();
void	LoadCronFile();
void	LoadBitMap();
void	WriteStr();
void	DRunCommand();
void	ExecuteCommands();
void	CheckFileModified();
void	DateToDateAry();
void	*GetHead();
void	*GetSucc();

int
brk()
{
    return(0);
}

void
main(ac, av)
short ac;
char *av[];
{
    PROC *proc = (PROC *)FindTask(NULL);
    APTR oldConsoleTask = proc->pr_ConsoleTask;

    onbreak(brk);
    NewList(&CmdList);
    {
	register short i;

	for (i = 1; i < ac; ++i) {
	    register char *ptr = av[i];
	    if (*ptr != '-') {
		LogFile = ptr;
		continue;
	    }
	    while (*++ptr) {
		switch(*ptr) {
		case 'd':
		    ++XDebug;
		    break;
		case 'f':
		    CronFile = av[++i];
		    break;
		default:
		    WriteStr(Output(), "bad option\n");
		    goto fail;
		}
	    }
	}
	if (CronFile == NULL) {
	    puts("-f : expected filename");
	    exit(1);
	}
    }
    if (!LogFile) {
fail:
	WriteStr(Output(), "DCron [-d] [-f cronfile] Logfile\n");
	WriteStr(Output(), "DCron, V2.02\n");
	exit(1);
    }
    if (OpenDevice("timer.device", 0, &Iot, UNIT_VBLANK)) {
	logmessage("Unable to open timer.device\n");
	exit(1);
    }
    if (!DeviceProc("NULL:")) {
	logmessage("NULL: device required for dcron to run\n");
	WriteStr(Output(), "NULL: device required to run\n");
	exit(1);
    }
    proc->pr_ConsoleTask = (APTR)DeviceProc("NULL:");
    fclose(stderr);

    logmessage("Startup: V2.02 (dist: 29 May 1990)\n");

    NilFH = (long)Open("null:", 1006);
    DateStamp(&LastDate);
    DateStamp(&ModDate);
    TPort = CreatePort(NULL,0);
    printf("RESULT %08lx\n", TPort);
    Iot.tr_time.tv_secs = 2;
    Iot.tr_time.tv_micro= 0;
    Iot.tr_node.io_Message.mn_ReplyPort = TPort;
    Iot.tr_node.io_Command = TR_ADDREQUEST;
    SendIO(&Iot);                                   /*  timeout */

    for (;;) {
	long mask;
	mask = Wait(SIGS | (1 << TPort->mp_SigBit));
	if (mask & (SIGBREAKF_CTRL_C|SIGBREAKF_CTRL_D)) {
	    logmessage("DCRON: Break\n");
	    break;
	}
	if (mask & (SIGBREAKF_CTRL_E|SIGBREAKF_CTRL_F)) {
	    logmessage("^E/F force check\n");
	    AbortIO(&Iot);          /*  force execution                     */
	}
	if (FatalError)
	    break;
	if (CheckIO(&Iot)) {        /*  if file/date modified, force exec.  */
	    DATESTAMP Ds;
	    DateAry D1, D2;
	    int st;

	    WaitIO(&Iot);
	    st = CheckDateChanged();
	    CheckFileModified();
	    DateStamp(&Ds);
	    DateToDateAry(&LastDate, &D1);
	    DateToDateAry(&Ds, &D2);
	    if (st == 0)
		ExecuteCommands(&D1, &D2);
	    LastDate = Ds;
	    DateStamp(&Ds);
	    Iot.tr_time.tv_secs = 61 - Ds.ds_Tick / 50;
	    Iot.tr_time.tv_micro= 0;
	    SendIO(&Iot);
	}
    }
    AbortIO(&Iot);
    WaitIO(&Iot);
    CloseDevice(&Iot);
    DeletePort(TPort);
    Close(NilFH);
    proc->pr_ConsoleTask = oldConsoleTask;
}

/*
 *  Returns 0 = execute objects for range
 *	    1 = do not execute objects for range
 */

CheckDateChanged()
{
    DATESTAMP Ds;
    long xold, xnew;
    static char state = 0;

    DateStamp(&Ds);
    xold = ModDate.ds_Days * 1440 + ModDate.ds_Minute;
    xnew = Ds.ds_Days * 1440 + Ds.ds_Minute;

    /*
     *	if backwards or more than T+5min
     */

    if (xnew < xold || xnew - 5 > xold) {
	DateStamp(&LastDate);
	if (state == 0)
	    logmessage("Date change noted, %d mins\n", xnew - xold + 1);
	state = 1;
    } else {
	state = 0;
    }

    /*
     *	If all is ok or too far away from old date then set new base
     *	date for next comparison (T +/- 10min)
     */

    if (state == 0 || xold - xnew > 10 || xold - xnew < -10 ) {
	ModDate = Ds;
    }
    return((int)state);
}

void
CheckFileModified()
{
    char buf[sizeof(FIB)+4];
    long lock;

    if (lock = (long)Lock(CronFile, SHARED_LOCK)) {
	register FIB *fib = (FIB *)(((long)buf+3)&~3);
	if (Examine(lock, fib)) {
	    if (CronFileExists < 1 ||
		fib->fib_Date.ds_Tick	!= TabDate.ds_Tick ||
		fib->fib_Date.ds_Minute != TabDate.ds_Minute ||
		fib->fib_Date.ds_Days	!= TabDate.ds_Days)
	    {
		if (TabDate.ds_Days) {
		    logmessage("crontab modification noted\n");
		}
		TabDate = fib->fib_Date;
		LoadCronFile();
	    }
	}
	UnLock(lock);
    } else {
	if (CronFileExists >= 0) {
	    logmessage("unable to lock cronfile %s!\n", CronFile);
	    CronFileExists = -1;
	}
    }
}

/*
 *  execute commands that fall d1 < cmd <= d2
 */

void
ExecuteCommands(d1, d2)
short *d1, *d2;     /*	min, hour, day, month, dow  */
{
    Command *cmd;
    short i;
    short n;

    for (cmd = GetHead(&CmdList); cmd; cmd = GetSucc(&cmd->Node)) {
	short ok = 1;
	for (i = 0; i < 5; ++i) {
	    UBYTE *bitmap = cmd->BitMap[i];

	    n = d2[i];
	    if (n == d1[i]) {
		if ((bitmap[n>>3] & (1 << (n & 7))) == 0) {
		    ok = 0;
		    break;
		}
	    } else {
		while (n != d1[i]) {
		    if (bitmap[n>>3] & (1 << (n & 7)))
			break;
		    n = (n - 1) & 63;
		}
		if (n == d1[i]) {
		    ok = 0;
		    break;
		}
	    }
	}
	if (ok)
	    DRunCommand(cmd->Command);
    }
}

void
DRunCommand(cmd)
char *cmd;
{
    char buf[256];

    logmessage("%s\n", cmd);
    strcpy(buf, "run ");
    strcat(buf, cmd);
    Execute(buf, NilFH, NilFH);
}

void
DateToDateAry(date, da)
DATESTAMP *date;
DateAry *da;
{
    static char dim[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
    long days;
    long years;
    char leap;
    short month;

    days = date->ds_Days + 731; 	    /*	1976	    */
    years = days / (365*3+366);             /*  #quad yrs   */
    days -= years * (365*3+366);
    leap = (days <= 365);                   /*  is a leap yr*/
    years = 1976 + 4 * years;
    dim[1] = 29;
    if (leap == 0) {
	dim[1] = 28;
	days -= 366;
	++years;
	years += days / 365;
	days %= 365;
    }
    for (month = 0; (month==1) ? (days >= 28 + leap) : (days >= dim[month]); ++month)
	days -= (month==1) ? (28 + leap) : dim[month];

    da->min    = date->ds_Minute % 60;
    da->hour   = date->ds_Minute / 60;
    da->day    = days + 1;
    da->month  = month + 1;
    da->dow    = date->ds_Days % 7;    /*  0 = sunday	*/
}

void
LoadCronFile()
{
    char buf[256];
    long fh;
    Command *cmd;

    while (cmd = (Command *)RemHead(&CmdList))
	free(cmd);

    ReadLn(NULL, NULL, 0);
    fh = (long)Open(CronFile, 1005);
    if (fh == NULL) {
	if (CronFileExists != -1)
	    logmessage("unable to open cronfile %s!\n", CronFile);
	CronFileExists = -1;
	return;
    }
    while (ReadLn(fh, buf, 256)) {
	char *ptr = buf;
	short i;

	if (buf[0] == 0 || buf[0] == '#')
	    continue;
	cmd = (Command *)malloc(sizeof(Command));
	setmem(cmd, sizeof(Command), 0);

	for (i = 0; i < 5; ++i) {               /*  5 time fields   */
	    /*
	    printf("lb %d\n", i);
	    */
	    LoadBitMap(&ptr, cmd->BitMap[i]);
	}

	while (*ptr == ' ' || *ptr == 9)
	    ++ptr;
	cmd->Command = malloc(strlen(ptr) + 1);
	strcpy(cmd->Command, ptr);

	/*
	for (i = 0; i < 5; ++i) {
	    for (j = 0; j < 4; ++j)
		printf("%02x", cmd->BitMap[i][j]);
	    printf(" ");
	    for (j = 4; j < 8; ++j)
		printf("%02x", cmd->BitMap[i][j]);
	    puts("");
	}

	printf("cmd = %s\n", cmd->Command);
	*/

	AddTail(&CmdList, &cmd->Node);
    }
    Close(fh);
    CronFileExists = 1;
}

void
LoadBitMap(pptr, bm)
char **pptr;
UBYTE *bm;	/*  8 bytes = 64 entries    */
{
    register char *ptr = *pptr;

    while (*ptr == ' ' || *ptr == 9)
	++ptr;

    /*
     *	looking for *, number range n-n, single numbers, etc...  1,2,8-10 ...
     */

    while (*ptr == '*' || (*ptr >= '0' && *ptr <= '9')) {
	short v1, v2;

	v1 = 0;
	while (*ptr >= '0' && *ptr <= '9') {
	    v1 = v1 * 10 + *ptr - '0';
	    ++ptr;
	}
	if (*ptr == '-') {
	    ++ptr;
	    v2 = 0;
	    while (*ptr >= '0' && *ptr <= '9') {
		v2 = v2 * 10 + *ptr - '0';
		++ptr;
	    }
	} else {
	    v2 = v1;
	}
	if (*ptr == '*') {
	    v1 = 0;
	    v2 = 63;
	    ++ptr;
	}
	if (v1 < 0)
	    v1 = 0;
	if (v1 > 63)
	    v1 = 63;
	if (v2 < 0)
	    v2 = 0;
	if (v2 > 63)
	    v2 = 63;

	--v1;
	do {
	    v1 = (v1 + 1) & 63;
	    bm[v1>>3] |= (1 << (v1 & 7));
	} while (v1 != v2);
	if (*ptr == ',')
	    ++ptr;
    }
    *pptr = ptr;
}

/*
 *  Poor man's log.  Note that the log file is not left open ... this allows
 *  one to read or tail it at any time.
 */

void
logmessage(ptr, a, b, c, d, e)
char *ptr;
{
    char *buf = malloc(512);
    DATESTAMP date;
    DateAry da;
    long    fh;
    static char *Dow[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
    static char *Miy[] = { "---", "Jan", "Feb", "Mar", "Apr", "May", "Jun",
			   "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };

    if (!buf)
	return;

    DateStamp(&date);
    DateToDateAry(&date, &da);

    sprintf(buf, "dcron: %s %2ld %s %02ld:%02ld   ",
	Dow[da.dow], da.day, Miy[da.month], da.hour, da.min
    );
    sprintf(buf+strlen(buf), ptr, a, b, c, d, e);
    if ((fh = (long)Open(LogFile, 1005)) == NULL)
	fh = (long)Open(LogFile, 1006);
    if (fh) {
	Seek(fh, 0L, 1);
	WriteStr(fh, buf);
	Close(fh);
    }
    free(buf);
}

void
WriteStr(fh, buf)
long fh;
char *buf;
{
    Write(fh, buf, strlen(buf));
}

ReadLn(fh, buf, max)
long fh;
char *buf;
short max;
{
    static char Data[1024];
    static short RIdx, RLen;
    register short i;

    if (fh == NULL) {
	RIdx = RLen = 0;
	return(0);
    }
    for (--max, i = 0; i < max; ++i) {
	if (RIdx == RLen) {
	    RLen = Read(fh, Data, 1024);
	    RIdx = 0;
	    if (RLen <= 0) {
		buf[i] = 0;
		return(0);
	    }
	}
	if ((buf[i] = Data[RIdx++]) == '\n')
	    break;
    }
    buf[i] = 0;
    return(1);
}

