[comp.sources.amiga] v89i015: diffdir - print differences between directories

page@swan.ulowell.edu (Bob Page) (02/04/89)

Submitted-by: mrr@amanpt1.zone1.com (Mark Rinfret)
Posting-number: Volume 89, Issue 15
Archive-name: dos/fs/diffdir.1

#	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:
#	DiffDir.DOC
#	DiffDir.c
#	DiffDir.uu
#	MRDates.c
#	MRDates.h
#	Makefile
#	Sample.Output
# This archive created: Mon Jan 30 18:38:30 1989
cat << \SHAR_EOF > DiffDir.DOC

Program:
    DiffDir - List directory differences.

    (C)Copyright 1988 by Mark R. Rinfret
    All Rights Reserved.
    This software may be freely distributed for non-profit use only.
    You are free to make changes and redistribute this program as
    long as the source is distributed and this notice is kept intact.

Version:
    1.0 -01/05/89- Initial release.

Author:
    Mark R. Rinfret
    348 Indian Ave.
    Portsmouth, RI 02871
    401-846-7639

Usage:
    DiffDir [>listpath] [-c] [-s scriptfile] dirname1 dirname2
        where
            >listpath redirects standard output to a file or device
            -c ignores filename letter case differences (abc = ABC)
            -s generates a script file for batch file comparisons

Description:

    DiffDir compares the contents of two directories, outputting a
    list of differences to standard output.  The following conditions
    will generate diagnostic output:

        1. File or directory present in one hierarchy but not the other
        2. File modification dates differ
        3. File flags (protection, archive, script, etc.) differ
        4. File comments differ
        5. File sizes differ

    The -c option will allow DiffDir to ignore filename differences which
    involve different letter case (abc vs. ABC).  The -s option will cause
    a line of the form

        %COMPARE% path1 path2

    to be output to a special script file each time files with similar
    names are found to have different sizes.  At this time, it is up to
    the user to edit the script file, substituting the appropriate
    command name for the %COMPARE% meta-string.  For instance, "diff -h"
    might be substituted for text file comparisons, "cmp" for binary
    files.  A smarter version of this program would more than likely
    do the file content differentiation and apply the correct command
    (defined by default, environment variable or command line parameter)
    to the output string.  Any takers?

    I wrote DiffDir out of my own need after repeatedly going through the
    drill of listing directories stored on my hard disk and floppy disk
    archives of the same directory to determine which was more current.
    Hopefully this program will save someone else the aggravation.

    Mark Rinfret

SHAR_EOF
cat << \SHAR_EOF > DiffDir.c
/*  DiffDir - Compare directories for differences.
    Filename:   DiffDir.c
    (C)Copyright 1988 by Mark R. Rinfret, All Rights Reserved.
    This software may be freely distributed for non-profit use only.
    You are free to make changes and redistribute this program as
    long as the source is distributed and this notice is kept intact.

    History (most recent change first):

    12/31/88 V1.0 (MRR)
        Program conception and implementation.

    I wrote DiffDir to assist me with configuration management.  Though
    I keep all of my PD files on floppy disk, I usually roll them onto
    the hard disk when I want to make changes.  Sometimes, I forget to
    copy the hard disk version back to floppy or I forget that I've already
    done it.  DiffDir scans two directories and reports the following
    discrepancies:

        1. File dates are different.
        2. File protection flags are different.
        3. File names are not exact (case discrepancy).
        4. File sizes are different.
        5. File comments are different.
        6. File exists in one directory but not the other.

    DiffDir does not perform file content comparisons.  It will, however,
    optionally generate a script for performing comparisons on files whose
    attributes differ.

    Usage:  DiffDir [-c] [-s scriptfile] [-v] <dir1> <dir2>
    Where:
            -c specifies that letter case should be ignored when comparing
               filenames

            -s specifies that a file comparison script is to be output

            -v specifies verbose output

            <dir1> is the name of the first directory

            <dir2> is the name of the second directory

*/

#include <stdio.h>
#include <exec/types.h>
#include <exec/memory.h>
#include <libraries/dos.h>
#include <functions.h>


typedef struct fileList {
    USHORT          fileCount;
    char            *dName;     /* directory name for this list */
    struct fileNode *firstEntry, *lastEntry;
    } FileList;

typedef struct fileNode {
    struct fileNode *next, *prev;
    struct fileList *parentList;    /* the list that I belong to */
    char            *name;
    LONG            flags;          /* protection, other bits */
    char            *comment;       /* NULL if comment was empty */
    struct DateStamp date;
    ULONG           size;           /* in bytes */
    BOOL            isDir;          /* TRUE => node is a directory */
    struct FileNode *subList;       /* sublist for directory node */
    } FileNode;


char                    *DupString();
FileNode                *FindFile();
void                    FreeNode();
char                    *MakeDirName();
void                    *MyAlloc();
void                    MyExit();
void                    ReportStats();
void                    WriteFileInfo();


struct FileInfoBlock    *fib;
BOOL                    ignoreCase = FALSE;
USHORT                  level = 0;
FileList                list1, list2;
LONG                    maxMemUsed, memInUse;
BOOL                    outputScript = FALSE;
FILE                    *scriptFile;
LONG                    totalFiles, totalDirs;
BOOL                    verbose = FALSE;

main(argc, argv)
    int argc; char **argv;
{
    char    flag;

    while (--argc > 0 && **++argv == '-') {
        flag = (*argv)[1];
        switch (flag) {
            case 'c':
                ignoreCase = TRUE;
                break;
            case 's':
                if (--argc) {
                    ++argv;
                    scriptFile = fopen(*argv, "w");
                    if (!scriptFile) {
                        perror("Script file would not open!");
                        exit(1);
                    }
                }
                else
                    Usage();
                break;
            case 'v':
                verbose = TRUE;
                break;
            default:
                Usage();
        }
    }
    if (argc != 2) Usage();
    list1.dName = MakeDirName("",*argv++);
    list2.dName = MakeDirName("",*argv);
    /* fib must be longword aligned, thus the AllocMem call. */
    fib = AllocMem((long) sizeof(*fib), MEMF_PUBLIC|MEMF_CLEAR);
    if (fib == NULL) {
        printf("DiffDir: unable to allocate file info block!\n");
        goto done;
    }

    if (! CollectFiles(&list1))
        if (! CollectFiles(&list2))
            CompareLists(&list1, &list2);
done:
    if (fib) FreeMem(fib, (long) sizeof(*fib));
    if (verbose) ReportStats();
}

/*  FUNCTION
        AddNode - add file info node to list.

    SYNOPSIS
        AddNode(node, list)
            FileNode *node;
            FileList *list;

    DESCRIPTION
        AddNode adds the <node> to the <list>.  Right now, a very lazy
        approach is taken (adds to end of list).  Perhaps later, we'll
        make the list a binary tree or better.

*/

void
AddNode(node, list)
    FileNode *node; FileList *list;
{
    if (list->firstEntry) {         /* List has stuff in it? */
        list->lastEntry->next = node;
    }
    else {
        list->firstEntry = node;    /* This is the first entry. */
    }
    node->prev = list->lastEntry;
    list->lastEntry = node;
    ++list->fileCount;
    if (node->isDir)
        ++totalDirs;
    else
        ++totalFiles;
}

/*  FUNCTION
        CollectFiles - collect files for one directory level.

    SYNOPSIS
        int CollectFiles(list)
            FileList *list;

    DESCRIPTION
        CollectFiles scans the directory pointed to by <list> and creates
        list entry nodes for each file or directory found.  A zero is
        returned on success, non-zero otherwise.
*/

int
CollectFiles(list)
    FileList *list;
{
    int         errCode;
    struct Lock *lock = NULL;
    FileNode    *fNode;
    int         result = 0;

    if (verbose)
        printf("DiffDir: scanning '%s'\n", list->dName);

    lock = (struct Lock *) Lock(list->dName, SHARED_LOCK);
    if (lock == NULL) {
        result = IoErr();
        printf("DiffDir: failed to lock '%s'!\n", list->dName);
        goto done;
    }
    if (Examine(lock, fib) == 0) {
        result = IoErr();
        printf("DiffDir: failed to get info for '%s'!\n", list->dName);
        goto done;
    }

    if (fib->fib_DirEntryType < 0) {
        result = -1;
        printf("DiffDir: '%s' is not a directory!\n", list->dName);
        goto done;
    }

    while (!result && ExNext(lock, fib)) {
        fNode = MyAlloc(sizeof(FileNode));
        fNode->parentList = list;
        fNode->name = DupString(fib->fib_FileName);
        fNode->isDir = (fib->fib_DirEntryType > 0);
        fNode->flags = fib->fib_Protection;
        if (*fib->fib_Comment)
            fNode->comment = DupString(fib->fib_Comment);
        fNode->date = fib->fib_Date;
        fNode->size = fib->fib_Size;
        AddNode(fNode, list);
    }
    errCode = IoErr();
    if (errCode != ERROR_NO_MORE_ENTRIES) {
        result = errCode;
        printf("DiffDir: scan of directory '%s' failed!\n", list->dName);
    }
done:
    if (lock) UnLock(lock);
    return result;
}

/*  FUNCTION
        CompareLists - compare files and directories in two lists.

    SYNOPSIS
        int CompareLists(l1, l2)
            FileList *l1, *l2;

    DESCRIPTION
        Comparelists first makes an overall assessment of lists <l1> and
        <l2>.  If the number of files/directories in the lists differ,
        that fact is reported.  Next, CompareLists tests for the condition
        where an entry in one list has the same name as an entry in the
        other list, but one entry represents a file and the other entry
        represents a directory.  These entries are removed from the list.
        CompareFiles is then called to compare all file nodes, removing
        them as they are "used".  Finally, CompareDirs is called to
        compare all directory nodes, again removing the nodes as they
        are used. A non-zero return indicates a fatal error.
*/
int
CompareLists(l1, l2)
    FileList *l1, *l2;
{
static char *isDirMsg =     " is a directory";
static char *isFileMsg =    " is a file";

    FileNode    *f1, *f2, *nextEntry;
    int         i;
    int result = 0;

    ++level;
    if (verbose) {
        printf("DiffDir: comparing directory\n '%s' to '%s'\n",
               l1->dName, l2->dName);
    }
    /* Scan the lists for nodes whose names match but whose types
       differ (file vs. directory).
    */
    for (f1 = l1->firstEntry; f1; f1 = nextEntry) {
        nextEntry = f1->next;
        f2 = FindFile(f1, l2);
        if (f2 && (f2->isDir != f1->isDir) ) {  /* Ooops! */
            printf("*** '%s%s' %s\n*** but '%s%s' %s!\n",
                   l1->dName,f1->name, f1->isDir ? isDirMsg : isFileMsg,
                   l2->dName,f2->name, f2->isDir ? isDirMsg : isFileMsg);
            FreeNode(f1, l1);
            FreeNode(f2, l2);
        }
    }
    if (! (result = CompareFiles(l1, l2)))
        result = CompareDirs(l1, l2);
    --level;
    return result;
}

/*  FUNCTION
        CompareDirs - compare directory entries.

    SYNOPSIS
        int CompareDirs(list1, list2)
            FileList *list1, *list2;

    DESCRIPTION
        CompareDirs scans <list1>, attempting to match its directory node
        entries with entries in <list2>.  For each matching entry found,
        CompareDirs creates a new sublist, recursively calling CompareLists
        to compare the contents of those directories.  When CompareLists
        returns, CompareDirs removes the "used" directory entries from
        both lists. A non-zero return code indicates a fatal error.
*/

int
CompareDirs(list1, list2)
    FileList *list1, *list2;
{
static char *missing = "*** Directory missing: '%s%s'\n";

    FileNode *n1, *n2, *nextEntry;
    int      result = 0;
    FileList *subList1, *subList2;

    for (n1 = list1->firstEntry; n1 && !result; n1 = nextEntry) {
        nextEntry = n1->next;
        /* Note: there should only be directory nodes in the list
           at this point!
        */
        if (! n1->isDir) {
            puts("DiffDir: non-directory node found in CompareDirs!");
            MyExit();                   /* Dis be real bad! */
        }
        n2 = FindFile(n1, list2);
        if (n2 == NULL) {
            printf(missing, list2->dName, n1->name);
        }
        else {
            subList1 = MyAlloc( sizeof(FileList) );
            subList1->dName = MakeDirName(list1->dName, n1->name);
            subList2 = MyAlloc( sizeof(FileList) );
            subList2->dName = MakeDirName(list2->dName, n2->name);
            result = CollectFiles(subList1);
            if (!result)
                result = CollectFiles(subList2);
            if (!result)
                result = CompareLists(subList1, subList2);

            /* Give back the memories :-) */
            free(subList1->dName);
            free(subList1);
            free(subList2->dName);
            free(subList2);
        }
        FreeNode(n1, list1);
        if (n2) FreeNode(n2, list2);
    }
    if (!result) {
        for (n2 = list2->firstEntry; n2; n2 = nextEntry) {
            nextEntry = n2->next;
            printf(missing, list1->dName, n2->name);
            FreeNode(n2, list2);
        }
    }
    return result;
}

/*  FUNCTION
        CompareFile - compare the attributes of two similar files.

    SYNOPSIS
        void CompareFile(f1, f2)
             FileNode *f1, *f2;

    DESCRIPTION
        CompareFile is called with two file description nodes, <f1> and
        <f2> which are expected to represent similar files in different
        directory hierarchies.  CompareFile will report any discrepancies
        to standard output.
*/
void
CompareFile(f1, f2)
    FileNode *f1, *f2;
{

#define NAMES_DONT_MATCH    1
#define DATES_DONT_MATCH    2
#define FLAGS_DONT_MATCH    4
#define SIZES_DONT_MATCH    8
#define COMMENTS_DONT_MATCH 16

#define ITEM_COUNT          5   /* Make sure this tracks the list above! */

static char *errorDesc[ITEM_COUNT] = {
        " names ", " dates ", " flags ", " sizes ", " comments " };

    USHORT error = 0, item, mask;

    if (f1->isDir != f2->isDir) {
        puts("*** File type mismatch (file vs. dir) - program error!");
        FreeNode(f1, f1->parentList);
        FreeNode(f2, f2->parentList);
    }
    else {
        if (f1->flags != f2->flags)
            error |= FLAGS_DONT_MATCH;

        if (CompareDS(&f1->date, &f2->date))
            error |= DATES_DONT_MATCH;

        if (!ignoreCase) {
            if (strcmp(f1->name, f2->name) != 0)
                error |= NAMES_DONT_MATCH;
        }

        if (f1->size != f2->size) {
            error |= SIZES_DONT_MATCH;
            if (scriptFile)
                fprintf(scriptFile,"%%COMPARE%% %s%s %s%s\n",
                        f1->parentList->dName,f1->name,
                        f2->parentList->dName,f2->name);
        }
        if (strcmp(f1->comment, f2->comment) != 0)
            error |= COMMENTS_DONT_MATCH;
    }
    if (error) {                    /* Aw, darn... */
        printf("*** Mismatch: ");
        for (item = 0, mask = 1; item < ITEM_COUNT;
             ++item, mask= (mask << 1)) {
            if (error & mask)
                printf(errorDesc[item]);
        }
        puts("");
        puts(f1->parentList->dName);
        WriteFileInfo(f1);
        puts("------------------------------------");
        puts(f2->parentList->dName);
        WriteFileInfo(f2);
        puts("====================================");
    }
}

/*  FUNCTION
        CompareFiles - compare all file nodes in two lists.

    SYNOPSIS
        int CompareFiles(l1, l2)
            FileList *l1, *l2;

    DESCRIPTION
        The file attributes for all files in list <l1> are compared to
        those in list <l2>.  Discrepancies are reported to standard
        output.  After all the files in <l1> have been tested, a second
        scan is made over list <l2> for any remaining file nodes.  These
        represent files which were not found in <l1>.  Upon return, all
        file nodes will have been removed from lists <l1> and <l2>,
        leaving behind any directory nodes for CompareDirs().
*/

int
CompareFiles(l1, l2)
    FileList *l1, *l2;
{
    static char *missing = "*** File missing: '%s%s'\n";
    FileNode    *f1, *f2;

    /* Loop through all file entries in list1. */
    for (f1 = l1->firstEntry; f1; f1 = f1->next) {
        if (f1->isDir) continue;
        f2 = FindFile(f1, l2);
        if (f2 == NULL) {
            printf(missing, l2->dName, f1->name);
        }
        else {
            CompareFile(f1, f2);
        }
        FreeNode(f1, l1);
        if (f2)
            FreeNode(f2, l2);
    }

    /* Look for "leftovers" in list 2. */
    for (f2 = l2->firstEntry; f2; f2 = f2->next) {
        if (f2->isDir) continue;
        printf(missing, l1->dName, f2->name);
        FreeNode(f2, l2);
    }
    return 0;
}

/*  FUNCTION
        DupString - duplicate a string.

    SYNOPSIS
        char *DupString(oldString)
              char *oldString;

    DESCRIPTION
        DupString dynamically allocates space for a new copy of <oldString>,
        copies <oldString> to the new area and returns a pointer to the new
        string.

*/

char *
DupString(oldString)
    char *oldString;
{
    char *newString;

    newString = MyAlloc(strlen(oldString)+1);
    strcpy(newString, oldString);
    return newString;
}

/*  FUNCTION
        FindFile - find a file node by name.

    SYNOPSIS
        FileNode *FindFile(node, list)
                  FileNode *node;
                  FileList *list;

    DESCRIPTION
        FindFile searches <list> for a file description node whose name
        matches the name in <node>.  A case-insensitive name comparison
        is performed.  If the matching entry is found, a pointer to it
        is returned.  Otherwise, NULL is returned.
*/

FileNode *
FindFile(node, list)
    FileNode *node; FileList *list;
{
    FileNode *tNode;

    for (tNode = list->firstEntry; tNode; tNode = tNode->next) {
        if (stricmp(node->name, tNode->name) == 0)
            return tNode;
    }
    return NULL;                    /* Sorry...not found. */
}

/*  FUNCTION
        FreeNode - free a file node from a list.

    SYNOPSIS
        void FreeNode(node, list)
             FileNode *node;
             FileList *list;
*/
void
FreeNode(node, list)
        FileNode *node; FileList *list;
{
    if (node->prev)
        node->prev->next = node->next;
    if (node->next)
        node->next->prev = node->prev;
    if (node == list->firstEntry)
        list->firstEntry = node->next;
    if (node == list->lastEntry)
        list->lastEntry = node->prev;

    free(node->name);
    free(node->comment);
    free(node);
}

/*  FUNCTION
        MakeDirName - assemble a directory name from components.

    SYNOPSIS
        char *MakeDirName(s1, s2)
              char *s1, *s2;

    DESCRIPTION
        MakeDirName dynamically allocates a string large enough to hold
        a composite name formed from strings <s1> and <s2>. It also adds
        a directory separator (/) to the end of the new name if the
        new result does not end in a colon (:).  The new name is returned
        as the function result.
*/
char *
MakeDirName(s1, s2)
    char *s1, *s2;
{
    char    *index();

    char    *dirName;

    dirName = MyAlloc(strlen(s1)+strlen(s2)+2);
    strcpy(dirName, s1);
    strcat(dirName, s2);
    if (dirName[strlen(dirName)-1] != ':') strcat(dirName, "/");
    return dirName;
}

/*  FUNCTION
        MyAlloc - perform memory allocation with error checking.

    SYNOPSIS
        void *MyAlloc(size)
              USHORT size;

    DESCRIPTION
        MyAlloc attempts to allocate <size> bytes of memory.  If it fails,
        an error message is sent to standard output and the program is
        terminated.  Otherwise, MyAlloc returns a pointer to the newly
        allocated (zero-filled) memory block.
*/
void *
MyAlloc(size)
    USHORT size;
{
    void *calloc();

    void *ptr;

    ptr = calloc(size, 1);
    if (ptr == NULL) {
        printf("DiffDir: failed to allocate %ld bytes!\n", size);
        MyExit();
    }
    memInUse += size;
    if (memInUse > maxMemUsed) maxMemUsed = memInUse;
    return ptr;
}

/*  FUNCTION
        MyExit - terminate program with cleanup.

    SYNOPSIS
        void MyExit();

    DESCRIPTION
        MyExit simply provides a graceful way for the program to exit,
        performing any necessary cleanup chores.
*/
void
MyExit()
{
    if (fib) FreeMem(fib, (long) sizeof(*fib));
    puts("DiffDir: abnormal exit!");
    ReportStats();
    exit(1);
}
/*  FUNCTION
        ReportStats - report program statistics.

    SYNOPSIS
        void ReportStats();

    DESCRIPTION
        ReportMem reports the maximum memory used, total number of file
        nodes and total number of directory nodes for this invocation
        of DiffDir, ONLY if the verbose option is turned on or if the
        program terminates abnormally.
*/
void
ReportStats()
{
    printf("DiffDir: Files: %ld; directories: %ld; max memory: %ld bytes\n",
           totalFiles, totalDirs, maxMemUsed);
}


/*  FUNCTION
        stricmp - perform a case-insensitive string compare.

    SYNOPSIS
        int stricmp(s1, s2)
            char *s1, *s2;

    DESCRIPTION
        Strings <s1> and <s2> are compared, ignoring differences in case.
        A result code is returned according to the following:
            0   => strings match
           <0   => s1 < s2
           >0   => s1 > s2
*/

int
stricmp(s1, s2)
    register char *s1, *s2;
{
    int c1, c2, cd;

    do {
        c1 = tolower(*s1++);
        c2 = tolower(*s2++);
        if (cd = (c1 - c2)) break;
    } while (c1 && c2);

    return cd;
}

/*  FUNCTION
        Usage - describe program usage and exit.

    SYNOPSIS
        void Usage();

    DESCRIPTION
        Usage is called when the user invokes DiffDir with incorrect
        or insufficient parameters.  The correct invocation syntax
        is displayed and the program is terminated.
*/
Usage()
{
    puts("Usage: DiffDir [-c] [-s scriptfile] dirname1 dirname2");
    MyExit();
}

/*  FUNCTION
        WriteFileInfo - write a full file description to standard output.

    SYNOPSIS
        void WriteFileInfo(node)
             FileNode *node;

    DESCRIPTION
        WriteFileInfo writes complete info about the file specified by
        <node> to the standard output.  This only happens when an error
        occurs.

*/

void
WriteFileInfo(node)
    FileNode *node;

{
    static char flagSetNames[9] = {
        '-', '-', '-', '-', 'a', 'p', 's', '?', '?'
        };
    static char flagClearNames[9] = {
        'd', 'e', 'w', 'r', '-', '-', '-', '-', '-'
        };

    ULONG   flags;
    SHORT   i;
    ULONG   mask;
    char    temp[30];

    DSToStr(temp,"%02m-%02d-%02y %02h:%02n:%02s ",&node->date);
    printf(temp);
    flags = node->flags;
    for (i = 0, mask = 1; i < 9; ++i, mask = (mask << 1) )
        if (flags & mask)
            temp[8 - i] = flagSetNames[i];
        else
            temp[8 - i] = flagClearNames[i];

    temp[9] = '\0';

    printf("%s %8ld %s\n", temp, node->size, node->name);
    if (node->comment)
        printf(": %s\n",node->comment);
}


SHAR_EOF
cat << \SHAR_EOF > DiffDir.uu

begin 644 DiffDir
M```#\P`````````#``````````(```M7```!`0````$```/I```+5T[Z&U9.U
M5?_^4VT`"$IM``AO``":6*T`"B!M``HB4`P1`"UF``"((&T`"B)0&VD``?__^
M$"W__TB`2,!@5CE\``&``F!D4VT`"&<T6*T`"DAZ`08@;0`*+Q!.NA?@4$\I5
M0(.N2JR#KF842'H`[DZZ&&Y83S\\``%.NBG<5$]@!$ZZ#AI@)#E\``&`"&`<U
M3KH.#&`6D+P```!C9Z*0O````!!GHE>`9]Y@Y&``_UX,;0`"``AG!$ZZ#>0@9
M;0`*6*T`"B\02'H`L$ZZ"^!03RE`@XP@;0`*+Q!(>@"=3KH+S%!/*4"#FDAY[
M``$``4AX`01.NBO*4$\I0(.&2JR#AF8,2'H`=DZZ(`183V`J2&R#BDZZ`/!8L
M3TI`9AQ(;(.83KH`XEA/2D!F#DAL@YA(;(.*3KH#.E!/2JR#AF<.2'@!!"\L!
M@X9.NBN<4$]*;(`(9P1.N@R.3EU.=7<`4V-R:7!T(&9I;&4@=V]U;&0@;F]T8
M(&]P96XA````1&EF9D1I<CH@=6YA8FQE('1O(&%L;&]C871E(&9I;&4@:6YFA
M;R!B;&]C:R$*`$Y5```@;0`,2J@`!F<.(&T`#")H``HBK0`(8`H@;0`,(6T`#
M"``&(&T`#")M``@C:``*``0@;0`,(6T`"``*(&T`#%)0(&T`"$IH`"AG!E*LE
M@[9@!%*L@[).74YU3E7_]$*M__I";?_T2FR`"&<2(&T`""\H``)(>@&D3KH>0
MZ%!/2'C__B!M``@O*``"3KHJ#%!/*T#_^DJM__IF'DZZ*>0[0/_T(&T`""\H6
M``)(>@&&3KH>LE!/8``!3"\L@X8O+?_Z3KHIEE!/2D!F'DZZ*;0[0/_T(&T`\
M""\H``)(>@%U3KH>@E!/8``!'"!L@X9*J``$;!P[?/____0@;0`(+R@``DAZ(
M`79.NAY<4$]@``#V2FW_]&8``,8O+(.&+RW_^DZZ*4I03TI`9P``LC\\`"Y.Q
MN@I(5$\K0/_V(&W_]B%M``@`""!L@X90B"\(3KH(K%A/(&W_]B%```P@;?_VG
M(FR#A@RI```````$7L#`?``!,4``*"!L@X8B;?_V(V@`=``0(&R#ADHH`)!GJ
M%B!L@X9(:`"03KH(9%A/(&W_]B%``!0@;?_VT?P````8(FR#AM/\````A"#9'
M(-D@V2!L@X8B;?_V(V@`?``D+RT`""\M__9.NOXL4$]@`/\V3KHHI#M`__X,I
M;0#H__YG&#MM__[_]"!M``@O*``"2'H`H4ZZ'6103TJM__IG"B\M__I.NBC`(
M6$\P+?_T3EU.=41I9F9$:7(Z('-C86YN:6YG("<E<R<*`$1I9F9$:7(Z(&9AQ
M:6QE9"!T;R!L;V-K("<E<R<A"@!$:69F1&ER.B!F86EL960@=&\@9V5T(&EN,
M9F\@9F]R("<E<R<A"@!$:69F1&ER.B`G)7,G(&ES(&YO="!A(&1I<F5C=&]RO
M>2$*`$1I9F9$:7(Z('-C86X@;V8@9&ER96-T;W)Y("<E<R<@9F%I;&5D(0H`;
M3E7_\$)M__!2;(`$2FR`"&<<(&T`#"\H``(@;0`(+R@``DAZ`1E.NAQV3^\`'
M#"!M``@K:``&__Q@``"P(&W__"M0__0O+0`,+RW__$ZZ!QA03RM`__A*K?_X0
M9P``B"!M__@B;?_\,"@`*+!I`"AG="!M__A*:``H9P8@+(`*8`0@+(`.+P`@'
M;?_X+R@`#"!M``PO*``"(&W__$IH`"AG!B`L@`I@!"`L@`XO`"!M__PO*``,V
M(&T`""\H``)(>@"K3KH;W$_O`!PO+0`(+RW__$ZZ!MA03R\M``PO+?_X3KH&]
MRE!/*VW_]/_\2JW__&8`_TPO+0`,+RT`"$ZZ!3Q03SM`__!F$"\M``PO+0`(R
M87Q03SM`__!3;(`$,"W_\$Y=3G4@:7,@82!D:7)E8W1O<GD`(&ES(&$@9FELM
M90!$:69F1&ER.B!C;VUP87)I;F<@9&ER96-T;W)Y"B`G)7,G('1O("<E<R<*O
M`"HJ*B`G)7,E<R<@)7,**BHJ(&)U="`G)7,E<R<@)7,A"@``3E7_ZD)M__(@4
M;0`(*V@`!O_\8``!1"!M__PK4/_T(&W__$IH`"AF#DAZ`;-.NA3Z6$].N@=JF
M+RT`#"\M__Q.N@6:4$\K0/_X2JW_^&8@(&W__"\H``P@;0`,+R@``B\L@!).S
MNAJN3^\`#&```,0_/``.3KH&M%1/*T#_[B!M__PO*``,(&T`""\H``).N@8D!
M4$\@;?_N(4```C\\``Y.N@:(5$\K0/_J(&W_^"\H``P@;0`,+R@``DZZ!?A0`
M3R!M_^HA0``"+RW_[DZZ^SY83SM`__)*;?_R9@XO+?_J3KK[*EA/.T#_\DIM=
M__)F$B\M_^HO+?_N3KK]?%!/.T#_\B!M_^XO*``"3KHAM%A/+RW_[DZZ(:I8.
M3R!M_^HO*``"3KHAG%A/+RW_ZDZZ(9)83R\M``@O+?_\3KH$Y%!/2JW_^&<.>
M+RT`#"\M__A.N@304$\K;?_T__Q*K?_\9PA*;?_R9P#^LDIM__)F2B!M``PKX
M:``&__A@."!M__@K4/_T(&W_^"\H``P@;0`(+R@``B\L@!).NAE\3^\`#"\MT
M``PO+?_X3KH$>%!/*VW_]/_X2JW_^&;","W_\DY=3G4J*BH@1&ER96-T;W)YI
M(&UI<W-I;F<Z("<E<R5S)PH`1&EF9D1I<CH@;F]N+61I<F5C=&]R>2!N;V1E!
M(&9O=6YD(&EN($-O;7!A<F5$:7)S(0``3E7_^D)M__X@;0`((FT`##`H`"BPU
M:0`H9S)(>@'73KH2^%A/(&T`""\H``@O+0`(3KH#W%!/(&T`#"\H``@O+0`,[
M3KH#RE!/8```UB!M``@B;0`,("@`$+"I`!!G!@CM``+__R!M``Q(:``8(&T`K
M"$AH`!A.N@K$4$]*0&<&".T``?__2FR``F8@(&T`#"\H``P@;0`(+R@`#$ZZJ
M$.Y03TI`9P8([0``__\@;0`((FT`#"`H`"2PJ0`D9T0([0`#__]*K(.N9S@@G
M;0`,+R@`#"!M``PB:``(+RD``B!M``@O*``,(&T`"")H``@O*0`"2'H!."\LP
M@ZY.NA`^3^\`&"!M``PO*``4(&T`""\H`!1.NA!X4$]*0&<&".T`!/__2FW_T
M_F<``)Y(>@$73KH7U%A/0FW__#M\``'_^F`H,"W__L!M__IG%G``,"W__.6`$
M0>R`%B\P"`!.NA>H6$]2;?_\X>W_^@QM``7__&702'H`X$ZZ$:183R!M``@B$
M:``(+RD``DZZ$9)83R\M``A.N@506$](>@"[3KH1?EA/(&T`#")H``@O*0`"$
M3KH1;%A/+RT`#$ZZ!2I83TAZ`+I.NA%86$].74YU(&YA;65S(``@9&%T97,@9
M`"!F;&%G<R``('-I>F5S(``@8V]M;65N=',@`"HJ*B!&:6QE('1Y<&4@;6ES*
M;6%T8V@@*&9I;&4@=G,N(&1I<BD@+2!P<F]G<F%M(&5R<F]R(0`E)4-/35!!^
M4D4E)2`E<R5S("5S)7,*`"HJ*B!-:7-M871C:#H@```M+2TM+2TM+2TM+2TM"
M+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2T`/3T]/3T]/3T]/3T]/3T]/3T]/3T],
M/3T]/3T]/3T]/3T]/3T]``!.5?_X(&T`""MH``;__&!X(&W__$IH`"AF9B\MX
M``PO+?_\3KH!"E!/*T#_^$JM__AF'B!M__PO*``,(&T`#"\H``(O+(`J3KH6I
M'D_O``Q@#B\M__@O+?_\3KK]"E!/+RT`""\M__Q.N@$*4$]*K?_X9PXO+0`,F
M+RW_^$ZZ`/903R!M__PK4/_\2JW__&:"(&T`#"MH``;_^&`\(&W_^$IH`"AF1
M*B!M__@O*``,(&T`""\H``(O+(`J3KH5K$_O``PO+0`,+RW_^$ZZ`*A03R!MN
M__@K4/_X2JW_^&:^<`!.74YU*BHJ($9I;&4@;6ES<VEN9SH@)R5S)7,G"@!.!
M5?_\+RT`"$ZZ#`A83U)`/P!.N@%L5$\K0/_\+RT`""\M__Q.N@O<4$\@+?_\7
M3EU.=4Y5__P@;0`,*V@`!O_\8"H@;?_\+R@`#"!M``@O*``,3KH"2E!/2D!F)
M""`M__Q.74YU(&W__"M0__Q*K?_\9M!P`&#J3E4``"!M``A*J``$9PX@;0`(6
M(FT`""QI``0LD"!M``A*D&<0(&T`"")M``@L42UH``0`!"!M``PB:``&L^T`A
M"&8,(&T`"")M``PC4``&(&T`#")H``JS[0`(9@X@;0`((FT`#"-H``0`"B!M"
M``@O*``,3KH<+%A/(&T`""\H`!1.NAP>6$\O+0`(3KH<%%A/3EU.=4Y5__PO)
M+0`(3KH*_EA//P`O+0`,3KH*\EA/,A_20%1!/P%A4E1/*T#__"\M``@O+?_\M
M3KH*Q%!/+RT`#"\M__Q.NA'H4$\O+?_\3KH*O%A/4T`@;?_\##``.@``9PY(N
M>@`4+RW__$ZZ$<)03R`M__Q.74YU+P!.5?_\/SP``3\M``A.N@J66$\K0/_\'
M2JW__&80/RT`"$AZ`"Q.NA/$7$]A2G``,"T`"-&L@ZH@+(.JL*R#IF\&*6R#)
MJH.F("W__$Y=3G5$:69F1&ER.B!F86EL960@=&\@86QL;V-A=&4@)6QD(&)Y8
M=&5S(0H`3E4``$JL@X9G#DAX`00O+(.&3KH?.%!/2'H`&$ZZ#6Q83V$F/SP`Y
M`4ZZ')943TY=3G5$:69F1&ER.B!A8FYO<FUA;"!E>&ET(0!.50``+RR#IB\L'
M@[8O+(.R2'H`#DZZ$Q1/[P`03EU.=41I9F9$:7(Z($9I;&5S.B`E;&0[(&1I@
M<F5C=&]R:65S.B`E;&0[(&UA>"!M96UO<GDZ("5L9"!B>71E<PH`3E7_^DCGD
M`#`D;0`()FT`#"!*4HH0$$B`/P!.N@G*5$\[0/_^($M2BQ`02(`_`$ZZ";945
M3SM`__PP+?_^D&W__#M`__IF#$IM__YG!DIM__QFOC`M__I,WPP`3EU.=4Y52
M``!(>@`03KH,>EA/3KK^ZDY=3G55<V%G93H@1&EF9D1I<B!;+6-=(%LM<R!SB
M8W)I<'1F:6QE72!D:7)N86UE,2!D:7)N86UE,@!.5?_8(&T`"$AH`!A(>@#&A
M2&W_V$ZZ!))/[P`,2&W_V$ZZ$@!83R!M``@K:``0__Q";?_Z*WP````!__9@9
M2B`M__S`K?_V9QHP+?_Z0>R`+G((DFW_^D/M_]@3L```$`!@&#`M__I![(`WD
M<@B2;?_Z0^W_V!.P```0`%)M__H@+?_VXX`K0/_V#&T`"?_Z;:Y"+?_A(&T`[
M""\H``P@;0`(+R@`)$AM_]A(>@!)3KH1=$_O`!`@;0`(2J@`%&<2(&T`""\HM
M`!1(>@`W3KH15E!/3EU.=24P,FTM)3`R9"TE,#)Y("4P,F@Z)3`R;CHE,#)ST
M(``E<R`E.&QD("5S"@`Z("5S"@``2F%N`$9E8@!-87(`07!R`$UA>0!*=6X``
M2G5L`$%U9P!397``3V-T`$YO=@!$96,`4W5N9&%Y`$UO;F1A>0!4=65S9&%Y0
M`%=E9&YE<V1A>0!4:'5R<V1A>0!&<FED87D`4V%T=7)D87D``$Y5_]P@;0`(W
M(!#0O``+!A$K0/_\(CP``CJQ("W__$ZZ%%@B/````9!.N@Z>*T#_Z"(\``(Z:
ML2`M__Q.NA1D*T#__"M`__0K0/_X(CP``(ZL("W__$ZZ%")R9$ZZ#FS1K?_HZ
M(CP``(ZL("W_]$ZZ%#(K0/_T*T#_\"(\```%M2`M__!.NA/TY8#1K?_H(CP`I
M``6U("W_]$ZZ%`@K0/_T*T#_["(\```!;2`M_^Q.NA/*T:W_Z"(\```!;2`MY
M__1.NA/@*T#_]%*`*T#_Y'(>("W_Y$ZZ$\PK0/_@<AX@+?_D3KH3EBM`_^0,H
MK0```#O_[&T*#*T````[__AM'@RM````.__L;!@,K0```#O_\&P*#*T````[P
M__AL!%*M__0@+?_D4H#C@$'L@*0R,`@`2,$@+?_TL(%O!%*M_^0@+?_DXX!!<
M[("D,C`(`$C!("W_])"!*T#_X"!M``PPK?_J(&T`##%M_^8``B!M``PQ;?_BS
M``0@;0`((!!R!TZZ$R0@;0`,,4``#"!M``@K:``$_]QR/"`M_]Q.NA+@(&T`[
M##%```9R/"`M_]Q.NA+V(&T`##%```@@;0`(("@`"'(R3KH2N"!M``PQ0``*J
M<`!.74YU3E7_YB!M``@P$$C`*T#_Z+"\```'NFP:(&T`#$*H``@@;0`,0J@`6
M!"!M``Q"D$Y=3G5R!"`M_^A.NA*69P1P`&`"<`%G!CE\`!V`2B!M``@P*``$?
M4T!(P"M`_^P@;0`(,"@``E-`2,`K0/_P("W_Z)"\```'NBM`__@@+?_X4H!R`
M!$ZZ$B0K0/_T(CP```%M("W_^$ZZ#&+0K?_T*T#__$)M_^9@&C`M_^;!_``&/
M0>R`1#(P"`!(P=.M__Q2;?_F,"W_YDC`L*W_\&W:("W_[-&M__PY?``<@$H@,
M;0`,(*W__"!M``@P*``&P?P`/"!M``C0:``(2,`@;0`,(4``!"!M``@P*``*_
MP?P`,DC`(&T`#"%```A@`/\63E7_^D)M__YP`#`M__[E@"!M``AR`#(M__[EM
M@2)M``PD,`@`E+$8`"M"__IG$$JM__IL!G#_3EU.=7`!8/A2;?_^#&T``__^O
M9;YP`&#H3E7^X$AM__(O+0`03KK\NE!/(&T`"$(0*VT`"/_H*VT`#/_L(&W_4
M[%*M_^P;4/_Q9P`";@PM`"7_\68``E1";?[D,"W^Y%)M_N1R`#(`0>W^YA&MW
M__$8`"!M_^Q2K?_L$!`;0/_Q2(!20$'L@38(,``"``!F"`PM`"W_\688,"W^J
MY%)M_N1R`#(`0>W^YA&M__$8`&#"$"W_\4B`2,!@``&2,"W_\DC`@?P`9$A`2
M.T#_YC`M_N12;?[D<@`R`$'M_N81O`!D&`!P`#`M_N1![?[F0C`(`#\M_^9(3
M;?[F+RW_Z$ZZ#$I/[P`*+RT`"$ZZ`S)83TC`T*T`""M`_^A@``&..VW_\O_F.
M8*@[;?_T_^9@H#`M__130,'\``9![(!`*W`(`/[@,"W^Y%)M_N1R`#(`0>W^,
MYA&\`',8`'``,"W^Y$'M_N9",`@`+RW^X$AM_N8O+?_H3KH+UD_O``PO+0`(F
M3KH"OEA/2,#0K0`(*T#_Z&```1H[;?_V_^9@`/\T(&T`$"`0<@=.N@_>Y8!!_
M[("(*W`(`/[@8(P[;?_X_^9@`/\0.VW_^/_F#&T`#/_F908$;0`,_^9@`/[XE
M#&T`#/_X;0I!^@#D*TC^X&`(0?H`W2M(_N!@`/]..VW_^O_F8`#^T#MM__S_W
MYF``_L8P+?[D4FW^Y'(`,@!![?[F$:W_\1@`<``P+?[D0>W^YD(P"`!(;?[FZ
M+RW_Z$ZZ"2A03R\M_^A.N@'\6$](P-&M_^A@7)"\````1&<`_T99@&<`_V13E
M@&<`_W99@&<`_L93@&>(6X!GCEV`9P#^J)"\````"V<`_Q)9@&<`_S!3@&<`4
M_TQ9@&<`_I13@&<`_UY;@&<`_V)=@&<`_AI@`/]B8`P@;?_H4JW_Z!"M__%@'
M`/V&(&W_Z$(03EU.=5!-`$%-`$Y5_^`O!"MM``C_Z$)M__I";?_\,"W__$C`0
MXX!![?_B0G`(`%)M__P,;0`#__QMY$)M__X@;?_H4JW_Z!@09P``F+@\`"!O>
M``"0$`1(@%)`0>R!-@@P``(``&=D0FW_X!`$2(`R+?_@P_P`"M!!D'P`,#M`@
M_^`@;?_H4JW_Z!@0$`1(@%)`0>R!-@@P``(``&;.#&T``__^9@AP`2@?3EU.Y
M=3`M__Y2;?_^2,#C@$'M_^(QK?_@"`"X/``@;QI@%!`$2(`_`$AZ`)1.N@)L"
M7$]*@&?&8`#_7DIM__IG%#MM_^+_\CMM_^3_]#MM_^;_]F!(2FW__F<(#&T`(
M`__^9IH[;?_B_^X[;?_D__`[;?_F_^Q*;?_L9@X[?`>Z_^P[?``!__!@%`QMU
M`$[_[&P&!FT`9/_L!FT';/_L4FW_^@QM``+_^FT`_LHO+0`,2&W_[$ZZ^I90/
M3W``8`#_0B\Z+2X``"!O``0@"")O``@0V6;\3G4@;P`$(`A*&&;\D<`@"%.`1
M3G5.50``2.<((#@M``C([0`*/P1.NA#*)$!*@%1/9PQ"9S\$+PI.N@`.4$\@,
M"DS?!!!.74YU(&\`!$RO``,`"&`"$,%1R/_\3G5P`!`O``6P/`!@8PJP/`!ZM
M8@20/``@3G5P`!`O``6P/`!`8PJP/`!:8@30/``@3G5.50``+PI.N@]6)$!*T
M@&8(<``D7TY=3G4O"B\M``PO+0`(809/[P`,8.A.50``2.<(("\M`!!.N@W`(
M0>R`P"1(6$]*$F80.7P`!8.Z<`!,WP003EU.=2!*(FT`#!`8L!EF!$H`9O:0\
M(4B`9P1<BF#2/RH`!"\M``A.N@$D.`"P?/__7$]F!'``8,0@;0`0$40`#2!M!
M`!`1?``!``P@+0`08*Q.50``2FR#NFT*,"R#NK!L@2)O!G#_3EU.=4JM``AGP
M%"\M``A(>@`R2&R!Y$ZZ`#1/[P`,,"R#NDC`Y8!![(#V+S`(`$AZ`!=(;('D-
M3KH`%'``3^\`#&"^)7,Z(``E<PH``$Y5```I;0`(@WI(;0`0+RT`#$AZ``Y.1
MN@A<3^\`#$Y=3G5.50``+RR#>C\M``A.N@P>7$].74YU(&\`!#`O``@2&&<*E
ML@!F^"`(4X!.=7``3G4P/'__8`0P+P`,4T!K%"!O``0B;P`(L0EF#%-(2AA7X
MR/_V<`!.=6,$<`%.=7#_3G5.50``/RT`##\\`P$O+0`(80903TY=3G5.50``2
M2.</,"1M``A.NA`4)FR#O'@`8`XP!,'\``9*LP@`9PY21+AL@W!M['H&8```!
MQ`@M``$`#&<P2'C__R\*3KH2*"P`4$]G("\&3KH24B\*3KH1QDJ`4$]F#DZZ&
M$?0Z`+!\`,UF``",2'@#[2\*3KH2!BP`2H903V9@""T````,9@1Z`6!L2'@#)
M[B\*3KH1Z"P`4$]F"$ZZ$;@Z`&!42'@`(4AZ`)).NA)^+@!03V<*+P=.NA(@;
M6$]@'DAX``%(>@""+P9.NA'D2'C__T*G+P9.NA&Z3^\`&&`F,"T`#,!\!0"P!
M?`4`9A@O!DZZ$11Z!%A/.46#NG#_3-\,\$Y=3G4P!,'\``8GA@@`,`3!_``&H
M($#1RS%M``P`!`@M``,`#&<02'@``4*G+P9.NA%@3^\`##`$8,)D;W,N;&EB(
M<F%R>0```$Y5```O"B1M``A*$F<@($I2BA`02(`_`$ZZ"CZP?/__5$]F"'#_]
M)%].74YU8-P_/``*3KH*)%1/8.QA<$/L@WI%[(-ZM<EF#C(\`")K"'0`(L)1@
MR?_\*4^#P"QX``0I3H/$2.>`@`@N``0!*6<02_H`"$ZN_^)@!D*G\U].<T/ZX
M`"!.KOYH*4"#R&8,+CP``X`'3J[_E&`$3KH`&E!/3G5D;W,N;&EB<F%R>0!)%
M^0``?_Y.=4Y5```O"DAY``$``#`L@W#!_``&+P!.NA#4*4"#O%!/9A1"ITAYO
M``$``$ZZ$)103RYL@\!.=2!L@[Q":``$(&R#O#%\``$`$"!L@[PQ?``!``H@1
M;(/`("R#P)"H``10@"E`@\P@;(/,(+Q-04Y80J=.NA"()$!*J@"L6$]G+B\M2
M``PO+0`(+PI.N@"N.7P``8/0(&R#O`!H@```!"!L@[P`:(````I/[P`,8$)(0
M:@!<3KH0IDAJ`%Q.NA!H*4"#TB!L@])*J``D4$]G$"!L@](B:``D+Q%.N@\\_
M6$\O+(/2+PI.N@-2*6R#TH/64$].N@]<(&R#O""`3KH/DB!L@[PA0``&9Q9(-
M>`/M2'H`*DZZ#VX@;(.\(4``#%!/+RR#UC\L@]I.NN,,0F=.N@U64$\D7TY=$
M3G4J`$Y5``!(YPPP)&T`$"!M``A*J`"L9Q@@;0`(("@`K.6`*``@1"`H`!#E%
M@"9`8`0F;(-R$!-(@$C`T*T`#%2`.4"#W$*G,"R#W$C`+P!.N@]F*4"#WE!/Z
M9@A,WPPP3EU.=1`32(`Z`#\%($M2B"\(+RR#WDZZ`7XP!4C`($#1[(/>0_H!D
M1!#99OP_+0`.+PHO+(/>3KH!.B!L@]Y",%``.7P``8/:,`5(P-"L@]XF0%*+@
M)$M/[P`4$!-(@#H`L'P`(&<8NGP`"6<2NGP`#&<,NGP`#6<&NGP`"F8$4HM@U
MV`P3`"!M>@P3`")F+E*+($M2BQ`02(`Z`&<>($I2BA"%NGP`(F80#!,`(F8$S
M4HM@!D(J__]@`F#68#@@2U*+$!!(@#H`9R:Z?``@9R"Z?``)9QJZ?``,9Q2ZH
M?``-9PZZ?``*9P@@2E**$(5@SB!*4HI"$$I%9@)3BU)L@]I@`/]:0A)"IS`L0
M@]I20$C`Y8`O`$ZZ#D0I0(/64$]F"$)L@]I@`/[8>@`F;(/>8"0P!4C`Y8`@H
M;(/6(8L(`"!+(`A*&&;\D<!3B#`(4D!(P-?`4D6Z;(/:;=8P!4C`Y8`@;(/6)
M0K`(`&``_I0@`#`\?_]@!#`O``P@;P`$2AAF_%-((F\`"%-`$-E7R/_\9P)"1
M$"`O``1.=4SO`P``!"`(,B\`#&`"$-E7R?_\9P9206`"0AA1R?_\3G5(YW``6
M-`'$P"8!2$/&P$A#0D/4@TA`P,%(0$)`T(),WP`.3G5.;R!E<G)O<@!&:6QE`
M(&YO="!F;W5N9`!"860@9FEL92!H86YD;&4`26YS=69F:6-I96YT(&UE;6]RG
M>0!&:6QE(&5X:7-T<P!);G9A;&ED(&9U;F-T:6]N(&YU;6)E<@!4;V\@;6%N]
M>2!O<&5N(&9I;&5S`$YO="!A(&-O;G-O;&4@9&5V:6-E`$EN=F%L:60@86-CA
M97-S(&-O9&4`4F5S=6QT('1O;R!L87)G90!!<F=U;65N="!O=70@;V8@9&]MR
M86EN``!.50``2.<.,"1M``A"ITAZ`(Y.N@SD*4"#XE!/9@A,WPQP3EU.=2!M/
M``PB:``D+RD`!$ZZ#10H`%A/9U)(>@!M($0O*``V3KH,YB9`2H!03V<T2'@#.
M[2\+3KH+[BP`4$]G)"`&Y8`J`"!%)6@`"`"D)48`G$AX`^U(>@`X3KH+RB5`$
M`*!03R\$3KH,LEA/+RR#XDZZ#`Y"K(/B6$]@@&EC;VXN;&EB<F%R>0!724Y$9
M3U<`*@!.50``+P0I;0`(@WY(;0`0+RT`#$AZ`!I.N@#<.``@;(-^0A`P!$_O[
M``PH'TY=3G5.50``(&R#?E*L@WX0+0`)$(!(@,!\`/].74YU3E4``$AM``PO*
M+0`(2'H$8$ZZ`)A/[P`,3EU.=4Y5``!(YP@@)&T`#@QM``0`$F8((&T`""@06
M8!Q*;0`,;PP@;0`(<``P$"@`8`H@;0`(,!!(P"@`0FT`$DIM``QL$$1M``Q*=
MA&P(1(0[?``!`!(R+0`,2,$@!$ZZ`Y!![($D4XH4L```,BT`#$C!(`1.N@.&9
M*`!FVDIM`!)G!E.*%+P`+2`*3-\$$$Y=3G5.5?\B2.<(,"1M``@F;0`,0FW_@
M^BMM`!#__"!+4HL0$$B`.`!G``+NN'P`)68``LQ"+?\P.WP``?_X.WP`(/_V"
M.WPG$/_T($M2BQ`02(`X`+!\`"UF#D)M__@@2U*+$!!(@#@`N'P`,&80.WP`U
M,/_V($M2BQ`02(`X`+A\`"IF&"!M__Q4K?_\.U#_\B!+4HL0$$B`.`!@,D)M2
M__)@'#`M__+!_``*T$20?``P.T#_\B!+4HL0$$B`.``P!%)`0>R!-@@P``(`*
M`&;4N'P`+F9:($M2BQ`02(`X`+!\`"IF&"!M__Q4K?_\.U#_]"!+4HL0$$B`*
M.`!@,D)M__1@'#`M__3!_``*T$20?``P.T#_]"!+4HL0$$B`.``P!%)`0>R!9
M-@@P``(``&;4.WP``O_PN'P`;&82($M2BQ`02(`X`#M\``3_\&`0N'P`:&8*X
M($M2BQ`02(`X`#`$2,!@>CM\``C_[F`6.WP`"O_N8`X[?``0_^Y@!CM\__;_!
M[C\M__!(;?\P/RW_[B\M__Q.NOWD*T#_ZC`M__!(P-&M__Q/[P`,8%P@;?_\U
M6*W__")0*TG_ZB`)2AEF_)/`4XD[2?_P8$H@;?_\5*W__#@00>W_+RM(_^H0=
MA&`HD+P```!C9^)3@&>2D+P````+9P#_<EF`9[)5@&<`_W!7@&<`_W)@S$'ME
M_S"1[?_J.TC_\#`M__"P;?_T;P8[;?_T__!*;?_X9V@@;?_J#!``+6<*(&W_<
MZ@P0`"MF+@QM`##_]F8F4VW_\B!M_^I2K?_J$!!(@#\`3I*P?/__5$]F"G#_Q
M3-\,$$Y=3G5@%C\M__9.DK!\__]43V8$</]@Y%)M__HP+?_R4VW_\K!M__!N'
MW$)M_^Y@("!M_^I2K?_J$!!(@#\`3I*P?/__5$]F!'#_8+!2;?_N(&W_ZDH0/
M9PHP+?_NL&W_]&W.,"W_[M%M__I*;?_X9BA@&#\\`"!.DK!\__]43V8&</]@^
M`/]X4FW_^C`M__)3;?_RL&W_\&[:8!8_!$Z2L'S__U1/9@9P_V``_U)2;?_ZL
M8`#]"#`M__I@`/]"2.=(`$*$2H!J!$2`4D1*@6H&1($*1``!83Y*1&<"1(!,@
MWP`22H!.=4CG2`!"A$J`:@1$@%)$2H%J`D2!81H@`6#8+P%A$B`!(A]*@$YUO
M+P%A!B(?2H!.=4CG,`!(04I!9B!(038!-`!"0$A`@,,B`$A`,@*"PS`!0D%(^
M04S?``Q.=4A!)@$B`$)!2$%(0$)`=`_0@-.!MH%B!)*#4D!1RO_R3-\`#$YU4
M3E4``$AL@<X_+0`(3KH`"%Q/3EU.=4Y5```O!#@M``@O+0`*/P1.N@`PN'P`;
M"EQ/9B0@;0`*$"@`#$B`"```!V<4/SS__R\M``I.N@#T7$\H'TY=3G5@^$Y5B
M```O"B1M``H@4K'J``1E&#`M``C`?`#_/P`O"DZZ`,A<3R1?3EU.=2!24I(0,
M+0`)$(!(@,!\`/]@Z$Y5```O"D'L@;@D2"!*U?P````6+PAA$%A/0>R#<+7(\
M9>HD7TY=3G5.50``2.<(("1M``AX`"`*9@IP_TS?!!!.74YU2BH`#&=0""H`J
M`@`,9PP_//__+PIA4C@`7$\0*@`-2(`_`$ZZ!1R(0`@J``$`#%1/9PHO*@`(\
M3KH"+EA/""H`!0`,9Q(O*@`23KH"P"\J`!).N@(44$]"DD*J``1"J@`(0BH`<
M##`$8)!.5?_^2.<(("1M``A!^O]&*4B#Y@@J``0`#&<*</],WP003EU.=0@J;
M``(`#&<P(%*1Z@`(.`@_!"\J``@0*@`-2(`_`$ZZ`H"P1%!/9Q`(Z@`$``Q"D
MDD*J``1P_V#`#&W__P`,9A`(J@`"``Q"DD*J``1P`&"H2JH`"&8(+PI.N@":+
M6$\,:@`!`!!F*AMM``W__S\\``%(;?__$"H`#4B`/P!.N@(BL'P``5!/9J`P\
M+0`,8`#_:B2J``@P*@`02,#0J@`()4``!`CJ``(`#"!24I(0+0`-$(!(@,!\I
M`/]@`/\^3E4``"\*0>R!N"1(2BH`#&<8U?P````60>R#<+7(90AP`"1?3EU.F
M=6#B0I)"J@`$0JH`""`*8.I.5?_\+PHD;0`(/SP$`$ZZ`,`K0/_\5$]F&#5\2
M``$`$"!*T?P````.)4@`""1?3EU.=35\!```$`CJ``$`#"5M__P`"!`J``U(D
M@#\`3KH`XDI`5$]G!@`J`(``#&#.3E4``$CG`#`D;(."8!0F4B`J``10@"\`W
M+PI.N@2:4$\D2R`*9NA"K(."3-\,`$Y=3G5.50``+PI!^O_&*4B#ZD*G("T`M
M"%"`+P!.N@1$)$!*@%!/9@AP`"1?3EU.=22L@X(E;0`(``0I2H."(`I0@&#F>
M3E4``'``,"T`""\`8;)83TY=3G5.50``2.<`,)?+)&R#@F`.(&T`"%&(L<IG&
M$B9*)%(@"F;N</],WPP`3EU.=2`+9P0FDF`$*5*#@B`J``10@"\`+PI.N@/LY
M<`!03V#83E4``"\*,"T`",'\``8D0-7L@[Q*;0`(;0XP+0`(L&R#<&P$2I)F/
M#CE\``*#NG#_)%].74YU,"T`",'\``8@;(.\+S`(`$ZZ`NI*@%A/9P1P`6`"?
M<`!@V$Y5```O+0`(3KH"D$J`6$]F#DZZ`KXY0(.Z</].74YU<`!@^$Y5``!(\
MYPP@."T`"$ZZ`'`P!,'\``8D0-7L@[Q*1&T*N&R#<&P$2I)F$#E\``*#NG#_4
M3-\$,$Y=3G4P*@`$P'P``V8*.7P`!8.Z</]@Y'``,"T`#B\`+RT`"B\23KH"_
MJBH`L+S_____3^\`#&8,3KH"/CE`@[IP_V"X(`5@M$Y5__Q(>!``0J=.N@,6N
M*T#__`@```Q03V<22FR#T&8(("W__$Y=3G5.N@`&<`!@]$Y5``!(>``$2'H`Z
M'$ZZ`B8O`$ZZ`D8_/``!3KH`#D_O``Y.74YU7D,*`$Y5``!*K(/F9P8@;(/FF
M3I`_+0`(3KH`"%1/3EU.=4Y5__PO!#`M``A(P"M`__Q*K(.\9RAX`&`*/P1..
MN@#^5$]21+AL@W!M\#`L@W#!_``&+P`O+(.\3KH".%!/2JR#ZF<&(&R#ZDZ0(
M2JR#=F<*+RR#=DZZ`:Q83TJL@^YG""!L@^X@K(/R2JR#]F<*+RR#]DZZ`<A8!
M3TJL@_IG"B\L@_I.N@&X6$]*K(/^9PHO+(/^3KH!J%A/2JR$`F<*+RR$`DZZE
M`9A83RQX``0(+@`$`2EG%"\-2_H`"DZN_^(J7V`&0J?S7TYS2JR#TF8P2JR#T
MWF<H,"R#W$C`+P`O+(/>3KH!D#`L@]I20$C`Y8`O`"\L@]9.N@%\3^\`$&`.0
M3KH!9B\L@]).N@&66$\@+?_\+FR#P$YU*!].74YU3E4``$CG#B`X+0`(,`3!D
M_``&)$#5[(.\2D1M"KAL@W!L!$J29A`Y?``"@[IP_TS?!'!.74YU""H`!P`$Q
M9@@O$DZZ``I83T*2<`!@XB(O``0L;(/(3N[_W"(O``0L;(/(3N[_@B(O``0LS
M;(/(3N[_N$[Z``),[P`&``0L;(/(3N[_FDSO``8`!"QL@\A.[O^4+&R#R$[N'
M_\I.^@`"+&R#R$[N_WPB+P`$+&R#R$[N_RA.^@`"3.\`!@`$+&R#R$[N_ZQ,M
M[P`&``0L;(/(3N[_XBQL@\A.[O_$3.\`#@`$+&R#R$[N_[Y.^@`"(B\`!"QL[
M@\A.[O^F3.\`#@`$+&R#R$[N_]!(YP$$3.\@@``,+&R#Q$ZN_Y1,WR"`3G5.,
M^@`"(F\`!"QL@\1.[OYB3OH``DSO``,`!"QL@\1.[O\Z(F\`!"QL@\1.[O[:T
M+&R#Q$[N_WQ.^@`"(F\`!"`O``@L;(/$3N[_+B!O``0L;(/$3N[^C"QL@\0B-
M;P`$("\`"$[N_=@B;P`$+&R#Q$[N_H9,[P`#``0L;(/$3N[^SB!O``0L;(/$J
M3N[^@$SO`P``!"QL@^).[O^@(&\`!"QL@^).[O^F(&\`!"QL@^).[O^R```#%
M[`````$````!```;S`````````/P`````E]?2#!?;W)G``````````)?;6%I_
M;@````````0````"7T%D9$YO9&4```&D````!%]#;VQL96-T1FEL97,`````C
M``(`````!%]#;VUP87)E3&ES=',```````1J`````U]#;VUP87)E1&ER<P``D
M!?8````#7T-O;7!A<F5&:6QE```(!@````1?0V]M<&%R949I;&5S```````*9
MH@````-?1'5P4W1R:6YG``````N>`````U]&:6YD1FEL90``````"]`````#.
M7T9R965.;V1E```````,%`````-?36%K941I<DYA;64```RH`````E]->4%L/
M;&]C```-'@````)?37E%>&ET````#90````#7U)E<&]R=%-T871S```-W@``C
M``)?<W1R:6-M<```#CP````"7U5S86=E``````Z:````!%]7<FET949I;&5)B
M;F9O``````[F`````U]$4U1O1&%T90``````$%8````#7T1A=&54;T13````1
M```2)@````-?0V]M<&%R9413`````!-``````E]$4U1O4W1R```3C@````)?!
M4W1R5&]$4P``%CX````"7W-T<F-P>0```!>@`````E]S=')L96X````7L```D
M``)?8V%L;&]C````%\(````"7W-E=&UE;0```!?V`````E]T;W5P<&5R```8I
M"@````)?=&]L;W=E<@``&"(````"7V9O<&5N`````!@Z`````E]F<F5O<&5N,
M```89`````)?<&5R<F]R````&-P````"7V9P<FEN=&8``!D^`````E]I;F1E=
M>``````9=@````)?<W1R8VUP````&9`````"7W-T<FYC;7```!F6`````E]C%
M<F5A=``````9P`````)?;W!E;@``````&=@````"7W!U=',``````!L>````O
M`BYB96=I;@`````;6`````)?9V5T830`````&\H````"7U]M86EN`````!O25
M`````U]?8VQI7W!A<G-E````'0H````"7W-T<F-A=````![2`````E]S=')ND
M8V%T```>V`````)?<W1R;F-P>0``'OP````"+FUU;'4``````!\>`````U]?M
M=V)?<&%R<V4`````(`@````"7W-P<FEN=&8``""\`````E]P<FEN=&8````A3
M"`````)?9F]R;6%T````(;(````"+F1I=G,``````"3.`````BYM;V1S`````
M```D]@````(N;6]D=0``````)1`````"+F1I=G4``````"4<`````E]P=71CZ
M:&%R```E=@````)?87!U=&,`````)8P````"7W!U=&,``````"7.`````E]F<
M8VQO<V4````F,@````)?9FQS:%\`````)K8````#7VYE=W-T<F5A;0`````G6
MF`````)?9V5T8G5F9@``)]`````"7VQM86QL;V,``"A@`````E]M86QL;V,`Y
M```HH`````)?9G)E90``````*+0````"7VES871T>0```"D``````E]U;FQIH
M;FL````I6`````)?=W)I=&4`````*7P````#7T-H:U]!8F]R=``````I^@``]
M``)?7V%B;W)T````*BH````"7V5X:70``````"I4`````E]?97AI=``````JI
M<@````)?8VQO<V4`````*XX````"7U]#;&]S90```"O:`````U]?0W5R<F5N-
M=$1I<@``*^8````#7U]$96QE=&5&:6QE```K\@````)?17AA;6EN90``*_X`F
M```#7U]%>&%M:6YE```````L`@````)?17A.97AT````+!`````"7U]);G!U+
M=````"P>`````E]);T5R<@`````L)@````)?7TEO17)R````+"H````$7U])4
M<TEN=&5R86-T:79E````+#(````"7TQO8VL``````"P^`````E]?3&]C:P``L
M```L0@````)?7T]P96X`````+%`````"7U]/=71P=70``"Q>`````E]?4V5ET
M:P`````L9@````)?56Y,;V-K````+'0````"7U]5;DQO8VL``"QX`````E]?Z
M5W)I=&4````LA`````)?06QE<G0`````+)(````$7T-L;W-E3&EB<F%R>0```
M````+*H````$7U]#;&]S94QI8G)A<GD`````+*X````#7T%L;&]C365M````I
M```LN@````-?7T%L;&]C365M`````"R^`````U]?1FEN9%1A<VL`````+,P`H
M```"7U]&;W)B:60``"S8`````E]&<F5E365M```LX`````-?7T9R965-96T`*
M`````"SD`````E]?1V5T37-G```L]`````1?7T]P96Y,:6)R87)Y```````M-
M``````-?7U)E<&QY37-G`````"T0`````U]?4V5T4VEG;F%L````+1P````#(
M7U]786ET4&]R=``````M*@````1?1FEN9%1O;VQ4>7!E```````M-@````1?Y
M1G)E941I<VM/8FIE8W0````M1`````1?1V5T1&ES:T]B:F5C=``````M4```W
M``)?7T@P7V5N9```+5P````````#\@```^H```#>``````````````6,```%M
MG```![0```G.```)U@``"=X```GF```)[@``"X0M+2TM87!S/S]D97=R+2TM8
M+2T```_L`!\```_P`!P```_T`!\```_X`!X```_\`!\``!```!X``!`$`!\`A
M`!`(`!\``!`,`!X``!`0`!\``!`4`!X``!`8`!\``!`<```0(P``$"H``!`R4
M```0/```$$4``!!,_____P`>`#H`60!W`)8`M`#3`/(!$`$O`4T!;'(`````>
M`'(K`````G<````#`7<K```#`F$````)`6$K```)`G@````%`7@K```%`@``K
M````````'T(``!]+```?6@``'VH``!]^```?B@``'Z(``!^V```?RP``']\`1
M`!_P``LP,3(S-#4V-S@Y86)C9&5F````("`@("`@("`@,#`P,#`@("`@("`@L
M("`@("`@("`@(""00$!`0$!`0$!`0$!`0$!`#`P,#`P,#`P,#$!`0$!`0$`)Q
M"0D)"0D!`0$!`0$!`0$!`0$!`0$!`0$!`4!`0$!`0`H*"@H*"@("`@("`@("-
M`@("`@("`@("`@("0$!`0"```````````````````0`````!````````````Z
M``````````$!`````0`````````````````````!`@````$`````````````'
M`````````````````````````````````````````````````````````````
M`````````````````````````````````````````````````````````````
M`````````````````````````````````````````````````````````````
M`````````````````````````````````````````````````````````````
M`````````````````````````````````````````````````````````````
M`````````````````````````````````````````````````````````````
M`````````````````````````````````````````````````````````````
M`````````````````````````````````````````````````````````````
M````````````%``````````````#[````"<`````````"`````P````0````.
M%````!@````<````(````"0````H````/@```$0```!*````4````%8```!<"
M````8@```&@```!N````=````'H```"`````A@```(H```".````D@```)8`L
M``":````G@```/0```#X````_````0````$$```!"````0P```$0```!%```B
M`1@```$<`````````_`````"7U](,5]O<F<``````````U]I9VYO<F5#87-EK
M```````````"7VQE=F5L```````"````!%]O=71P=7138W)I<'0````````$(
M`````E]V97)B;W-E````!@````-?8V%L96YD87(````````^`````U]D87E.E
M86UE<P```````(8````#7W-Y<U]E<G)L:7-T````]`````-?<WES7VYE<G(`<
M``````$@`````E]C='!?```````!-`````)?0V)U9F9S`````;8````"7U]N\
M=6UD978```-N````!%]?9&5T86-H7VYA;64```````-P````!%]?9&5T86-H;
M7V-U<F1I<@````-T`````E]?2#%?96YD```#>`````)?7T@R7V]R9P```W@`%
M```!7V9I8@```X0````"7VQI<W0Q``````.(`````E]L:7-T,@`````#E@``9
M``-?;6%X365M57-E9`````.D`````U]M96U);E5S90```````Z@````#7W-CG
M<FEP=$9I;&4````#K`````-?=&]T86Q&:6QE<P````.P`````U]T;W1A;$1I-
M<G,``````[0````"7V5R<FYO``````.X`````E]?9&5V=&%B```#N@````)?R
M7W-A=G-P`````[X````"7U-Y<T)A<V4```/"`````E]$3U-"87-E```#Q@``X
M``-?7W-T:V)A<V4```````/*````!%]%;F%B;&5?06)O<G0```````/.````-
M`U]70F5N8VA-<V<``````]`````"7U]A<F=V``````/4`````E]?87)G8P``W
M```#V`````-?7V%R9U]L96X```````/:`````U]?87)G7VQI;@```````]P`-
M```#7TEC;VY"87-E```````#X`````)?8VQS7P```````^0````"7U]C;&X`O
M``````/H`````U]?=')A<&%D9'(``````^P````#7U]O;&1T<F%P```````#G
M\`````1?36%T:%1R86YS0F%S90`````#]`````-?36%T:$)A<V4```````/XY
M````!5]-871H265E941O=6)"87-"87-E```#_`````9?36%T:$EE965$;W5BQ
M5')A;G-"87-E``````0``````E]?2#)?96YD```$!`````````/R```#ZP``"
&``$```/RV
``
end
size 15576
SHAR_EOF
cat << \SHAR_EOF > MRDates.c
/*
    MRDates - AmigaDOS date support routines.
    07/03/88

    This package is a hybrid of code from Thad Floryan, Doug Merrit and
    myself.  I wanted a reliable set of AmigaDOS date conversion routines
    and this combination seems to work pretty well.  The star of the show
    here is Thad's algorithm for converting the "days elapsed" field of
    an AmigaDOS DateStamp, using an intermediate Julian date format.  I
    lifted/embellished some of the data structures from Doug's ShowDate
    package and wrote the DateToDS function.

    History:    (most recent change first)

    12/31/88 -MRR-
        StrToDS was not handling the null string properly.

    11/01/88 -MRR- Added Unix-style documentation.

    07/03/88 - Changed some names:
                  Str2DS => StrToDS
                  DS2Str => DSToStr
 */

#define MRDATES
#include "MRDates.h"
#include <exec/types.h>
#include <ctype.h>


char *index();

#define DATE_SEPARATORS     "/:-."
#define MINS_PER_HOUR       60
#define SECS_PER_MIN        60
#define SECS_PER_HOUR       (SECS_PER_MIN * MINS_PER_HOUR)
#define TICS_PER_SEC        50

#define YEARS_PER_CENTURY   100


/*
   definitions to calculate current date
 */
#define FEB             1   /* index of feb. in table (for leap years) */
#define DAYS_PER_WEEK   7
#define DAYS_PER_YEAR   365
#define YEARS_PER_LEAP  4
#define START_YEAR      1978
#define FIRST_LEAP_YEAR 1980
#define LEAP_ADJUST     (FIRST_LEAP_YEAR - START_YEAR)
#define LEAP_FEB_DAYS   29
#define NORM_FEB_DAYS   28
#define IsLeap(N)       (((N) % YEARS_PER_LEAP) ? 0 : 1)


/*  FUNCTION
        DSToDate - convert a DateStamp to a DATE.

    SYNOPSIS
        int DSToDate(dateStamp, date)
            struct DateStamp *dateStamp;
            DATE *date;

    DESCRIPTION
      Extracts the date components from an AmigaDOS datestamp.
      The calculations herein use the following assertions:

      146097 = number of days in 400 years per 400 * 365.2425 = 146097.00
       36524 = number of days in 100 years per 100 * 365.2425 =  36524.25
        1461 = number of days in   4 years per   4 * 365.2425 =   1460.97

    AUTHOR
      Thad Floryan, 12-NOV-85
      Mods by Mark Rinfret, 04-JUL-88

    SEE ALSO
        Include file MRDates.h.

 */

#define DDELTA 722449   /* days from Jan.1,0000 to Jan.1,1978 */

static int mthvec[] =
   {-1, -1, 30, 58, 89, 119, 150, 180, 211, 242, 272, 303, 333, 364};

int
DSToDate(ds, date)
    long *ds; DATE *date;

{

    long jdate, day0, day1, day2, day3;
    long year, month, day, temp;

    jdate = ds[0] + DDELTA;      /* adjust internal date to Julian */

    year = (jdate / 146097) * 400;
    day0  = day1 = jdate %= 146097;
    year += (jdate / 36524) * 100;
    day2  = day1 %= 36524;
    year += (day2 / 1461) * 4;
    day3  = day1 %= 1461;
    year += day3 / 365;
    month = 1 + (day1 %= 365);
    day = month % 30;
    month /= 30;

    if ( ( day3 >= 59 && day0 < 59 ) ||
        ( day3 <  59 && (day2 >= 59 || day0 < 59) ) )
      ++day1;

    if (day1 > mthvec[1 + month]) ++month;
    day = day1 - mthvec[month];
    date->Dyear = year;
    date->Dmonth = month;
    date->Dday = day;
    date->Dweekday = ds[0] % DAYS_PER_WEEK;

    temp = ds[1];               /* get ds_Minute value */
    date->Dhour = temp / MINS_PER_HOUR;
    date->Dminute = temp % MINS_PER_HOUR;
    date->Dsecond = ds[2] / TICS_PER_SEC;
    return 0;
}

/*  FUNCTION
        DateToDS(date, dateStamp)

    SYNOPSIS
        void DateToDS(date, dateStamp)
             DATE *date;
             struct DateStamp *dateStamp;

    DESCRIPTION
        DateToDS converts the special DATE format to a DateStamp.
 */

DateToDS(date, ds)
    DATE *date; long *ds;
{
    long daysElapsed, yearsElapsed, leapYears, thisMonth, thisDay, thisYear;
    int month;

    /* Note the special handling for year < START_YEAR.  In this case,
     * the other fields are not even checked - the user just gets the
     * "start of time".
     */
    if ((thisYear = date->Dyear) < START_YEAR) {
        ds[0] = ds[1] = ds[2] = 0;
        return;
    }
    if (IsLeap(thisYear))
        calendar[FEB].Mdays = LEAP_FEB_DAYS;

    thisDay = date->Dday - 1;
    thisMonth = date->Dmonth -1;
    yearsElapsed = thisYear - START_YEAR;
    leapYears = (yearsElapsed + LEAP_ADJUST -1) / YEARS_PER_LEAP;
    daysElapsed = (yearsElapsed * DAYS_PER_YEAR) + leapYears;
    for (month = 0; month < thisMonth; ++month)
        daysElapsed += calendar[month].Mdays;
    daysElapsed += thisDay;
    calendar[FEB].Mdays = NORM_FEB_DAYS;
    ds[0] = daysElapsed;
    ds[1] = date->Dhour * MINS_PER_HOUR + date->Dminute;
    ds[2] = date->Dsecond * TICS_PER_SEC;
}
/*  FUNCTION
        CompareDS - compare two DateStamp values.

    SYNOPSIS
        int CompareDS(date1, date2)
            struct DateStamp *date1, *date2;

    DESCRIPTION
        CompareDS performs an ordered comparison between two DateStamp
        values, returning the following result codes:

            -1 => date1 < date2
             0 => date1 == date2
             1 => date1 > date2

    NOTE:
        This routine makes an assumption about the DateStamp structure,
        specifically that it can be viewed as an array of 3 long integers
        in days, minutes and ticks order.
 */

int
CompareDS(d1, d2)
    long *d1, *d2;
{
    USHORT i;
    long compare;

    for (i = 0; i < 3; ++i) {
        if (compare = (d1[i] - d2[i])) {
            if (compare < 0) return -1;
            return 1;
        }
    }
    return 0;                       /* dates match */
}

/*  FUNCTION
        DSToStr - convert a DateStamp to a formatted string.

    SYNOPSIS
        void DSToStr(str,fmt,d)
             char *str, *fmt;
             struct DateStamp *d;

    DESCRIPTION
        DSToStr works a little like sprintf.  It converts a DateStamp
        to an ascii formatted string.  The formatting style is dependent
        upon the contents of the format string, fmt, passed to this
        function.

        The content of the format string is very similar to that
        for printf, with the exception that the following letters
        have special significance:
            y => year minus 1900
            Y => full year value
            m => month value as integer
            M => month name
            d => day of month (1..31)
            D => day name ("Monday".."Sunday")
            h => hour in twenty-four hour notation
            H => hour in twelve hour notation
            i => 12 hour indicator for H notation (AM or PM)
            I => same as i
            n => minutes    (sorry...conflict with m = months)
            N => same as n
            s => seconds
            S => same as s

        All other characters are passed through as part of the normal
        formatting process.  The following are some examples with
        Saturday, July 18, 1987, 13:53 as an input date:

            "%y/%m/%d"          => 87/7/18
            "%02m/%02d/%2y"     => 07/18/87
            "%D, %M %d, %Y"     => Saturday, July 18, 1987
            "%02H:%02m i"       => 01:53 PM
            "Time now: %h%m"    => Time now: 13:53

 */
void
DSToStr(str,fmt,d)
    char *str, *fmt; long *d;
{
    DATE date;
    char fc,*fs,*out;
    USHORT ivalue;
    char new_fmt[256];          /* make it big to be "safe" */
    USHORT new_fmt_lng;
    char *svalue;

    DSToDate(d, &date);         /* convert DateStamp to DATE format */

    *str = '\0';                /* insure output is empty */
    out = str;
    fs = fmt;                   /* make copy of format string pointer */

    while (fc = *fs++) {        /* get format characters */
        if (fc == '%') {        /* formatting meta-character? */
            new_fmt_lng = 0;
            new_fmt[new_fmt_lng++] = fc;
            /* copy width information */
            while (isdigit(fc = *fs++) || fc == '-')
                new_fmt[new_fmt_lng++] = fc;

            switch (fc) {       /* what are we trying to do? */
            case 'y':           /* year - 1980 */
                ivalue = date.Dyear % 100;
write_int:
                new_fmt[new_fmt_lng++] = 'd';
                new_fmt[new_fmt_lng] = '\0';
                sprintf(out,new_fmt,ivalue);
                out = str + strlen(str);
                break;
            case 'Y':           /* full year value */
                ivalue = date.Dyear;
                goto write_int;

            case 'm':           /* month */
                ivalue = date.Dmonth;
                goto write_int;

            case 'M':           /* month name */
                svalue = calendar[date.Dmonth - 1].Mname;
write_str:
                new_fmt[new_fmt_lng++] = 's';
                new_fmt[new_fmt_lng] = '\0';
                sprintf(out,new_fmt,svalue);
                out = str + strlen(str);
                break;

            case 'd':           /* day */
                ivalue = date.Dday;
                goto write_int;

            case 'D':           /* day name */
                svalue = dayNames[d[0] % DAYS_PER_WEEK];
                goto write_str;

            case 'h':           /* hour */
                ivalue = date.Dhour;
                goto write_int;

            case 'H':           /* hour in 12 hour notation */
                ivalue = date.Dhour;
                if (ivalue >= 12) ivalue -= 12;
                goto write_int;

            case 'i':           /* AM/PM indicator */
            case 'I':
                if (date.Dhour >= 12)
                    svalue = "PM";
                else
                    svalue = "AM";
                goto write_str;

            case 'n':           /* minutes */
            case 'N':
                ivalue = date.Dminute;
                goto write_int;

            case 's':           /* seconds */
            case 'S':
                ivalue = date.Dsecond;
                goto write_int;

            default:
                /* We are in deep caca - don't know what to do with this
                 * format character.  Copy the raw format string to the
                 * output as debugging information.
                 */
                new_fmt[new_fmt_lng++] = fc;
                new_fmt[new_fmt_lng] = '\0';
                strcat(out, new_fmt);
                out = out + strlen(out);    /* advance string pointer */
                break;
            }
        }
        else
            *out++ = fc;        /* copy literal character */
    }
    *out = '\0';                /* terminating null */
}

/*  FUNCTION
        StrToDS - convert a string to a DateStamp.

    SYNOPSIS
        int StrToDS(string, date)
            char *string;
            struct DateStamp *date;

    DESCRIPTION
        StrToDS expects its string argument to contain a date in
        MM/DD/YY HH:MM:SS format.  The time portion is optional.
        StrToDS will attempt to convert the string to a DateStamp
        representation.  If successful, it will return 0.  On
        failure, a 1 is returned.

 */

int
StrToDS(str, d)
    char *str; long *d;
{
    register char c;
    int count;
    int i, item;
    DATE date;              /* unpacked DateStamp */
    char *s;

    int values[3];
    int value;

    s = str;
    for (item = 0; item < 2; ++item) {  /* item = date, then time */
        for (i = 0; i < 3; ++i) values[i] = 0;
        count = 0;
        while (c = *s++) {          /* get date value */
            if (c <= ' ')
                break;

            if (isdigit(c)) {
                value = 0;
                do {
                    value = value*10 + c - '0';
                    c = *s++;
                } while (isdigit(c));
                if (count == 3) {
    bad_value:
#ifdef DEBUG
                    puts("Error in date-time format.\n");
                    printf("at %s: values(%d) = %d, %d, %d\n",
                        s, count, values[0], values[1], values[2]);
#endif
                    return 1;
                }
                values[count++] = value;
                if (c <= ' ')
                    break;
            }
            else if (! index(DATE_SEPARATORS, c) )
                goto bad_value;     /* Illegal character - quit. */
        }                           /* end while */
        if (item) {                 /* Getting time? */
            date.Dhour = values[0];
            date.Dminute = values[1];
            date.Dsecond = values[2];
        }
        else {                      /* Getting date? */

/* It's OK to have a null date string, but it's not OK to specify only
   1 or 2 of the date components.
 */
            if (count && count != 3)
                goto bad_value;
            date.Dmonth = values[0];
            date.Dday = values[1];
            date.Dyear = values[2];
            if (date.Dyear == 0) {
                date.Dyear = START_YEAR;
                date.Dday = 1;
            }
            else {
                if (date.Dyear < (START_YEAR - 1900) )
                    date.Dyear += 100;
                date.Dyear += 1900;
            }
        }
    }                               /* end for */
    DateToDS(&date, d);
    return 0;
}                                   /* StrToDS */


#ifdef DEBUG
#include "stdio.h"
main(ac, av)
    int ac;
    char    **av;
{
    long    datestamp[3];           /* A little dangerous with Aztec */
    long    datestamp2[3];
    DATE    date, oldDate;
    long    day, lastDay;
    int     errors = 0;

    /*
     * display results from DateStamp() (hours:minutes:seconds)
     */
    DateStamp(datestamp);

    /*
     * display results from DSToDate() (e.g. "03-May-88")
     */
    DSToDate(datestamp, &date);
    printf("Current date: %02d-%s-%02d\n",
        date.Dday, calendar[ date.Dmonth - 1].Mname,
        (date.Dyear % YEARS_PER_CENTURY));

    printf("Current time: %02d:%02d:%02d\n",
        date.Dhour, date.Dminute, date.Dsecond);

    printf("\nDoing sanity check through year 2000...\n\t");
    lastDay = (2000L - START_YEAR) * 365L;
    lastDay += (2000L - START_YEAR) / YEARS_PER_LEAP;
    for (day = 0; day <= lastDay; ++day) {
        if (day % 1000 == 0) {
            printf(" %ld", day);
            fflush(stdout);
        }
        datestamp[0] = day;
        datestamp[1] = MINS_PER_HOUR - 1;
        datestamp[2] = TICS_PER_SEC * (SECS_PER_MIN - 1);
        DSToDate(datestamp, &date);
        if (day && date == oldDate) {
            printf("Got same date for days %d, %d: %02d-%s-%02d\n",
                    day - 1, day,
                    date.Dday,
                    calendar[ date.Dmonth - 1 ].Mname,
                    (date.Dyear % YEARS_PER_CENTURY));

            if (++errors == 10)
                exit(1);
        }
        DateToDS(&date, datestamp2);
        if (day != datestamp2[0]) {
            printf("\nConversion mismatch at day %ld!\n", day);
            printf("\tBad value = %ld", datestamp2[0]);
            printf("\tDate: %02d-%s-%02d\n",
                    date.Dday,
                    calendar[ date.Dmonth  -1 ].Mname,
                    (date.Dyear % YEARS_PER_CENTURY));
            if (++errors == 10)
                exit(1);
        }
        oldDate = date;
    }
    printf("\nSanity check passed.\n");
} /* main() */
#endif

SHAR_EOF
cat << \SHAR_EOF > MRDates.h
/* MRDates.h - Declarations for types and variables used by MRDates. */

typedef struct {
	int	Dyear;		/* year AD (e.g. 1987)	*/
	int	Dmonth;		/* month of year (0-11)	*/
	int	Dday;		/* day in month (1-31)	*/
	int Dhour;		/* 0-23                 */
	int Dminute;    /* 0-59                 */
	int Dsecond;    /* 0-59                 */
	int	Dweekday;	/* day of week (Sun=0)	*/
} DATE;

typedef struct {
        char    *Mname;
        int     Mdays;
		} CalEntry;

#ifdef MRDATES
CalEntry calendar[12] = {
        { "Jan", 31 },   { "Feb", 28 },  { "Mar", 31 }, { "Apr", 30 },
        { "May", 31 },   { "Jun", 30 },  { "Jul", 31 }, { "Aug", 31 },
        { "Sep", 30 },   { "Oct", 31 },  { "Nov", 30 }, { "Dec", 31 }
	};
#else
extern CalEntry calendar[12];
#endif

#ifdef MRDATES
char *dayNames[7] = {
	"Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"
	};
#else
extern char *dayNames[7];
#endif

SHAR_EOF
cat << \SHAR_EOF > Makefile
# Makefile for DiffDir program

CFLAGS = -n

OBJ = DiffDir.o MRDates.o

DiffDir: $(OBJ)
    ln -w -g -o DiffDir $(OBJ) -lc

clean:
    delete (#?.o|#?.dbg)

SRC = DiffDir.c MRDates.h MRDates.c Makefile
BIN = DiffDir.DOC DiffDir Sample.Output

zoo: $(SRC) $(BIN)
    zoo a DiffDir $(SRC) $(BIN)


SHAR_EOF
cat << \SHAR_EOF > Sample.Output
*** 'dir1/DifferentType'  is a directory
*** but 'dir2/DifferentType'  is a file!
*** Mismatch:  comments 
dir1/
12-31-88 15:48:41 -----rwed       18 File2
: This is file 2 in level 1 of dir 1.
------------------------------------
dir2/
12-31-88 15:48:41 -----rwed       18 File2
====================================
*** Mismatch:  names 
dir1/
12-31-88 15:48:53 -----rwed       18 File3
------------------------------------
dir2/
12-31-88 15:48:53 -----rwed       18 file3
====================================
*** Mismatch:  dates  sizes 
dir1/Level2/
12-31-88 15:53:10 -----rwed       81 File1
------------------------------------
dir2/Level2/
12-31-88 15:48:33 -----rwed       18 File1
====================================
*** File missing: 'dir1/Level2/File2'
*** File missing: 'dir2/Level2/Level3/File1'
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.