/* 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): 07/04/89 V1.1 (MRR) Added flags for specific tests. 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] [-D] [-c] [-s scriptfile] [-v] Where: -C suppresses the testing of comments -S suppresses the testing of file sizes -D suppresses the testing of file dates -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 is the name of the first directory is the name of the second directory */ #include #include #include #include #include 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; #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 " }; 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; ULONG testFlags = 0; LONG totalFiles, totalDirs; BOOL verbose = FALSE; main(argc, argv) int argc; char **argv; { char flag; testFlags = 0xFFFFFFFF; while (--argc > 0 && **++argv == '-') { flag = (*argv)[1]; switch (flag) { case 'C': /* Ignore comments. */ testFlags &= (~COMMENTS_DONT_MATCH); break; case 'D': /* Suppress date test. */ testFlags &= (~DATES_DONT_MATCH); break; case 'F': /* Ignore flag differences */ testFlags &= (~FLAGS_DONT_MATCH); break; case 'S': /* Ignore size differences (?!?) */ testFlags &= (~SIZES_DONT_MATCH); break; 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 to the . 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 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 and . 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 , attempting to match its directory node entries with entries in . 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, and which are expected to represent similar files in different directory hierarchies. CompareFile compares the currently selected file attributes and will report any discrepancies to standard output. */ void CompareFile(f1, f2) FileNode *f1, *f2; { 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 ((testFlags & FLAGS_DONT_MATCH) && (f1->flags != f2->flags) ) error |= FLAGS_DONT_MATCH; if ((testFlags & DATES_DONT_MATCH) && (CompareDS(&f1->date, &f2->date) ) ) error |= DATES_DONT_MATCH; if (!ignoreCase) { if (strcmp(f1->name, f2->name) != 0) error |= NAMES_DONT_MATCH; } if ( (testFlags & SIZES_DONT_MATCH) && (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 ((testFlags & COMMENTS_DONT_MATCH) && (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 are compared to those in list . Discrepancies are reported to standard output. After all the files in have been tested, a second scan is made over list for any remaining file nodes. These represent files which were not found in . Upon return, all file nodes will have been removed from lists and , 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 , copies 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 for a file description node whose name matches the name in . 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 and . 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 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 and 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] [-D] [-S] [-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 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); }