1 /*
2 * PROJECT: ReactOS FC Command
3 * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
4 * PURPOSE: Comparing files
5 * COPYRIGHT: Copyright 2021 Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com)
6 */
7 #include "fc.h"
8
9 #ifdef __REACTOS__
10 #include <conutils.h>
11 #else
12 #include <stdio.h>
13 #define ConInitStdStreams() /* empty */
14 #define StdOut stdout
15 #define StdErr stderr
ConPuts(FILE * fp,LPCWSTR psz)16 void ConPuts(FILE *fp, LPCWSTR psz)
17 {
18 fputws(psz, fp);
19 }
ConPrintf(FILE * fp,LPCWSTR psz,...)20 void ConPrintf(FILE *fp, LPCWSTR psz, ...)
21 {
22 va_list va;
23 va_start(va, psz);
24 vfwprintf(fp, psz, va);
25 va_end(va);
26 }
ConResPuts(FILE * fp,UINT nID)27 void ConResPuts(FILE *fp, UINT nID)
28 {
29 WCHAR sz[MAX_PATH];
30 LoadStringW(NULL, nID, sz, _countof(sz));
31 fputws(sz, fp);
32 }
ConResPrintf(FILE * fp,UINT nID,...)33 void ConResPrintf(FILE *fp, UINT nID, ...)
34 {
35 va_list va;
36 WCHAR sz[MAX_PATH];
37 va_start(va, nID);
38 LoadStringW(NULL, nID, sz, _countof(sz));
39 vfwprintf(fp, sz, va);
40 va_end(va);
41 }
42 #endif
43 #include <strsafe.h>
44 #include <shlwapi.h>
45
NoDifference(VOID)46 FCRET NoDifference(VOID)
47 {
48 ConResPuts(StdOut, IDS_NO_DIFFERENCE);
49 return FCRET_IDENTICAL;
50 }
51
Different(LPCWSTR file0,LPCWSTR file1)52 FCRET Different(LPCWSTR file0, LPCWSTR file1)
53 {
54 ConResPrintf(StdOut, IDS_DIFFERENT, file0, file1);
55 return FCRET_DIFFERENT;
56 }
57
LongerThan(LPCWSTR file0,LPCWSTR file1)58 FCRET LongerThan(LPCWSTR file0, LPCWSTR file1)
59 {
60 ConResPrintf(StdOut, IDS_LONGER_THAN, file0, file1);
61 return FCRET_DIFFERENT;
62 }
63
OutOfMemory(VOID)64 FCRET OutOfMemory(VOID)
65 {
66 ConResPuts(StdErr, IDS_OUT_OF_MEMORY);
67 return FCRET_INVALID;
68 }
69
CannotRead(LPCWSTR file)70 FCRET CannotRead(LPCWSTR file)
71 {
72 ConResPrintf(StdErr, IDS_CANNOT_READ, file);
73 return FCRET_INVALID;
74 }
75
InvalidSwitch(VOID)76 FCRET InvalidSwitch(VOID)
77 {
78 ConResPuts(StdErr, IDS_INVALID_SWITCH);
79 return FCRET_INVALID;
80 }
81
ResyncFailed(VOID)82 FCRET ResyncFailed(VOID)
83 {
84 ConResPuts(StdOut, IDS_RESYNC_FAILED);
85 return FCRET_DIFFERENT;
86 }
87
PrintCaption(LPCWSTR file)88 VOID PrintCaption(LPCWSTR file)
89 {
90 ConPrintf(StdOut, L"***** %ls\n", file);
91 }
92
PrintEndOfDiff(VOID)93 VOID PrintEndOfDiff(VOID)
94 {
95 ConPuts(StdOut, L"*****\n\n");
96 }
97
PrintDots(VOID)98 VOID PrintDots(VOID)
99 {
100 ConPuts(StdOut, L"...\n");
101 }
102
PrintLineW(const FILECOMPARE * pFC,DWORD lineno,LPCWSTR psz)103 VOID PrintLineW(const FILECOMPARE *pFC, DWORD lineno, LPCWSTR psz)
104 {
105 if (pFC->dwFlags & FLAG_N)
106 ConPrintf(StdOut, L"%5d: %ls\n", lineno, psz);
107 else
108 ConPrintf(StdOut, L"%ls\n", psz);
109 }
PrintLineA(const FILECOMPARE * pFC,DWORD lineno,LPCSTR psz)110 VOID PrintLineA(const FILECOMPARE *pFC, DWORD lineno, LPCSTR psz)
111 {
112 if (pFC->dwFlags & FLAG_N)
113 ConPrintf(StdOut, L"%5d: %hs\n", lineno, psz);
114 else
115 ConPrintf(StdOut, L"%hs\n", psz);
116 }
117
DoOpenFileForInput(LPCWSTR file)118 HANDLE DoOpenFileForInput(LPCWSTR file)
119 {
120 HANDLE hFile = CreateFileW(file, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
121 if (hFile == INVALID_HANDLE_VALUE)
122 {
123 ConResPrintf(StdErr, IDS_CANNOT_OPEN, file);
124 }
125 return hFile;
126 }
127
BinaryFileCompare(FILECOMPARE * pFC)128 static FCRET BinaryFileCompare(FILECOMPARE *pFC)
129 {
130 FCRET ret;
131 HANDLE hFile0, hFile1, hMapping0 = NULL, hMapping1 = NULL;
132 LPBYTE pb0 = NULL, pb1 = NULL;
133 LARGE_INTEGER ib, cb0, cb1, cbCommon;
134 DWORD cbView, ibView;
135 BOOL fDifferent = FALSE;
136
137 hFile0 = DoOpenFileForInput(pFC->file[0]);
138 if (hFile0 == INVALID_HANDLE_VALUE)
139 return FCRET_CANT_FIND;
140 hFile1 = DoOpenFileForInput(pFC->file[1]);
141 if (hFile1 == INVALID_HANDLE_VALUE)
142 {
143 CloseHandle(hFile0);
144 return FCRET_CANT_FIND;
145 }
146
147 do
148 {
149 if (_wcsicmp(pFC->file[0], pFC->file[1]) == 0)
150 {
151 ret = NoDifference();
152 break;
153 }
154 if (!GetFileSizeEx(hFile0, &cb0))
155 {
156 ret = CannotRead(pFC->file[0]);
157 break;
158 }
159 if (!GetFileSizeEx(hFile1, &cb1))
160 {
161 ret = CannotRead(pFC->file[1]);
162 break;
163 }
164 cbCommon.QuadPart = min(cb0.QuadPart, cb1.QuadPart);
165 if (cbCommon.QuadPart > 0)
166 {
167 hMapping0 = CreateFileMappingW(hFile0, NULL, PAGE_READONLY,
168 cb0.HighPart, cb0.LowPart, NULL);
169 if (hMapping0 == NULL)
170 {
171 ret = CannotRead(pFC->file[0]);
172 break;
173 }
174 hMapping1 = CreateFileMappingW(hFile1, NULL, PAGE_READONLY,
175 cb1.HighPart, cb1.LowPart, NULL);
176 if (hMapping1 == NULL)
177 {
178 ret = CannotRead(pFC->file[1]);
179 break;
180 }
181
182 ret = FCRET_IDENTICAL;
183 for (ib.QuadPart = 0; ib.QuadPart < cbCommon.QuadPart; )
184 {
185 cbView = (DWORD)min(cbCommon.QuadPart - ib.QuadPart, MAX_VIEW_SIZE);
186 pb0 = MapViewOfFile(hMapping0, FILE_MAP_READ, ib.HighPart, ib.LowPart, cbView);
187 pb1 = MapViewOfFile(hMapping1, FILE_MAP_READ, ib.HighPart, ib.LowPart, cbView);
188 if (!pb0 || !pb1)
189 {
190 ret = OutOfMemory();
191 break;
192 }
193 for (ibView = 0; ibView < cbView; ++ib.QuadPart, ++ibView)
194 {
195 if (pb0[ibView] == pb1[ibView])
196 continue;
197
198 fDifferent = TRUE;
199 if (cbCommon.QuadPart > MAXDWORD)
200 {
201 ConPrintf(StdOut, L"%016I64X: %02X %02X\n", ib.QuadPart,
202 pb0[ibView], pb1[ibView]);
203 }
204 else
205 {
206 ConPrintf(StdOut, L"%08lX: %02X %02X\n", ib.LowPart,
207 pb0[ibView], pb1[ibView]);
208 }
209 }
210 UnmapViewOfFile(pb0);
211 UnmapViewOfFile(pb1);
212 pb0 = pb1 = NULL;
213 }
214 if (ret != FCRET_IDENTICAL)
215 break;
216 }
217
218 if (cb0.QuadPart < cb1.QuadPart)
219 ret = LongerThan(pFC->file[1], pFC->file[0]);
220 else if (cb0.QuadPart > cb1.QuadPart)
221 ret = LongerThan(pFC->file[0], pFC->file[1]);
222 else if (fDifferent)
223 ret = Different(pFC->file[0], pFC->file[1]);
224 else
225 ret = NoDifference();
226 } while (0);
227
228 UnmapViewOfFile(pb0);
229 UnmapViewOfFile(pb1);
230 CloseHandle(hMapping0);
231 CloseHandle(hMapping1);
232 CloseHandle(hFile0);
233 CloseHandle(hFile1);
234 return ret;
235 }
236
TextFileCompare(FILECOMPARE * pFC)237 static FCRET TextFileCompare(FILECOMPARE *pFC)
238 {
239 FCRET ret;
240 HANDLE hFile0, hFile1, hMapping0 = NULL, hMapping1 = NULL;
241 LARGE_INTEGER cb0, cb1;
242 BOOL fUnicode = !!(pFC->dwFlags & FLAG_U);
243
244 hFile0 = DoOpenFileForInput(pFC->file[0]);
245 if (hFile0 == INVALID_HANDLE_VALUE)
246 return FCRET_CANT_FIND;
247 hFile1 = DoOpenFileForInput(pFC->file[1]);
248 if (hFile1 == INVALID_HANDLE_VALUE)
249 {
250 CloseHandle(hFile0);
251 return FCRET_CANT_FIND;
252 }
253
254 do
255 {
256 if (_wcsicmp(pFC->file[0], pFC->file[1]) == 0)
257 {
258 ret = NoDifference();
259 break;
260 }
261 if (!GetFileSizeEx(hFile0, &cb0))
262 {
263 ret = CannotRead(pFC->file[0]);
264 break;
265 }
266 if (!GetFileSizeEx(hFile1, &cb1))
267 {
268 ret = CannotRead(pFC->file[1]);
269 break;
270 }
271 if (cb0.QuadPart == 0 && cb1.QuadPart == 0)
272 {
273 ret = NoDifference();
274 break;
275 }
276 if (cb0.QuadPart > 0)
277 {
278 hMapping0 = CreateFileMappingW(hFile0, NULL, PAGE_READONLY,
279 cb0.HighPart, cb0.LowPart, NULL);
280 if (hMapping0 == NULL)
281 {
282 ret = CannotRead(pFC->file[0]);
283 break;
284 }
285 }
286 if (cb1.QuadPart > 0)
287 {
288 hMapping1 = CreateFileMappingW(hFile1, NULL, PAGE_READONLY,
289 cb1.HighPart, cb1.LowPart, NULL);
290 if (hMapping1 == NULL)
291 {
292 ret = CannotRead(pFC->file[1]);
293 break;
294 }
295 }
296 if (fUnicode)
297 ret = TextCompareW(pFC, &hMapping0, &cb0, &hMapping1, &cb1);
298 else
299 ret = TextCompareA(pFC, &hMapping0, &cb0, &hMapping1, &cb1);
300 } while (0);
301
302 CloseHandle(hMapping0);
303 CloseHandle(hMapping1);
304 CloseHandle(hFile0);
305 CloseHandle(hFile1);
306 return ret;
307 }
308
IsBinaryExt(LPCWSTR filename)309 static BOOL IsBinaryExt(LPCWSTR filename)
310 {
311 // Don't change this array. This is by design.
312 // See also: https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/fc
313 static const LPCWSTR s_exts[] = { L"EXE", L"COM", L"SYS", L"OBJ", L"LIB", L"BIN" };
314 size_t iext;
315 LPCWSTR pch, ext, pch0 = wcsrchr(filename, L'\\'), pch1 = wcsrchr(filename, L'/');
316 if (!pch0 && !pch1)
317 pch = filename;
318 else if (!pch0 && pch1)
319 pch = pch1;
320 else if (pch0 && !pch1)
321 pch = pch0;
322 else if (pch0 < pch1)
323 pch = pch1;
324 else
325 pch = pch0;
326
327 ext = wcsrchr(pch, L'.');
328 if (ext)
329 {
330 ++ext;
331 for (iext = 0; iext < _countof(s_exts); ++iext)
332 {
333 if (_wcsicmp(ext, s_exts[iext]) == 0)
334 return TRUE;
335 }
336 }
337 return FALSE;
338 }
339
FileCompare(FILECOMPARE * pFC)340 static FCRET FileCompare(FILECOMPARE *pFC)
341 {
342 FCRET ret;
343 ConResPrintf(StdOut, IDS_COMPARING, pFC->file[0], pFC->file[1]);
344
345 if (!(pFC->dwFlags & FLAG_L) &&
346 ((pFC->dwFlags & FLAG_B) || IsBinaryExt(pFC->file[0]) || IsBinaryExt(pFC->file[1])))
347 {
348 ret = BinaryFileCompare(pFC);
349 }
350 else
351 {
352 ret = TextFileCompare(pFC);
353 }
354
355 ConPuts(StdOut, L"\n");
356 return ret;
357 }
358
359 /* Is it L"." or L".."? */
360 #define IS_DOTS(pch) \
361 ((*(pch) == L'.') && (((pch)[1] == 0) || (((pch)[1] == L'.') && ((pch)[2] == 0))))
362 #define HasWildcard(filename) \
363 ((wcschr((filename), L'*') != NULL) || (wcschr((filename), L'?') != NULL))
364
IsTitleWild(LPCWSTR filename)365 static inline BOOL IsTitleWild(LPCWSTR filename)
366 {
367 LPCWSTR pch = PathFindFileNameW(filename);
368 return (pch && *pch == L'*' && pch[1] == L'.' && !HasWildcard(&pch[2]));
369 }
370
FileCompareOneSideWild(const FILECOMPARE * pFC,BOOL bWildRight)371 static FCRET FileCompareOneSideWild(const FILECOMPARE *pFC, BOOL bWildRight)
372 {
373 FCRET ret = FCRET_IDENTICAL;
374 WIN32_FIND_DATAW find;
375 HANDLE hFind;
376 WCHAR szPath[MAX_PATH];
377 FILECOMPARE fc;
378
379 hFind = FindFirstFileW(pFC->file[bWildRight], &find);
380 if (hFind == INVALID_HANDLE_VALUE)
381 {
382 ConResPrintf(StdErr, IDS_CANNOT_OPEN, pFC->file[bWildRight]);
383 ConPuts(StdOut, L"\n");
384 return FCRET_CANT_FIND;
385 }
386 StringCbCopyW(szPath, sizeof(szPath), pFC->file[bWildRight]);
387
388 fc = *pFC;
389 fc.file[!bWildRight] = pFC->file[!bWildRight];
390 fc.file[bWildRight] = szPath;
391 do
392 {
393 if (IS_DOTS(find.cFileName))
394 continue;
395
396 // replace file title
397 PathRemoveFileSpecW(szPath);
398 PathAppendW(szPath, find.cFileName);
399
400 switch (FileCompare(&fc))
401 {
402 case FCRET_IDENTICAL:
403 break;
404 case FCRET_DIFFERENT:
405 if (ret != FCRET_INVALID)
406 ret = FCRET_DIFFERENT;
407 break;
408 default:
409 ret = FCRET_INVALID;
410 break;
411 }
412 } while (FindNextFileW(hFind, &find));
413
414 FindClose(hFind);
415 return ret;
416 }
417
FileCompareWildTitle(const FILECOMPARE * pFC)418 static FCRET FileCompareWildTitle(const FILECOMPARE *pFC)
419 {
420 FCRET ret = FCRET_IDENTICAL;
421 WIN32_FIND_DATAW find;
422 HANDLE hFind;
423 WCHAR szPath0[MAX_PATH], szPath1[MAX_PATH];
424 FILECOMPARE fc;
425 LPWSTR pch;
426
427 hFind = FindFirstFileW(pFC->file[0], &find);
428 if (hFind == INVALID_HANDLE_VALUE)
429 {
430 ConResPrintf(StdErr, IDS_CANNOT_OPEN, pFC->file[0]);
431 ConPuts(StdOut, L"\n");
432 return FCRET_CANT_FIND;
433 }
434 StringCbCopyW(szPath0, sizeof(szPath0), pFC->file[0]);
435 StringCbCopyW(szPath1, sizeof(szPath1), pFC->file[1]);
436 pch = PathFindExtensionW(pFC->file[1]);
437
438 fc = *pFC;
439 fc.file[0] = szPath0;
440 fc.file[1] = szPath1;
441 do
442 {
443 if (IS_DOTS(find.cFileName))
444 continue;
445
446 // replace file title
447 PathRemoveFileSpecW(szPath0);
448 PathRemoveFileSpecW(szPath1);
449 PathAppendW(szPath0, find.cFileName);
450 PathAppendW(szPath1, find.cFileName);
451
452 // replace dot extension
453 PathRemoveExtensionW(szPath1);
454 PathAddExtensionW(szPath1, pch);
455
456 switch (FileCompare(&fc))
457 {
458 case FCRET_IDENTICAL:
459 break;
460 case FCRET_DIFFERENT:
461 if (ret != FCRET_INVALID)
462 ret = FCRET_DIFFERENT;
463 break;
464 default:
465 ret = FCRET_INVALID;
466 break;
467 }
468 } while (FindNextFileW(hFind, &find));
469
470 FindClose(hFind);
471 return ret;
472 }
473
FileCompareBothWild(const FILECOMPARE * pFC)474 static FCRET FileCompareBothWild(const FILECOMPARE *pFC)
475 {
476 FCRET ret = FCRET_IDENTICAL;
477 WIN32_FIND_DATAW find0, find1;
478 HANDLE hFind0, hFind1;
479 WCHAR szPath0[MAX_PATH], szPath1[MAX_PATH];
480 FILECOMPARE fc;
481
482 hFind0 = FindFirstFileW(pFC->file[0], &find0);
483 if (hFind0 == INVALID_HANDLE_VALUE)
484 {
485 ConResPrintf(StdErr, IDS_CANNOT_OPEN, pFC->file[0]);
486 ConPuts(StdOut, L"\n");
487 return FCRET_CANT_FIND;
488 }
489 hFind1 = FindFirstFileW(pFC->file[1], &find1);
490 if (hFind1 == INVALID_HANDLE_VALUE)
491 {
492 CloseHandle(hFind0);
493 ConResPrintf(StdErr, IDS_CANNOT_OPEN, pFC->file[1]);
494 ConPuts(StdOut, L"\n");
495 return FCRET_CANT_FIND;
496 }
497 StringCbCopyW(szPath0, sizeof(szPath0), pFC->file[0]);
498 StringCbCopyW(szPath1, sizeof(szPath1), pFC->file[1]);
499
500 fc = *pFC;
501 fc.file[0] = szPath0;
502 fc.file[1] = szPath1;
503 do
504 {
505 while (IS_DOTS(find0.cFileName))
506 {
507 if (!FindNextFileW(hFind0, &find0))
508 goto quit;
509 }
510 while (IS_DOTS(find1.cFileName))
511 {
512 if (!FindNextFileW(hFind1, &find1))
513 goto quit;
514 }
515
516 // replace file title
517 PathRemoveFileSpecW(szPath0);
518 PathRemoveFileSpecW(szPath1);
519 PathAppendW(szPath0, find0.cFileName);
520 PathAppendW(szPath1, find1.cFileName);
521
522 switch (FileCompare(&fc))
523 {
524 case FCRET_IDENTICAL:
525 break;
526 case FCRET_DIFFERENT:
527 if (ret != FCRET_INVALID)
528 ret = FCRET_DIFFERENT;
529 break;
530 default:
531 ret = FCRET_INVALID;
532 break;
533 }
534 } while (FindNextFileW(hFind0, &find0) && FindNextFileW(hFind1, &find1));
535 quit:
536 CloseHandle(hFind0);
537 CloseHandle(hFind1);
538 return ret;
539 }
540
WildcardFileCompare(FILECOMPARE * pFC)541 static FCRET WildcardFileCompare(FILECOMPARE *pFC)
542 {
543 BOOL fWild0, fWild1;
544
545 if (pFC->dwFlags & FLAG_HELP)
546 {
547 ConResPuts(StdOut, IDS_USAGE);
548 return FCRET_INVALID;
549 }
550
551 if (!pFC->file[0] || !pFC->file[1])
552 {
553 ConResPuts(StdErr, IDS_NEEDS_FILES);
554 return FCRET_INVALID;
555 }
556
557 fWild0 = HasWildcard(pFC->file[0]);
558 fWild1 = HasWildcard(pFC->file[1]);
559 if (fWild0 && fWild1)
560 {
561 if (IsTitleWild(pFC->file[0]) && IsTitleWild(pFC->file[1]))
562 return FileCompareWildTitle(pFC);
563 else
564 return FileCompareBothWild(pFC);
565 }
566 else if (fWild0)
567 {
568 return FileCompareOneSideWild(pFC, FALSE);
569 }
570 else if (fWild1)
571 {
572 return FileCompareOneSideWild(pFC, TRUE);
573 }
574 return FileCompare(pFC);
575 }
576
wmain(int argc,WCHAR ** argv)577 int wmain(int argc, WCHAR **argv)
578 {
579 FILECOMPARE fc = { .dwFlags = 0, .n = 100, .nnnn = 2 };
580 PWCHAR endptr;
581 INT i;
582
583 /* Initialize the Console Standard Streams */
584 ConInitStdStreams();
585
586 for (i = 1; i < argc; ++i)
587 {
588 if (argv[i][0] != L'/')
589 {
590 if (!fc.file[0])
591 fc.file[0] = argv[i];
592 else if (!fc.file[1])
593 fc.file[1] = argv[i];
594 else
595 return InvalidSwitch();
596 continue;
597 }
598 switch (towupper(argv[i][1]))
599 {
600 case L'A':
601 fc.dwFlags |= FLAG_A;
602 break;
603 case L'B':
604 fc.dwFlags |= FLAG_B;
605 break;
606 case L'C':
607 fc.dwFlags |= FLAG_C;
608 break;
609 case L'L':
610 if (_wcsicmp(argv[i], L"/L") == 0)
611 {
612 fc.dwFlags |= FLAG_L;
613 }
614 else if (towupper(argv[i][2]) == L'B')
615 {
616 if (iswdigit(argv[i][3]))
617 {
618 fc.dwFlags |= FLAG_LBn;
619 fc.n = wcstoul(&argv[i][3], &endptr, 10);
620 if (endptr == NULL || *endptr != 0)
621 return InvalidSwitch();
622 }
623 else
624 {
625 return InvalidSwitch();
626 }
627 }
628 break;
629 case L'N':
630 fc.dwFlags |= FLAG_N;
631 break;
632 case L'O':
633 if (_wcsicmp(argv[i], L"/OFF") == 0 || _wcsicmp(argv[i], L"/OFFLINE") == 0)
634 {
635 fc.dwFlags |= FLAG_OFFLINE;
636 }
637 break;
638 case L'T':
639 fc.dwFlags |= FLAG_T;
640 break;
641 case L'U':
642 fc.dwFlags |= FLAG_U;
643 break;
644 case L'W':
645 fc.dwFlags |= FLAG_W;
646 break;
647 case L'0': case L'1': case L'2': case L'3': case L'4':
648 case L'5': case L'6': case L'7': case L'8': case L'9':
649 fc.nnnn = wcstoul(&argv[i][1], &endptr, 10);
650 if (endptr == NULL || *endptr != 0)
651 return InvalidSwitch();
652 fc.dwFlags |= FLAG_nnnn;
653 break;
654 case L'?':
655 fc.dwFlags |= FLAG_HELP;
656 break;
657 default:
658 return InvalidSwitch();
659 }
660 }
661 return WildcardFileCompare(&fc);
662 }
663
664 #ifndef __REACTOS__
main(int argc,char ** argv)665 int main(int argc, char **argv)
666 {
667 INT my_argc;
668 LPWSTR *my_argv = CommandLineToArgvW(GetCommandLineW(), &my_argc);
669 INT ret = wmain(my_argc, my_argv);
670 LocalFree(my_argv);
671 return ret;
672 }
673 #endif
674