[comp.sources.amiga] v02i108: dcron - cron utility

page@swan.ulowell.edu (Bob Page) (12/29/88)

Submitted-by: dillon@postgres.berkeley.edu (Matt Dillon)
Posting-number: Volume 2, Issue 108
Archive-name: unix/dcron.1

[uuencoded executable included.  ..Bob]

#	This is a shell archive.
#	Remove everything above and including the cut line.
#	Then run the rest of the file through sh.
#----cut here-----cut here-----cut here-----cut here----#
#!/bin/sh
# shar:    Shell Archiver
#	Run the following text with /bin/sh to create:
#	dcron.c
#	dcron.doc
#	Mountlist
#	dcron.uu
#	null-handler.uu
# This archive created: Wed Dec 28 16:36:08 1988
cat << \SHAR_EOF > dcron.c

/*
 *  DCRON.C  V1.05
 *
 *  Limitations:    The only RUN commands allowed are of the form:
 *
 *		    RUN >nil: <nil: <command>
 *
 *  -Wakeup once every minute & check the modify date for S:CRONTAB
 *   and to check if the DateStamp() has been re-set.
 *
 *  -Attempt to be smart about checking s:crontab (scan the file
 *   as little as possible).
 *
 *  -Handles massive Date changes and crontab modifications
 *
 *  DCRON [-d] logfile
 */

#include <stdio.h>
#include <local/typedefs.h>

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

#define ACTION_READWRITE    1004
#define ACTION_FIND_INPUT   1005	    /* various ACTION's supported   */
#define ACTION_FIND_OUTPUT  1006
#define ACTION_END	    1007
#define ACTION_EXAMINE	    23
#define ACTION_EXAMINENEXT  24
#define ACTION_LOCATE	    8
#define ACTION_FREELOCK     15
#define ACTION_COPYDIR	    19

#define DOS_FALSE    0
#define DOS_TRUE     -1

#define BTOC(bptr)  ((void *)((long)(bptr) << 2))

typedef struct FileHandle FH;

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

typedef struct {
    short entry[5];
} MHDMWARY;

typedef struct {
    MHDMW   Beg;
    MHDMW   End;
} CDATE;

typedef struct {
    char min[60];
    char hour[24];
    char day[32];
    char month[13];
    char dow[7];
} CARRY;

extern	int	Enable_Abort;
extern	APTR	DeviceProc();

DATESTAMP   BegDate;	    /*	Start and end range for     */
DATESTAMP   EndDate;	    /*	 file compare		    */
DATESTAMP   ModDate;	    /*	Check if date modified	    */
DATESTAMP   TabDate;	    /*	Check if crontab modified   */

IOT	Iot;		    /*	Best guess at next timeout		*/
IOT	Iotmin; 	    /*	1 minute T.O. (date/file modified test  */
PORT	*TPort; 	    /*	Collector plate for IO requests 	*/
short	FatalError;	    /*	Uh oh, can't recover                    */
short	NextTimeout;	    /*	# minutes till next timeout		*/
char	*LogFile;
char	XDebug;
long	NilFH;
short	CronFileExists = 1; /*	Does the crontab file exist?		*/

void	logmessage();

void
main(ac, av)
short ac;
char *av[];
{
    PROC *proc = FindTask(NULL);

    Enable_Abort = 0;
    {
	register short i;
	short j = 0;

	for (i = 1; i < ac; ++i) {
	    register char *ptr = av[i];
	    if (*ptr != '-') {
		switch(j++) {
		case 0:
		    LogFile = ptr;
		    break;
		}
		continue;
	    }
	    while (*++ptr) {
		switch(*ptr) {
		case 'd':
		    ++XDebug;
		    break;
		default:
		    WriteStr(Output(), "bad option\n");
		    goto fail;
		}
	    }
	}
    }
    if (!LogFile) {
fail:
	WriteStr(Output(), "DCron [-d] Logfile\n");
	WriteStr(Output(), "DCron, V1.05\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 = DeviceProc("NULL:");
    fclose(stderr);
    logmessage("Startup: V1.05 (dist: 22 December 1988)\n");

    NilFH = Open("null:", 1006);
    DateStamp(&EndDate);
    DateStamp(&ModDate);
    TPort = CreatePort(NULL,0);
    Iot.tr_time.tv_secs = 1;
    Iot.tr_time.tv_micro= 0;
    Iot.tr_node.io_Message.mn_ReplyPort = TPort;
    Iot.tr_node.io_Command = TR_ADDREQUEST;
    Iotmin = Iot;
    SendIO(&Iot);
    SendIO(&Iotmin);
    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 scan\n");
	    AbortIO(&Iot);          /*  force execution                     */
	}
	if (FatalError)
	    break;
	if (CheckIO(&Iotmin)) {     /*  if file/date modified, force exec.  */
	    WaitIO(&Iotmin);
	    if (datemod() + filemod())
		AbortIO(&Iot);
	    Iotmin.tr_time.tv_secs = 60;
	    Iotmin.tr_time.tv_micro= 0;
	    SendIO(&Iotmin);
	}
	if (CheckIO(&Iot)) {
	    DATESTAMP Ds;
	    short secmin;

	    WaitIO(&Iot);
	    dostuff();
	    if (NextTimeout <= 0)
		NextTimeout = 1;
	    if (XDebug) {
		logmessage(" Next Timeout in %02ld:%02ld\n",
		    NextTimeout / 60,
		    NextTimeout % 60
		);
	    }
	    DateStamp(&Ds);
	    secmin = 61 - (Ds.ds_Tick / 50);    /*  1+Secs till next min. */
	    Iot.tr_time.tv_secs = secmin + (NextTimeout - 1) * 60;
	    Iot.tr_time.tv_micro= 0;
	    SendIO(&Iot);
	}
    }
    AbortIO(&Iot);
    AbortIO(&Iotmin);
    WaitIO(&Iot);
    WaitIO(&Iotmin);
    CloseDevice(&Iot);
    DeletePort(TPort);
    Close(NilFH);
}

/*
 *  If the current date is less than the previous date
 *  If the current date is more than +5 minutes the previous date
 */

datemod()
{
    DATESTAMP Date;
    long xold, xnew;

    DateStamp(&Date);
    xold = ModDate.ds_Days * 1440 + ModDate.ds_Minute;
    xnew = Date.ds_Days * 1440 + Date.ds_Minute;
    ModDate = Date;
    if (xnew < xold || xnew - 5 > xold) {
	DateStamp(&EndDate);
	logmessage("Date change noted\n");
	return(1);
    }
    return(0);
}

/*
 *  If the file modification time is different than when
 *  we last checked, we want to recalculate our timeout
 *  and rescan it.
 */

filemod()
{
    char buf[sizeof(FIB)+4];
    register FIB *fib = (FIB *)(((long)buf+3)&~3);
    long lock;
    long result = 0;

    if (lock = Lock(CRONTAB, SHARED_LOCK)) {
	if (Examine(lock, fib)) {
	    if (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");
		    result = 1;
		}
		TabDate = fib->fib_Date;
	    }
	}
	UnLock(lock);
    }
    return(result);
}

/*
 *  DOSTUFF()
 *
 *  Scan the CRONTAB and execute any entries that fall between
 *  BegDate and EndDate.  Specifically, >= BegDate and < EndDate.
 *
 *  This is done as little as possible.  While scanning, we calculate the
 *  timeout period till the next entry and will attempt to sleep for that
 *  period of time before comming back to this routine.  Checks are still
 *  done every 60 seconds for radical date changes and crontab modification
 *  (routines above).
 *
 *  The calculation of the timeout period is not entirely accurate.
 *  Specifically, in cases similar to:
 *
 *	"30 16 * * *"
 *
 *  If the current time is 16:xx, the timeout period calculated for the hours
 *  will 0 instead of 24 (if the minutes had been '*', this would be
 *  correct.  But if it is 16:31 the system will wait 29 minutes instead of
 *  23 hours and 30 minutes.  Of course, it will re-scan at +29 minutes and
 *  not find anything to do, but you get the point.
 */

static char dim[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
static CARRY CArry;
static short CRanStart[] = {  0,  0,  1  ,  1, 0 };
static short CRanEnd[]	 = { 59, 23, 31  , 12, 6 };
static long  CWeight[]	 = {  1, 60, 1440, 28*1440, 1440 };
static char *CBase[]	 = { CArry.min, CArry.hour, CArry.day, CArry.month, CArry.dow };

dostuff()
{
    CDATE cdate;
    long fh;
    char buf[256];
    int ie;

    NextTimeout = -1;

    BegDate = EndDate;
    DateStamp(&EndDate);
    breakup(&EndDate, &cdate.End);          /*  Breakup the time            */
    breakup(&BegDate, &cdate.Beg);          /*  note: dim[FEB] is modified  */
    bzero(&CArry, sizeof(CArry));           /*  range array                 */

    /*
     *	Generate the range for the delta time that has occured since the
     *	last reading.  (A) This is used to ensure we don't miss anything
     *		       (B) Ensure we don't repeat anything
     */

    ie = makerange(CArry.min  , 0, 59, cdate.Beg.min  , cdate.End.min  ,  1);
    ie = makerange(CArry.hour , 0, 23, cdate.Beg.hour , cdate.End.hour , ie);
    ie = makerange(CArry.day  , 1, dim[cdate.Beg.month-1],
				       cdate.Beg.day  , cdate.End.day  , ie);
    ie = makerange(CArry.month, 1, 12, cdate.Beg.month, cdate.End.month, ie);
    ie = makerange(CArry.dow  , 0,  6, cdate.Beg.dow  , cdate.End.dow  , ie);

    /*
     *	Note:	if ie is set after this then dostuff() was called
     *		within the same minute segment and would yield a
     *		repeation of commands.
     */

    if (ie == 0) {
	ReadLn(NULL, NULL, 0);
	if (fh = Open(CRONTAB, 1005)) {
	    long whennext;				/*  in minutes, estimate    */

	    if (CronFileExists == 0) {
		CronFileExists = 1;
		logmessage("DCron File %s exists\n", CRONTAB);
	    }
	    while (ReadLn(fh, buf, 256)) {
		if (buf[0] == 0 || buf[0] == '#')
		    continue;
		whennext = handleline(buf, &cdate.End); /*  take smallest           */
		if (XDebug > 1)
		    logmessage("whennext == %ld\n", whennext);
		if (NextTimeout < 0 || whennext < NextTimeout)
		    NextTimeout = whennext;
	    }
	    Close(fh);
	} else {
	    if (CronFileExists == 1) {
		CronFileExists = 0;
		logmessage("Unable to open %s\n", CRONTAB);
	    }
	}
    }

    /*
     *	You can't trust the Amiga's clock vs the timer.device ... this is to
     *	ensure that they don't get too far off... no more than an hour timeout
     *	is allowed.  Frankly, anything above 30 minutes will yield unnoticeable
     *	results.
     */

    if (--NextTimeout > 60)
	NextTimeout = 60;
}

breakup(date, mhd)
DATESTAMP *date;
MHDMW *mhd;
{
    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 < 366);                    /*  is a leap yr*/
    years = 1976 + 4 * years;
    dim[1] = 29;
    if (!leap) {
	dim[1] = 28;
	days -= 366;
	++years;
    }
    years += days / 365;
    days -= (days / 365) * 365;
    for (month = 0; (month==1) ? (days >= 28 + leap) : (days >= dim[month]); ++month)
	days -= (month==1) ? (28 + leap) : dim[month];
    mhd->min	= date->ds_Minute % 60;
    mhd->hour	= date->ds_Minute / 60;
    mhd->day	= days + 1;
    mhd->month	= month + 1;
    mhd->dow	= date->ds_Days % 7;	/*  0 = sunday	 */
}

/*
 *  S < RANGE <= E	(if previous entries were equal)
 *	    note: if S == E, E is still included
 *  S <= RANGE <= E	(if previous entries were not equal)
 */

makerange(ptr, cs, ce, s, e, isequal)
register char *ptr;
{
    long error = 5000;
    short oldisequal = isequal;

    isequal = (isequal && s == e);
    if (oldisequal && s != e) {
	if (++s > ce)
	    s = cs;
    }
    while (--error && s != e) {
	ptr[s++] = 1;
	if (s > ce)
	    s = cs;
    }
    ptr[e] = 1;
    if (error == 0) {
	logmessage("DCRON,makerange(): software error\n");
	FatalError = 1;
    }
    return(isequal);
}

/*
 *  This is the core of the routine.  The fields are interpreted, times
 *  estimated, ready entries run, etc..
 *
 *  order:  minutes hours days months dayofweek
 *
 *  Each field may be a * indicating all-times, and a combination of a
 *  range and comma delimited numbers:	0-3,45,46-49	    (MUST BE SORTED)
 *
 *  Each field is separated by one or spaces+tabs
 */

handleline(buf, endt)
char *buf;
MHDMWARY *endt;
{
    register char *ptr = buf;
    register short i;
    char *gnum();
    short sumok = 0;
    long howsoon = 0;		    /*	how soon until next entry */

    for (i = 0; i < 5; ++i) {       /*  5 arguments     */
	char ok = 0;
	short nextunit = -1;	    /*	count, in units */

	while (*ptr == ' ' || *ptr == 9)
	    ++ptr;
	while (*ptr && *ptr != 9 && *ptr != ' ') {
	    short start;
	    short finish;
	    if (*ptr == '*') {
		++ptr;
		ok = 1;
		break;
	    }
	    ptr = gnum(ptr, &start);
	    finish = start;
	    if (*ptr == '-')
		ptr = gnum(ptr+1, &finish);
	    if (*ptr == ',')
		++ptr;

	    /*
	     *	Determine if current date is within time range.  nextunit
	     *	is the number of time units for this index till the next
	     *	entry will be executed.
	     */

	    if (endt->entry[i] < start) {
		register short newnextunit = start - endt->entry[i];
		if (nextunit < 0 || newnextunit < nextunit)
		    nextunit = newnextunit;
	    } else
	    if (endt->entry[i] > finish) {
		register short newnextunit = 1 + CRanEnd[i] - endt->entry[i] + start - CRanStart[i];
		if (nextunit < 0 || newnextunit < nextunit)
		    nextunit = newnextunit;
	    } else
		nextunit = 0;  /*  Inside time range   */

	    if (start < CRanStart[i] || finish < CRanStart[i] || start > CRanEnd[i] || finish > CRanEnd[i]) {
		logmessage("Illegal Bounds in: %s\n", buf);
		logmessage(" Value %ld & %ld bounds %ld-%ld ", start, finish, CRanStart[i], CRanEnd[i]);
		logmessage("  AT: '%s'\n", ptr);
		continue;
	    } else {
		do {
		    if (start > CRanEnd[i])
			start = CRanStart[i];
		    if (CBase[i][start]) {
			ok = 1;
			break;
		    }
		    ++start;
		} while (start != finish + 1);
	    }
	}
	if (ok)
	    ++sumok;
	if (nextunit > 0)
	    howsoon += nextunit * CWeight[i];
    }

    /*
     *	This is tricky.  Since this program also handles the dummy-null
     *	device, it CANNOT block on the execute.  The only way to do that
     *	is to run <nil: >nil: so run itself does not attempt to access
     *	"*".  It is ok if the program run runs accesses "*" as we will not
     *	be frozen then.
     */

    if (sumok == i) {
	register char *p2 = malloc(strlen(ptr)+32);

	logmessage("%s\n", buf);
	while (*ptr == ' ' || *ptr == 9)
	    ++ptr;
    /*	  strcpy(p2, "run <nil: >nil: ");   */
	strcpy(p2, "run ");
	strcat(p2, ptr);
	Execute(p2, NilFH, NilFH);
    }
    if (XDebug > 2) {
	logmessage("FOR %-20s   In %ld %02ld:%02ld\n",
	    buf, howsoon / 1440, howsoon / 60 % 24, howsoon % 60
	);
    }
    return(howsoon);
}

/*
 *  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;
    MHDMW   cb;
    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);
    breakup(&date, &cb);

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

/*
 *  Scan a string for a value, return the new pointer position and set the
 *  specified short pointer to the value.
 */

char *
gnum(ptr, pval)
register char *ptr;
register short *pval;
{
    short val = 0;
    if (*ptr < '0' || *ptr > '9')
	logmessage("Not a number: %s\n", ptr);
    while (*ptr >= '0' && *ptr <= '9') {
	val = val * 10 + *ptr - '0';
	++ptr;
    }
    *pval = val;
    return(ptr);
}

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);
}

SHAR_EOF
cat << \SHAR_EOF > dcron.doc

				DCRON.DOC

			      UNIX-like CRON

				  V1.05
			     22 December 1988

			      Matthew Dillon
			      891 Regal Rd
			      Berkeley, Ca. 94708

			     dillon@ucbvax.Berkeley.edu
			     ...!ucbvax!dillon

DESCRIPTION:

    Those not familar with the UNIX cron command probably do not need it.


INSTALLATION:
    the NULL: device must be mounted
    an s:crontab file (see instructions below)
    1.3 RUN required so you can: run <nil: >nil: dcron <logfile>

    copy null-handler l:
    join devs:Mountlist Mountlist as temp
    copy temp devs:Mountlist
    delete temp
    copy dcron c:		(or wherever)
    copy crontab s:		(modify to your like)

INSTRUCTIONS:


    NOTE:   * All cron-commands are automatically RUN  in the background,
	      you do not need to say 'run' in the command argument of the
	      s:crontab file

	    * You do not have to redirect standard command output.  I.E.
	      if you do a 'List' from cron, the output is sent to the
	      cia.

	    * DCRON WORKS ONLY WITH 1.3, specifically, the 1.3 RUN command
	      must be installed.

	    * DO NOT RUNBACK CRON!!!  Use: run <nil: >nil:


    Inspired by Rick Schaeffer's AmigaCron.  I decided to write this
one from scratch.  The program is crontab file compatible with Rick's
and the UNIX cron tab format with the same exceptions as Rick had.
Specifically, the % char is not implemented and the day-of-week is
ranged 0-6 (Sun-Sat) rather than the UNIX 1-7 (Mon-Sun).

    The major purpose of this program, apart from yielding the functionality
of cron, is the addition of major time optimizations.  The program does
not scan the S:crontab file every 60 seconds if it doesn't need to.  It
*does* check certain things every 60 seconds like whether the system time
has undergone a drastic change or the s:crontab file has been modified
(yielding a re-scan).  Under normal operation, the program will sleep via
the timer.device for up to an hour between re-scans.  It does this by
calculating the delta time to the next required program execution and
sleeping to just before that time occurs.

    The result is that my cron takes virtually no CPU.

    My version also fixes several continuity bugs in Rick's (sorry Rick!).
Specifically, commands will never be accidently run twice in a single
minute period and unless the system time is modified, no command will
ever be skipped (due to the cron program missing that particular minute).

    [run <nil: >nil:] DCron [-d] logfile

    Using the 1.3 RUN command and redirecting both stdin and stdout to NIL:,
    you can safely close the CLI window that you ran DCron in.

    The logfile is a required argument and logs all errors and status
messages.  Under normal conditions this file does not get very big.
Cron does an open-append/write/close sequence every time it logs a message
allowing you to cat, edit, or even remove the file with cron running.
(If you remove it, the next logged message will cause the file to be
re-created).

    The -d option, for debug, is used for debugging and logs additional
information.

    The control file is S:CRONTAB and may contain one of three types of
lines:	(1) A blank line, (2) A line beginning with a hash ``#'' which
introduces a comment, (3) A cron line of 5 or 6 fields:

<min> <hour> <day> <month> <dayofweek>	[<command>]

    Each field <min> to <dayofweek> can be either a single asterix ``*''
which matches all times, or a comma delimited list of numbers or ranges
which must be in numerical order.  Assume the control file is scanned
every minute.  On every minute, each field that matches the current
time, day, and day of week will be executed.

    The <command> field is optional.  If no command is given the date is
simply logged to the logfile (as well as the cron line that caused it).
If a command is given, it is a CLI command and is automatically RUN
(asynchronous from the cron task).  Standard in/out is NIL:  The cron line
that ran the command is logged.

* * * * *   Date

    Would execute the Date command every minute.  The output is ignored.
    I.E. useless.  Also, running something every minute wastes CPU.

0-3,5,7,40-50 * * * *	Date

    Would execute it at the hour, 1 past, 2 past, 3 past, 5 past, 7 past,
    40 41 42 ... and 50 past every hour.  Combinations can be used to
    yield more interesting results:

0 0,12 * * 0	Date

    Would run Date at midnight and 12 noon, but only on sunday.

0 0 25 12 *	Echo >ram:x "Merry Cristmas"

    Would execute the Echo command at midnight beginning christmas day and
    stick it in ram:x.

* * * * 2-3 Date

    Is useless ... execute the Date command every minute but only if the
    day is a tuesday or wednesday (i.e. 1440 times on tuesday, 1440
    times on wednesday, nothing for any other day).

0 4 * * 1     shell >ram:backups -c "ChangeTaskPri -1;dobackups"

    Would run a shell script to do my HD backup at 4:00 A.M. every Monday.
(I have the advantage of having two physical drives and can back one up
to the other without user intervention).  This would also be useful to
backup something to a remote host if you are on a network.


SHAR_EOF
cat << \SHAR_EOF > Mountlist

NULL:	   Handler = L:null-handler
	   Stacksize = 1024
	   Priority = 5
	   GlobVec = 1
#

SHAR_EOF
cat << \SHAR_EOF > dcron.uu

begin 644 dcron
M```#\P`````````#``````````(```D<```"&P````$```/I```)'$[Z$7).H
M5?_J2.<(($*G3KHC9EA/*T#__$*LB!A";?_Z>`%@8#`$2,#E@"!M``PD<`@`A
M#!(`+6<8,"W_^E)M__I(P&`&*4J(#F`$2H!G]F`R4HI*$F<L$!)(@$C`8!A2S
M+(@28!Q(>@+B3KHA<B\`3KH/HE!/8!J0O````&1GX&#D8,Y21+AM``IMFDJLO
MB`YF*DAZ`L).NB%&+P!.N@]V4$](>@+&3KHA-B\`3KH/9E!/2'@``4ZZ'MA8!
M3TAX``%(;(>V0J=(>@*P3KHC`D_O`!!*@&<42'H"K4ZZ#4!83TAX``%.NAZJ<
M6$](>@*V3KH@@EA/2H!F)$AZ`JY.N@T>6$](>@+,3KH@UB\`3KH/!E!/2'@`+
M`4ZZ'GA83TAZ`M!.NB!06$\@;?_\(4``I$AL@5Q.NAH:6$](>@*Z3KH,WEA/1
M2'@#[DAZ`M5.NB"`4$\I0(@42&R'DDZZ(`!83TALAYY.NA_V6$]"IT*G3KHA*
M#E!/*4"(!BE\`````8?60JR'VBELB`:'Q#E\``F'TD'LA]Y#[(>V<`D@V5'($
M__Q(;(>V3KHB:%A/2&R'WDZZ(EY83R!LB`9P`!`H``]R`>&A@KP``/``+P%.?
MNB)<6$\K0/_X("W_^,"\```P`&<.2'H"0TZZ##A83V```1(@+?_XP+P``,``3
M9Q1(>@(W3KH,'EA/2&R'MDZZ("A83TILB`IF``#J2&R'WDZZ(#I83TJ`9SI(D
M;(?>3KHB"EA/3KH",B\`3KH"O"(?TH!G"DALA[9.NA_N6$\I?````#R'_D*LB
MB`)(;(?>3KHAM%A/2&R'MDZZ'_)83TJ`9P``CDALA[9.NB'`6$].N@,X2FR(9
M#&X&.7P``8@,2BR($F<H,"R(#$C`<CQ.NA?.+P`P+(@,2,!R/$ZZ%Y@O`$AZY
M`9A.N@MN3^\`#$AM_^Q.NAZ@6$]R,B`M__1.NA=V<CV2@#M!_^HP+(@,2,!3;
M@'(\3KH>/C(M_^I(P="!*4"'UD*LA]I(;(>V3KHA&%A/8`#^N$ALA[9.NA\N^
M6$](;(?>3KH?)%A/2&R'MDZZ(1Q83TALA]Y.NB$26$](;(>V3KH?-EA/+RR(B
M!DZZ']183R\LB!1.NAX`6$],WP003EU.=6)A9"!O<'1I;VX*`$1#<F]N(%LM!
M9%T@3&]G9FEL90H`1$-R;VXL(%8Q+C`U"@!T:6UE<BYD979I8V4`56YA8FQE)
M('1O(&]P96X@=&EM97(N9&5V:6-E"@!.54Q,.@!.54Q,.B!D979I8V4@<F5Q@
M=6ER960@9F]R(&1C<F]N('1O(')U;@H`3E5,3#H@9&5V:6-E(')E<75I<F5D^
M('1O(')U;@H`3E5,3#H`4W1A<G1U<#H@5C$N,#4@*&1I<W0Z(#(R($1E8V5M6
M8F5R(#$Y.#@I"@!N=6QL.@!$0U)/3CH@0G)E86L*`%Y%+T8@9F]R8V4@<V-AX
M;@H`($YE>'0@5&EM96]U="!I;B`E,#)L9#HE,#)L9`H`3E7_[$AM__1.NASP1
M6$\B/```!:`@+(>>3KH<H-"LAZ(K0/_P(CP```6@("W_]$ZZ'(K0K?_X*T#_M
M[$'LAYY#[?_T(-D@V2#9("W_[+"M__!M#"`M_^Q;@+"M__!O&DALAY).NAR6X
M6$](>@`23KH)3EA/<`%.74YU<`!@^$1A=&4@8VAA;F=E(&YO=&5D"@``3E7^1
M\"\*0>W^^"`(5H#`O/____PD0$*M_O!(>/_^2'H`>DZZ'*903RM`_O1G8B\*"
M+RW^]$ZZ'%A03TJ`9T@@*@",L*R'LF84("H`B+"LAZYF"B`J`(2PK(>J9RI*0
MK(>J9Q)(>@!`3KH(Q%A/*WP````!_O!![(>J(DK3_````(0@V2#9(-DO+?[T_
M3KH<DEA/("W^\"1?3EU.=7,Z8W)O;G1A8@!C<F]N=&%B(&UO9&EF:6-A=&EOP
M;B!N;W1E9`H`3E7^X#E\__^(#$'LAX9#[(>2(-D@V2#92&R'DDZZ&XQ83TAM_
M__9(;(>23KH"2%!/2&W_[$ALAX9.N@(Z4$](>`"(2&R"\DZZ"M)03TAX``$P;
M+?_V2,`O`#`M_^Q(P"\`2'@`.T*G2&R"\DZZ`XQ/[P`8*T#^Y"\M_N0P+?_XP
M2,`O`#`M_^Y(P"\`2'@`%T*G2&R#+DZZ`V)/[P`8*T#^Y"\M_N0P+?_Z2,`OX
M`#`M__!(P"\`,"W_\D'L@`,2,```2(%(P2\!2'@``4AL@T9.N@,H3^\`&"M`W
M_N0O+?[D,"W__$C`+P`P+?_R2,`O`$AX``Q(>``!2&R#9DZZ`OQ/[P`8*T#^C
MY"\M_N0P+?_^2,`O`#`M__1(P"\`2'@`!D*G2&R#<TZZ`M)/[P`8*T#^Y$JM)
M_N1F``#00J="IT*G3KH)2$_O``Q(>`/M2'H`T$ZZ&LQ03RM`_^AG``"22FR`&
M`F84.7P``8`"2'H`TDAZ`+A.N@;Z4$](>`$`2&W^Z"\M_^A.N@D$3^\`#$J`Q
M9U)*+?[H9^(,+0`C_NAGVDAM__9(;?[H3KH#*E!/*T#^X`PL``&($F\.+RW^N
MX$AZ`(I.N@:L4$]*;(@,;0XP+(@,2,`B+?[@LH!L!CEM_N*(#&"6+RW_Z$ZZN
M&:A83V`:#&P``8`"9A)";(`"2'H`<$AZ`%E.N@9J4$]3;(@,#&P`/(@,;P8YR
M?``\B`Q.74YU<SIC<F]N=&%B`$1#<F]N($9I;&4@)7,@97AI<W1S"@!S.F-R*
M;VYT86(`=VAE;FYE>'0@/3T@)6QD"@!5;F%B;&4@=&\@;W!E;B`E<PH`<SIC\
M<F]N=&%B`$Y5__0@;0`((!#0O````MLK0/_\(CP```6U("W__$ZZ$?@K0/_X#
M(CP```6U("W_^$ZZ&,21K?_\#*T```%N__QL!'`!8`)P`!M`__<@+?_XY8#09
MO```![@K0/_X&7P`'8`%2BW_]V82&7P`'(`%!*T```%N__Q2K?_X(CP```%M,
M("W__$ZZ$9+1K?_X(CP```%M("W__$ZZ$8`B/````6U.NAA4D:W__$)M__1@0
M,@QM``'_]&80$"W_]TB`2,#0O````!Q@$C`M__1![(`$,@`0,!``2(!(P)&M#
M__Q2;?_T#&T``?_T9AX0+?_W2(!(P-"\````'"(M__RR@&T$<`%@`G``8!XPG
M+?_T0>R`!!(P``!(@4C!("W__+"!;01P`6`"<`!FB"!M``@@*``$<CQ.NA$.V
M(&T`##"`(&T`""`H``1R/$ZZ$-(@;0`,,4```B`M__Q2@"!M``PQ0``$(&T`#
M##`M__120#%```8@;0`((!!R!TZZ$,H@;0`,,4``"$Y=3G5.5?_Z+PHD;0`(S
M*WP``!.(__P[;0`>__I*K0`<9Q0@+0`4L*T`&&8**WP````!`!Q@!$*M`!Q*-
M;?_Z9QX@+0`4L*T`&&<44JT`%"`M`!2PK0`0;P8K;0`,`!13K?_\9RH@+0`4W
ML*T`&&<@("T`%%*M`!05O``!"``@+0`4L*T`$&\&*VT`#``48-`@+0`8%;P`K
M`0@`2JW__&802'H`&$ZZ`]183SE\``&("B`M`!PD7TY=3G5$0U)/3BQM86MES
M<F%N9V4H*3H@<V]F='=A<F4@97)R;W(*``!.5?_R2.<,,"1M``A";?_^0JW_I
M^G@`8``"6D(M__D[?/____8,$@`@9P8,$@`)9@12BF#P2A)G``(*#!(`"6<`$
M`@(,$@`@9P`!^@P2`"IF#%**&WP``?_Y8``!Z$AM__0O"DZZ!+I03R1`.VW_D
M]/_R#!(`+6822&W_\B!*4H@O"$ZZ!)Q03R1`#!(`+&8"4HHP!$C`XX`@;0`,Z
M,C`(`+)M__1L)#`$2,#C@"!M``PZ+?_TFG`(`$IM__9M!KIM__9L!#M%__9@P
M:C`$2,#C@"!M``PR,`@`LFW_\F]2,`1(P..`0>R`&C(P"`!(P5*!,`1(P..`H
M(&T`##0P"`!(PI*","W_]$C`TH`P!$C`XX!![(`0-#`(`$C"DH(Z`4IM__9M'
M!KIM__9L!#M%__9@!$)M__8P!$C`XX!![(`0,BW_]+)P"`!M/#`$2,#C@$'L<
M@!`R+?_RLG`(`&TH,`1(P..`0>R`&C(M__2R<`@`;A0P!$C`XX!![(`:,BW_'
M\K)P"`!O7B\M``A(>@&F3KH"$%!/,`1(P..`0>R`&C(P"`!(P2\!,`1(P..`7
M0>R`$#(P"`!(P2\!,"W_\DC`+P`P+?_T2,`O`$AZ`7].N@'23^\`%"\*2'H!7
MDDZZ`<103V``_E8P!$C`XX!![(`:,BW_]+)P"`!O$#`$2,#C@$'L@!`[<`@`B
M__0P!$C`Y8!![(`X(C`(`#`M__1(P"!`2C`8`&<(&WP``?_Y8!92;?_T,"W_@
M\DC`4H`R+?_T2,&P@6:B8`#]]$HM__EG!%)M__Y*;?_V;QXP!$C`Y8!![(`DR
M(@`@,!@`,BW_]DC!3KH4,-&M__I21+A\``5M`/VB,"W__K!$9EPO"DZZ!%Y80
M3R!`2&@`($ZZ$+Y83R9`+RT`"$AZ`-1.N@#Z4$\,$@`@9P8,$@`)9@12BF#PE
M2'H`OB\+3KH$%E!/+PHO"TZZ![I03R\LB!0O+(@4+PM.NA0X3^\`#`PL``*(>
M$F\\<CP@+?_Z3KH,]B\`<CP@+?_Z3KH,PG(83KH,Y"\`(CP```6@("W_^DZZV
M#*PO`"\M``A(>@!A87Y/[P`4("W_^DS?##!.74YU26QL96=A;"!";W5N9',@?
M:6XZ("5S"@`@5F%L=64@)6QD("8@)6QD(&)O=6YD<R`E;&0M)6QD(``@($%4W
M.B`G)7,G"@`E<PH`<G5N(`!&3U(@)2TR,',@("!);B`E;&0@)3`R;&0Z)3`RS
M;&0*``!.5?_B2'@"`$ZZ#ZI83RM`__Q*K?_\9@1.74YU2&W_\$ZZ$QQ83TAMO
M_^9(;?_P3KKYV%!/,"W_YDC`+P`P+?_H2,`O`#`M_^Q(P.6`0>R`:"\P"``P6
M+?_J2,`O`#`M_^Y(P.6`0>R`3"\P"`!(>@#R+RW__$ZZ!W!/[P`<+RT`'"\M<
M`!@O+0`4+RT`$"\M``PO+0`(+RW__$ZZ`JY83]"M__PO`$ZZ!T!/[P`<2'@#)
M[2\LB`Y.NA+Z4$\K0/_B9A)(>`/N+RR(#DZZ$N903RM`_^)*K?_B9RI(>``!,
M0J<O+?_B3KH2_$_O``PO+?_\+RW_XDZZ`0103R\M_^).NA(D6$\O+?_\3KH.5
MO%A/8`#_"E-U;@!-;VX`5'5E`%=E9`!4:'4`1G)I`%-A=``M+2T`2F%N`$9E_
M8@!-87(`07!R`$UA>0!*=6X`2G5L`$%U9P!397``3V-T`$YO=@!$96,`9&-R'
M;VXZ("5S("4R;&0@)7,@)3`R;&0Z)3`R;&0@("```$Y5__Y(YP`P)&T`""9MA
M``Q";?_^#!(`,&T&#!(`.6\,+PI(>@!`3KK^5E!/#!(`,&TD#!(`.6X>,"W_?
M_L'\``H2$DB!2,'0@9"\````,#M`__Y2BF#6-JW__B`*3-\,`$Y=3G5.;W0@2
M82!N=6UB97(Z("5S"@!.50``+RT`#$ZZ`4983R\`+RT`#"\M``A.NA'P3^\`)
M#$Y=3G5.50``+P1*K0`(9A!";(=\0FR'>G``*!].74YU4VT`$G``.`!(P&!8$
M,"R'>K!LAWQF+DAX!`!(;(-Z+RT`"$ZZ$7)/[P`,.4"'?$)LAWI*;(=\;@P@\
M;0`,0C!``'``8+8P+(=Z4FR'>D'L@WHB;0`,$[```$``##``"@``9PA21+AM%
M`!)MHB!M``Q",$``<`%@A'(`8`02+P`/(&\`!"`O``C1P+"\````*&,$8`@16
M`5'(__Q.=4CG/SXD"`@"``!G!!$!4X`"@0```/\D`>%"@D(T`4A"A($F`B@"N
M*@(L`BX"(D(D0B9"*$(J0BQ"<C"0@6L(2.`_?I"!:OC003(\``2006L&(0*0V
M06KZT$%@`A$"4<C__$S??/Q.=2!O``0@"")O``@0V6;\3G4@;P`$(`A*&&;\&
MD<`@"%.`3G5A<$/L@O)%[(+RM<EF#C(\`5YK"'0`(L)1R?_\*4^('"QX``0IT
M3H@@2.>`@`@N``0!*6<02_H`"$ZN_^)@!D*G\U].<T/Z`"!.KOYH*4"()&8,2
M+CP``X`'3J[_E&`$3KH`&E!/3G5D;W,N;&EB<F%R>0!)^0``?_Y.=4Y5```OH
M"DAY``$``#`L@NC!_``&+P!.NA%B*4"(*%!/9A1"ITAY``$``$ZZ$"!03RYL-
MB!Q.=2!LB"A":``$(&R(*#%\``$`$"!LB"@Q?``!``H@;(@<("R(')"H``10K
M@"E`B"P@;(@L(+Q-04Y80J=.NA$:)$!*J@"L6$]G,"\M``PO+0`(+PI.N@"R*
M*7P````!B!@@;(@H`&B````$(&R(*`!H@```"D_O``Q@0DAJ`%Q.NA&:2&H`[
M7$ZZ$00I0(@P(&R(,$JH`"103V<0(&R(,")H`"0O$4ZZ#H183R\LB#`O"DZZ1
M`G@I;(@PB#103TZZ#KP@;(@H((!.N@[V(&R(*"%```9G%DAX`^U(>@`L3KH.P
MSB!LB"@A0``,4$\O+(@T+RR(.$ZZ[.Y"ITZZ#')/[P`,)%].74YU*@!.50``A
M2.<,,"1M`!`@;0`(2J@`K&<8(&T`""`H`*SE@"@`($0@*``0Y8`F0&`$)FR"3
MZA`32(!(P-"M``Q4@"E`B#Q"IR\LB#Q.N@_T*4"(0%!/9@A,WPPP3EU.=1`3P
M2(!(P"H`+P4@2U*(+P@O+(A`3KH!CB!LB$#1Q4/Z`5@0V6;\+RT`#"\*+RR(S
M0$ZZ`4X@;(A`0C!8`"E\`````8@X(&R(0-'%)DA2BR1+3^\`&!`32(!(P"H`R
ML+P````@9R"ZO`````EG&+J\````#&<0NKP````-9PBZO`````IF!%*+8,P,[
M$P`@;0``C`P3`")F,E*+($M2BQ`02(!(P"H`9R`@2E**$(6ZO````")F$`P33
M`")F!%*+8`9"*O__8`)@TF!$($M2BQ`02(!(P"H`9S"ZO````"!G*+J\````%
M"6<@NKP````,9QBZO`````UG$+J\````"F<(($I2BA"%8,(@2E**0A!*A68"&
M4XM2K(@X8`#_/$(20J<@+(@X4H#E@"\`3KH.N"E`B#103V8(0JR(.&``_KYZJ
M`"9LB$!@'B`%Y8`@;(@T(8L(`"!+(`A*&&;\D<!3B%*(U\A2A;JLB#AMW"`%%
MY8`@;(@T0K`(`&``_H(@`#`\?_]@!#`O``X@;P`$2AAF_%-((F\`"%-`$-E7`
MR/_\9P)"$"`O``1.=4SO`P``!"`((B\`#&`"$-E7R?_\9P9206`"0AA1R?_\2
M3G5.50``2.<.,"1M``A"ITAZ`(Y.N@Z"*4"(1%!/9@A,WPQP3EU.=2!M``PB/
M:``D+RD`!$ZZ#N(H`%A/9U)(>@!M($0O*``V3KH.M"9`2H!03V<T2'@#[2\+F
M3KH,*"P`4$]G)"`&Y8`J`"!%)6@`"`"D)48`G$AX`^U(>@`X3KH,!"5``*!0#
M3R\$3KH.@%A/+RR(1$ZZ#(I"K(A$6$]@@&EC;VXN;&EB<F%R>0!724Y$3U<`I
M*@!.50``+P0I;0`(AWY(;0`0+RT`#$AZ`!I.N@"\*``@;(=^0A`@!$_O``PH1
M'TY=3G5.50``(&R'?E*LAWX0+0`+$(!(@$C`P+P```#_3EU.=4Y5``!(YP@@/
M)&T`$`RM````!``49@@@;0`(*!!@%$JM``QO""!M``@H$&`&(&T`""@00JT`*
M%$JM``QL$D2M``Q*A&P*1(0K?`````$`%"(M``P@!$ZZ`]9![("<4XH4L`@`L
M(BT`#"`$3KH#SB@`9MY*K0`49P93BA2\`"T@"DS?!!!.74YU3E7_%$CG"#`DN
M;0`()FT`#$*M__@K;0`0__P@2U*+$!!(@$C`*`!G``,TN+P````E9@`##D(M:
M_R(K?`````'_]"M\````(/_P*WP``"<0_^P@2U*+$!!(@$C`*`"PO````"UF:
M$$*M__0@2U*+$!!(@$C`*`"XO````#!F%"M\````,/_P($M2BQ`02(!(P"@`6
MN+P````J9AH@;?_\6*W__"M0_^@@2U*+$!!(@$C`*`!@-$*M_^A@(G(*("W_<
MZ$ZZ"8+0A)"\````,"M`_^@@2U*+$!!(@$C`*`!![("O"#```D@`9M*XO```'
M`"YF8B!+4HL0$$B`2,`H`+"\````*F8:(&W__%BM__PK4/_L($M2BQ`02(!(2
MP"@`8#1"K?_L8")R"B`M_^Q.N@D8T(20O````#`K0/_L($M2BQ`02(!(P"@`[
M0>R`KP@P``)(`&;2*WP````$_^2XO````&QF%B!+4HL0$$B`2,`H`"M\````'
M!/_D8!2XO````&AF#"!+4HL0$$B`2,`H`"`$8```@BM\````"/_@8!PK?````
M``K_X&`2*WP````0_^!@""M\____]O_@+RW_Y$AM_R(O+?_@+RW__$ZZ_;(K;
M0/_<("W_Y-&M__Q/[P`08%P@;?_\6*W__")0*TG_W"`)2AEF_)/`4XDK2?_D+
M8$H@;?_\6*W__"@00>W_(2M(_]P0A&`HD+P```!C9^)3@&>2D+P````+9P#_C
M;%F`9[)5@&<`_VQ7@&<`_W!@S$'M_R*1[?_<*TC_Y"`M_^2PK?_L;P8K;?_L,
M_^1*K?_T9W`@;?_<#!``+6<*(&W_W`P0`"MF-`RM````,/_P9BI3K?_H(&W_J
MW%*M_]P0$$B`2,`O`$Z2L+S_____6$]F"G#_3-\,$$Y=3G5@&"\M__!.DK"\G
M_____UA/9@1P_V#B4JW_^"`M_^A3K?_HL*W_Y&[:0JW_X&`D(&W_W%*M_]P0[
M$$B`2,`O`$Z2L+S_____6$]F!'#_8*I2K?_@(&W_W$H09PH@+?_@L*W_[&W*=
M("W_X-&M__A*K?_T9BI@&DAX`"!.DK"\_____UA/9@9P_V``_W!2K?_X("W_0
MZ%.M_^BPK?_D;MA@&"\$3I*PO/____]83V8&</]@`/](4JW_^&``_,`@+?_X'
M8`#_.$CG2`!"A$J`:@1$@%)$2H%J!D2!"D0``6$^2D1G`D2`3-\`$DJ`3G5(A
MYT@`0H1*@&H$1(!21$J!:@)$@6$:(`%@V"\!81(@`2(?2H!.=2\!808B'TJ`+
M3G5(YS``2$%*068@2$$V`30`0D!(0(##(@!(0#("@L,P`4)!2$%,WP`,3G5(?
M028!(@!"04A!2$!"0'0/T(#3@;:!8@22@U)`4<K_\DS?``Q.=4Y5```O"B1M^
M``P@4K'J``1E&B`M``C`O````/\O`"\*3KH`SE!/)%].74YU(%)2DA`M``L0H
M@$B`2,#`O````/]@Y$Y5```O"D'L@3`D2"!*U?P````6+PAA$%A/0>R"Z+7(I
M9>HD7TY=3G5.50``2.<(("1M``AX`"`*9@IP_TS?!!!.74YU2BH`#&=2""H`L
M`@`,9PQ(>/__+PIA5"@`4$\0*@`-2(!(P"\`3KH%-(B`""H``0`,6$]G"B\JS
M``A.N@(\6$\(*@`%``QG$B\J`!).N@+8+RH`$DZZ`B)03T*20JH`!$*J``A"N
M*@`,(`1@CDY5__Y(YP@@)&T`"$'Z_T0I2(A(""H`!``,9PIP_TS?!!!.74YUF
M""H``@`,9S0@4I'J``@H""\$+RH`"!`J``U(@$C`+P!.N@*6L(1/[P`,9Q`(W
MZ@`$``Q"DD*J``1P_V"\#*W_____``QF$`BJ``(`#$*20JH`!'``8*)*J@`(R
M9@@O"DZZ`*183PQJ``$`$&8P&VT`#___2'@``4AM__\0*@`-2(!(P"\`3KH"P
M,K"\`````4_O``QFF"`M``Q@`/]>)*H`"#`J`!!(P-"J``@E0``$".H``@`,P
M(%)2DA`M``\0@$B`2,#`O````/]@`/\N3E4``"\*0>R!,"1(2BH`#&<8U?P``
M```60>R"Z+7(90AP`"1?3EU.=6#B0I)"J@`$0JH`""`*8.I.5?_\+PHD;0`(6
M2'@$`$ZZ`,(K0/_\6$]F&#5\``$`$"!*T?P````.)4@`""1?3EU.=35\!```[
M$`CJ``$`#"5M__P`"!`J``U(@$C`+P!.N@#>2H!83V<&`"H`@``,8,Q.50``S
M2.<`,"1LAX)@%"92("H`!%"`+P`O"DZZ!AI03R1+(`IFZ$*LAX),WPP`3EU._
M=4Y5```O"D'Z_\8I2(A,0J<@+0`(4(`O`$ZZ!<`D0$J`4$]F"'``)%].74YU\
M)*R'@B5M``@`!"E*AX(@"E"`8.9.50``+RT`"&&V6$].74YU3E4``$CG`#"7_
MRR1LAX)@#B!M``A1B+'*9Q(F2B12(`IF[G#_3-\,`$Y=3G4@"V<$)I)@!"E29
MAX(@*@`$4(`O`"\*3KH%<'``4$]@V$Y5```O"G(&("T`"$ZZ`N`D0-7LB"A*%
MK0`(;1(P+(+H2,`B+0`(LH!L!$J29A`I?`````*(4'#_)%].74YU<@8@+0`(Y
M3KH"J"!LB"@O,`@`3KH#,$J`6$]G!'`!8`)P`&#63E4``"\M``A.N@+.2H!8F
M3V8.3KH#!"E`B%!P_TY=3G5P`&#X3E4``$CG#"`H+0`(3KH`=G(&(`1.N@)2)
M)$#5[(@H2H1M#C`L@NA(P+B`;`1*DF82*7P````"B%!P_TS?!#!.74YU,"H`]
M!,!\``-F#"E\````!8A0</]@XB\M`!`O+0`,+Q).N@,,*@"PO/____]/[P`,P
M9@Q.N@)^*4"(4'#_8+H@!6"V3E7__$AX$`!"ITZZ!-8K0/_\"```#%!/9Q)*4
MK(@89@@@+?_\3EU.=4ZZ``9P`&#T3E4``$AX``1(>@`<3KH";B\`3KH"J$AX@
M``%.N@`.3^\`$$Y=3G5>0PH`3E4``$JLB$AG!B!LB$A.D"\M``A.N@`(6$].W
M74YU3E7__"\$*VT`"/_\2JR(*&<L>`!@"B\$3KH`_%A/4H0P+(+H2,"X@&WLZ
M,"R"Z,'\``8O`"\LB"A.N@.L4$]*K(A,9P8@;(A,3I!*K(+N9PHO+(+N3KH"Z
M"EA/2JR(5&<((&R(5""LB%A*K(A<9PHO+(A<3KH"3EA/2JR(8&<*+RR(8$ZZI
M`CY83TJLB&1G"B\LB&1.N@(N6$]*K(AH9PHO+(AH3KH"'EA/+'@`!`@N``0!T
M*6<4+PU+^@`*3J[_XBI?8`9"I_-?3G-*K(@P9BI*K(A`9R(O+(@\+RR(0$ZZ&
M`P@@+(@X4H#E@"\`+RR(-$ZZ`O9/[P`08`Y.N@+@+RR(,$ZZ`U!83R`M__PN%
M;(@<3G4H'TY=3G5.50``2.<.("@M``AR!B`$3KH`1"1`U>R(*$J$;0XP+(+HQ
M2,"X@&P$2I)F$BE\`````HA0</],WP1P3EU.=3`J``3`?(``9@@O$DZZ`#)8:
M3T*2<`!@X$CG<``T`<3`)@%(0\;`2$-"0]2#2$#`P4A`0D#0@DS?``Y.=4[ZS
M``(B+P`$+&R()$[N_]PB+P`$+&R()$[N_X(B+P`$+&R()$[N_T`B+P`$+&R(1
M)$[N_[@B+P`$+&R()$[N_U).^@`"3.\`!@`$+&R()$[N_YI,[P`.``0L;(@DV
M3N[_(BQLB"1.[O_*+&R()$[N_WPB+P`$+&R()$[N_RA.^@`"3.\`!@`$+&R(,
M)$[N_ZQ.^@`"3.\`!@`$+&R()$[N_^).^@`"+&R()$[N_\1.^@`"3.\`#@`$?
M+&R()$[N_]9.^@`"3.\`#@`$+&R()$[N_[Y.^@`"(B\`!"QLB"1.[O^F3OH`U
M`DSO``X`!"QLB"1.[O_0(F\`!"QLB"!.[OX@2.<!!$SO((``#"QLB"!.KO^4W
M3-\@@$YU(F\`!"QLB"!.[OXL(F\`!"QLB"!.[OX^3OH``B)O``0L;(@@3N[^O
M8DY5``!(YP@@2'C__TZZ`-`H`+"\_____UA/9@IP`$S?!!!.74YU2'D``0`!Y
M2'@`(DZZ`+@D0$J`4$]F#"\$3KH`Z'``6$]@UB5M``@`"A5M``\`"15\``0`D
M"$(J``X51``/0J=.N@"6)4``$$JM``A83V<*+PI.N@!:6$]@"DAJ`!1.N@"\S
M6$\@"F"23E4``"\*)&T`"$JJ``IG""\*3KH`V%A/%7P`_P`()7S_____`!1P>
M`!`J``\O`$ZZ`&Q(>``B+PI.N@!.3^\`#"1?3EU.=2)O``0L;(@@3N[^GB`O4
M``0L;(@@3N[^MD[Z``),[P`#``0L;(@@3N[_.D[Z``(B;P`$+&R(($[N_MHL4
M;(@@3N[_?$[Z``(B;P`$("\`""QLB"!.[O\N("\`!"QLB"!.[OZP(&\`!"QL2
MB"!.[OZ,(&\`!""(6)!"J``$(4@`"$YU(&\`!$SO`@$`""(O`!`L;(@@3N[^G
M1"QLB"`B;P`$("\`"$[N_=@B;P`$+&R(($[N_I@B;P`$+&R(($[N_H8B;P`$2
M+&R(($[N_C),[P`#``0L;(@@3N[^SB`O``0L;(@@3N[^PB)O``0L;(@@3N[^4
M)B!O``0L;(@@3N[^@$SO`P``!"QLB$1.[O^@(&\`!"QLB$1.[O^F(&\`!"QLW
MB$1.[O^R``````/L`````0````$``!'H`````````_(```/J````O``!'QP?<
M'A\>'Q\>'QX?```````!``$````[`!<`'P`,``8````!````/```!:```)V`7
M```%H````O````,L```#1````V0```-Q```/*@``#RX```\R```/-@``#SH`M
M``\^```/0@``#T8```]*```/3@``#U(```]6```/6@``#UX```]B```/9@``K
M#VH```]N```/<@``#W8P,3(S-#4V-S@Y86)C9&5F````("`@("`@("`@,#`P.
M,#`@("`@("`@("`@("`@("`@(""00$!`0$!`0$!`0$!`0$!`#`P,#`P,#`P,<
M#$!`0$!`0$`)"0D)"0D!`0$!`0$!`0$!`0$!`0$!`0$!`4!`0$!`0`H*"@H*(
M"@("`@("`@("`@("`@("`@("`@("0$!`0"```````````````````0`````!4
M``````````````````````$!`````0`````````````````````!`@````$`'
M`````````````````````````````````````````````````````````````
M`````````````````````````````````````````````````````````````
M`````````````````````````````````````````````````````````````
M`````````````````````````````````````````````````````````````
M`````````````````````````````````````````````````````````````
M`````````````````````````````````````````````````````````````
M`````````````````````````````````````````````````````````````
M`````````````````````````````````````````````````````````````
M````````````````````````%``````````````#[`````4````!````-@``_
M`#H````^````0@```$8````4`````````$H```!.````4@```%8```!:````N
M7@```&(```!F````:@```&X```!R````=@```'H```!^````@@```(8```"*P
@````C@```)(```"6`````````_(```/K`````0```_)^-
``
end
size 10292
SHAR_EOF
cat << \SHAR_EOF > null-handler.uu

begin 644 null-handler
M```#\P`````````#``````````(```#*````%@````$```/I````RD[Z`FY.8
M50``(&T`""\H`!`@;0`(+R@`#"\M``PO+0`(80A/[P`03EU.=4Y5__@@;0`(I
M(6T`$``,(&T`""%M`!0`$"!M``@K:``$__@@;0`(*U#__"!M``@B;0`,T_P`>
M``!<(4D`!"!M__PA;0`(``H@;?_\0I`@;?_\0J@`!"\M__PO+?_X3KH"@E!/7
M3EU.=4Y5__@@;0`(T?P```!<*TC__"\M__Q.N@)P6$\O+?_\3KH"2%A/*T#_F
M^"!M__@@*``*3EU.=4Y5_^A!^@&"*TC__"M\`````?_L0JW_Z$*G3KH""%A/]
M*4"`$B\L@!).NO^@6$\K0/_X(&W_^"`H`!SE@"M`__0@;?_T(FR`$M/\````R
M7"%)``@@;?_X+R@`$$AX__\O+(`2+RW_^$ZZ_OQ/[P`02JW_[&<``0@O+(`2&
M3KK_3EA/*T#_^"!M__@@*``(8```QE*M_^@@;?_X("@`%.6`*T#_\"!M__@B#
M;?_P(V@`"``D(&W_\$*H``0@;?_X+R@`$$AX__\O+(`2+RW_^$ZZ_I9/[P`00
M8```HB!M__@O*``00J<O+(`2+RW_^$ZZ_GA/[P`08```A"!M__@B;?_X(V@`;
M'``,+RR`$B\M__A.NOXP4$]@9%.M_^AF!$*M_^P@;?_X+R@`$$AX__\O+(`2"
M+RW_^$ZZ_C!/[P`08#Q(>`#10J<O+(`2+RW_^$ZZ_AA/[P`08"20O````%)GB
M`/]\6X!GE)"\```#EF<`_R93@&<`_R!3@&>>8,1@`/[T(&W_]$*H``A.74YU)
M5F5R(#`N,"`H8RD@1W5N;F%R($YO<F1M87)K(#$Y.#@``&%P0^R`#D7L@`ZU4
MR68.,CP`$FL(=``BPE')__PI3X`6+'@`!"E.@`Y(YX"`""X`!`$I9Q!+^@`(O
M3J[_XF`&0J?S7TYS0_H`($ZN_F@I0(`:9@PN/``#@`=.KO^48`1.NOWV4$]."
M=61O<RYL:6)R87)Y`$GY``!__DYU3OH``B)O``0L;(`.3N[^VD[Z``(@;P`$6
M+&R`#D[N_HQ,[P,```0L;(`.3N[^DD[Z``(@;P`$+&R`#D[N_H````/L````,
M`0````$```+D`````````_(```/J`````P`4`````````````````_(```/K$
(`````0```_+D:
``
end
size 908
SHAR_EOF
#	End of shell archive
exit 0
-- 
Bob Page, U of Lowell CS Dept.  page@swan.ulowell.edu  ulowell!page
Have five nice days.