/* This program should have been a substitute of the FC (file compare) DOS program. In reality the ASCII comparison engine is so rudimental that this program is barely a substitute of the COMP DOS program. Anyway it's better than nothing, isn't it? Maurizio Spagni (24/10/99) */ #include #include #include #include #include #include #define TAB_SIZE 8 #define DEFAULT_MAX_DIFF 10 #define MAX_LINE_LEN 10000 #define END_OF_STRING 0 #define NO_DIFFERENCES "No differences encountered\n" #define TOO_MANY "Too many differences encountered (Use /LBn to change the maximum)\n" #define LINE_TOO_LONG "FC: Line too long (exceeding %d chars)\n" #define INVALID_SWITCH "FC: Invalid switch: %s\n" #define ASCII_STOP "******\n" typedef enum { FALSE, TRUE } bool; // Global Variables bool NoTabExpand = FALSE, Trim = FALSE, FirstAndLastOnly = FALSE; bool IgnoreCase = FALSE, ShowLineNumbers = FALSE; FILE *FilePointers[2]; char *FileNames[2]; int MaxDifferences = DEFAULT_MAX_DIFF; enum { DEFAULT, ASCII, BINARY } CompareMode = DEFAULT; void HelpMessage() { printf("Compares two files or sets of files and displays the differences between them.\n"); printf("\nFC [/A] [/C] [/L] [/LBn] [/N] [/T] [/W] [/nnnn] [drive1:][path1]filename1"); printf("\n [drive2][path2]filename2"); printf("\nFC /B [drive1][path1]filename1 [drive2:][path2]filename2\n"); printf("\n /A Displays only first and last lines for each set of differences."); printf("\n /B Performs a binary comparison."); printf("\n /C Disregards the case of letters."); printf("\n /L Compares files as ASCII text."); printf("\n /LBn Sets the maximum consecutive mismatches to n lines."); printf("\n /N Displays the line numbers on an ASCII comparison."); printf("\n /T Does not expand tabs to spaces."); printf("\n /W Compress white space (tabs and spaces) for comparison.\n"); } // Scan arguments for switches and files bool ScanArguments(int argc, char *argv[]) { int FileCounter = 0; int i; // Scan through arguments for (i = 1; i < argc; i++) if ((argv[i][0] == '/') || (argv[i][0] == '-')) // Switch chars if (argv[i][2] == END_OF_STRING) switch (toupper(argv[i][1])) // Single char switch in the form "/x" { case 'B': CompareMode = BINARY; break; case 'L': CompareMode = ASCII; break; case 'A': FirstAndLastOnly = TRUE; break; case 'C': IgnoreCase = TRUE; break; case 'N': ShowLineNumbers = TRUE; break; case 'T': NoTabExpand = TRUE; break; case 'W': Trim = TRUE; break; case '?': case 'H': HelpMessage(); return FALSE; default: printf(INVALID_SWITCH, argv[i]); return FALSE; } else // Not a single char switch if ((toupper(argv[i][1]) == 'L') && (toupper(argv[i][2]) == 'B')) MaxDifferences = atoi(&argv[i][3]); else { printf(INVALID_SWITCH, argv[i]); return FALSE; } else // Not a switch: it's a filename { if (FileCounter >= 2) { printf("FC: Too many filespecs\n"); return FALSE; } FileNames[FileCounter] = strdup(argv[i]); FileCounter++; } switch (FileCounter) { case 0: printf("FC: No file specified\n"); return FALSE; case 1: FileNames[1] = strdup("."); // Default: the current directory } return TRUE; } long FileSize(FILE* Stream) { long CurrPos, Length; CurrPos = ftell(Stream); fseek(Stream, 0L, SEEK_END); Length = ftell(Stream); fseek(Stream, CurrPos, SEEK_SET); return Length; } void BinaryCompare(void) { unsigned char a, b; long int Index, FileSize1, FileSize2; int Differences = 0; FileSize1 = FileSize(FilePointers[0]); FileSize2 = FileSize(FilePointers[1]); if (FileSize1 != FileSize2) { printf("FC: Warning: the files are of different size!\n"); if (FileSize1 < FileSize2) FileSize1 = FileSize2; } for (Index = 0; Index < FileSize1; Index++) { fread(&a, 1, 1, FilePointers[0]); fread(&b, 1, 1, FilePointers[1]); if (a != b) { Differences++; if (Differences > MaxDifferences) { printf(TOO_MANY); return; } printf("%08X: %02X %02X\n", Index, a, b); } } if (Differences == 0) printf(NO_DIFFERENCES); } // Read a line from an ascii file void ReadLine(FILE* fp, char* Line, unsigned int* Length) { bool Space = FALSE; char c; unsigned int LineLength = 0; do { c = fgetc(fp); if (feof(fp)) break; if (Trim) // Pack spaces { if (isspace(c)) { if (Space) continue; // No more blanks or tabs allowed: ignore Space = TRUE; c = ' '; // Insert a blank } else Space = FALSE; } else if ((c == '\t') && (!NoTabExpand)) // Tab expansion { int Blanks = TAB_SIZE - (LineLength % TAB_SIZE); if ((LineLength + Blanks) >= MAX_LINE_LEN) { printf(LINE_TOO_LONG, MAX_LINE_LEN); return; } do // Insert the blanks { *Line = ' '; Line++; Blanks--; LineLength++; } while (Blanks > 0); continue; // Chars inserted: go on } if (LineLength >= MAX_LINE_LEN) { printf(LINE_TOO_LONG, MAX_LINE_LEN); return; } LineLength++; *Line = c; Line++; // Insert the char } while (c != '\n'); *Length = LineLength; // In case of end of file simply return Length = 0 without altering Line if (LineLength != 0) *Line = END_OF_STRING; // Close the string } void AsciiCompare(void) { unsigned int LineNumber; char* String[2]; unsigned int StringLen[2]; bool Equal, Show, FirstDiff; int Differences; int i; for (i = 0; i < 2; i++) { String[i] = new char[MAX_LINE_LEN + 1]; *String[i] = END_OF_STRING; // Empty string } for (i = 0; i < 2; i++) { rewind(FilePointers[0]); rewind(FilePointers[1]); Differences = 0; LineNumber = 0; Show = TRUE; FirstDiff = TRUE; while (TRUE) { ReadLine(FilePointers[0], String[0], &StringLen[0]); if ((i == 0) && feof(FilePointers[0])) break; ReadLine(FilePointers[1], String[1], &StringLen[1]); if ((i == 1) && feof(FilePointers[1])) break; LineNumber++; Equal = FALSE; if (StringLen[0] == StringLen[1]) if (IgnoreCase) Equal = (bool)(memicmp(String[0], String[1], StringLen[0]) == 0); else Equal = (bool)(memcmp(String[0], String[1], StringLen[0]) == 0); if (!Equal) { if (FirstDiff) printf("****** %s\n", FileNames[i]); FirstDiff = FALSE; Differences++; if (Differences > MaxDifferences) { printf(ASCII_STOP); printf(TOO_MANY); return; } if (Show) { if (ShowLineNumbers) printf("%d: ", LineNumber); printf("%s", String[i]); } if (FirstAndLastOnly) Show = FALSE; } else { Differences = 0; if (!Show) // If it's the first matching line after a difference { if (ShowLineNumbers) printf("%d: ", LineNumber); printf("%s", String[i]); Show = TRUE; } } } // End of file if (!Show) // No matching line after a difference { // Print the last one if (ShowLineNumbers) printf("%d: ", LineNumber); printf("%s", String[i]); } if (!FirstDiff) printf(ASCII_STOP); } for (i = 0; i < 2; i++) delete (String[i]); if (Differences == 0) printf(NO_DIFFERENCES); } bool HasWildcards(const char* Filename) { return (bool)(fnsplit(Filename, NULL, NULL, NULL, NULL) & WILDCARDS); } bool IsADirectory(char* Filename) { struct stat statbuf; if (stat(Filename, &statbuf) != -1) if (statbuf.st_mode & S_IFDIR) return TRUE; return FALSE; } /* Add a '\' at the end of Filename if not already present */ void EndingBackSlash(char** Filename) { if ((*Filename)[strlen(*Filename) - 1] != '\\') { char* Temp = (char*)malloc(strlen(*Filename) + 2); sprintf(Temp, "%s\\", *Filename); free(*Filename); (*Filename) = Temp; } } /* Extract the path part of the filename */ char* FilePath(char* Filename) { char drive[MAXDRIVE]; char dir[MAXDIR]; char *Path; fnsplit(Filename, drive, dir, NULL, NULL); Path = (char*)malloc(strlen(drive) + strlen(dir) + 1); sprintf(Path, "%s%s", drive, dir); return Path; } /* Check if the file extension indicates a binary file */ bool BinaryFile(char* Filename) { const char* BinExt[] = { ".EXE", ".COM", ".SYS", ".OBJ", ".LIB", ".BIN" }; char Ext[MAXEXT]; int i; if (fnsplit(Filename, NULL, NULL, NULL, Ext) & EXTENSION) for (i = 0; i < sizeof (BinExt)/sizeof (BinExt[0]); i++) if (strcmpi(BinExt[i], Ext) == 0) return TRUE; return FALSE; } int main(int argc, char *argv[]) { char* Path0; char* Path1; bool File1IsDirectory = TRUE; int Count = 0; struct ffblk FindData; bool Done0; if (!ScanArguments(argc, argv)) return 1; if (IsADirectory(FileNames[0])) { Path0 = FileNames[0]; EndingBackSlash(&Path0); /* Assume all files */ FileNames[0] = (char*)malloc(strlen(Path0) + 3 + 1); sprintf(FileNames[0], "%s*.*", Path0); } else Path0 = FilePath(FileNames[0]); if (FileNames[1][strlen(FileNames[1]) - 1] == ':') // Disk drive only Path1 = strdup(FileNames[1]); else if (IsADirectory(FileNames[1])) { Path1 = strdup(FileNames[1]); EndingBackSlash(&Path1); } else { Path1 = FilePath(FileNames[1]); // Any wildcard here means "all files" File1IsDirectory = HasWildcards(FileNames[1]); } Done0 = (bool)findfirst(FileNames[0], &FindData, 0); while (!Done0) { free(FileNames[0]); FileNames[0] = (char*)malloc(strlen(Path0) + strlen(FindData.ff_name) + 1); sprintf(FileNames[0], "%s%s", Path0, FindData.ff_name); if (File1IsDirectory) { free(FileNames[1]); FileNames[1] = (char*)malloc(strlen(Path1) + strlen(FindData.ff_name) + 1); sprintf(FileNames[1], "%s%s", Path1, FindData.ff_name); } if ((FilePointers[1] = fopen(FileNames[1], "rb")) != NULL) { if ((FilePointers[0] = fopen(FileNames[0], "rb")) == NULL) { fcloseall(); printf("FC: Error opening file %s\n", FileNames[0]); return 1; } Count++; printf("Comparing files %s and %s\n", FileNames[0], FileNames[1]); switch (CompareMode) { case ASCII: AsciiCompare(); break; case BINARY: BinaryCompare(); break; default: if (BinaryFile(FileNames[0]) || BinaryFile(FileNames[1])) BinaryCompare(); else AsciiCompare(); } fcloseall(); } Done0 = (bool)findnext(&FindData); } free (Path0); free (Path1); if (Count == 0) { fprintf(stderr, "FC: No such file or directory\n"); return 1; } return 0; }