e9ec950bc2dd62e784b8ed0276d394dbe47e66ed
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)
17 // See also: https://stackoverflow.com/questions/33125766/compare-files-with-a-cmd
18 typedef enum FCRET
{ // return code of FC command
26 #define MAX_VIEW_SIZE (256 * 1024 * 1024) // 256 MB
28 #define MAX_VIEW_SIZE (64 * 1024 * 1024) // 64 MB
31 #define FLAG_A (1 << 0)
32 #define FLAG_B (1 << 1)
33 #define FLAG_C (1 << 2)
34 #define FLAG_L (1 << 3)
35 #define FLAG_LBn (1 << 4)
36 #define FLAG_N (1 << 5)
37 #define FLAG_OFFLINE (1 << 6)
38 #define FLAG_T (1 << 7)
39 #define FLAG_U (1 << 8)
40 #define FLAG_W (1 << 9)
41 #define FLAG_nnnn (1 << 10)
42 #define FLAG_HELP (1 << 11)
44 typedef struct FILECOMPARE
46 DWORD dwFlags
; // FLAG_...
51 static FCRET
NoDifference(VOID
)
53 ConResPuts(StdOut
, IDS_NO_DIFFERENCE
);
54 return FCRET_IDENTICAL
;
57 static FCRET
Different(LPCWSTR file1
, LPCWSTR file2
)
59 ConResPrintf(StdOut
, IDS_DIFFERENT
, file1
, file2
);
60 return FCRET_DIFFERENT
;
63 static FCRET
LongerThan(LPCWSTR file1
, LPCWSTR file2
)
65 ConResPrintf(StdOut
, IDS_LONGER_THAN
, file1
, file2
);
66 return FCRET_DIFFERENT
;
69 static FCRET
OutOfMemory(VOID
)
71 ConResPuts(StdErr
, IDS_OUT_OF_MEMORY
);
75 static FCRET
CannotRead(LPCWSTR file
)
77 ConResPrintf(StdErr
, IDS_CANNOT_READ
, file
);
81 static FCRET
InvalidSwitch(VOID
)
83 ConResPuts(StdErr
, IDS_INVALID_SWITCH
);
87 static HANDLE
DoOpenFileForInput(LPCWSTR file
)
89 HANDLE hFile
= CreateFileW(file
, GENERIC_READ
, FILE_SHARE_READ
, NULL
, OPEN_EXISTING
, 0, NULL
);
90 if (hFile
== INVALID_HANDLE_VALUE
)
92 ConResPrintf(StdErr
, IDS_CANNOT_OPEN
, file
);
97 static FCRET
BinaryFileCompare(FILECOMPARE
*pFC
)
100 HANDLE hFile1
, hFile2
, hMapping1
= NULL
, hMapping2
= NULL
;
101 LPBYTE pb1
= NULL
, pb2
= NULL
;
102 LARGE_INTEGER ib
, cb1
, cb2
, cbCommon
;
103 DWORD cbView
, ibView
;
104 BOOL fDifferent
= FALSE
;
106 hFile1
= DoOpenFileForInput(pFC
->file1
);
107 if (hFile1
== INVALID_HANDLE_VALUE
)
108 return FCRET_CANT_FIND
;
109 hFile2
= DoOpenFileForInput(pFC
->file2
);
110 if (hFile2
== INVALID_HANDLE_VALUE
)
113 return FCRET_CANT_FIND
;
118 if (_wcsicmp(pFC
->file1
, pFC
->file2
) == 0)
120 ret
= NoDifference();
123 if (!GetFileSizeEx(hFile1
, &cb1
))
125 ret
= CannotRead(pFC
->file1
);
128 if (!GetFileSizeEx(hFile2
, &cb2
))
130 ret
= CannotRead(pFC
->file2
);
133 cbCommon
.QuadPart
= min(cb1
.QuadPart
, cb2
.QuadPart
);
134 if (cbCommon
.QuadPart
> 0)
136 hMapping1
= CreateFileMappingW(hFile1
, NULL
, PAGE_READONLY
,
137 cb1
.HighPart
, cb1
.LowPart
, NULL
);
138 if (hMapping1
== NULL
)
140 ret
= CannotRead(pFC
->file1
);
143 hMapping2
= CreateFileMappingW(hFile2
, NULL
, PAGE_READONLY
,
144 cb2
.HighPart
, cb2
.LowPart
, NULL
);
145 if (hMapping2
== NULL
)
147 ret
= CannotRead(pFC
->file2
);
151 ret
= FCRET_IDENTICAL
;
152 for (ib
.QuadPart
= 0; ib
.QuadPart
< cbCommon
.QuadPart
; )
154 cbView
= (DWORD
)min(cbCommon
.QuadPart
- ib
.QuadPart
, MAX_VIEW_SIZE
);
155 pb1
= MapViewOfFile(hMapping1
, FILE_MAP_READ
, ib
.HighPart
, ib
.LowPart
, cbView
);
156 pb2
= MapViewOfFile(hMapping2
, FILE_MAP_READ
, ib
.HighPart
, ib
.LowPart
, cbView
);
162 for (ibView
= 0; ibView
< cbView
; ++ib
.QuadPart
, ++ibView
)
164 if (pb1
[ibView
] == pb2
[ibView
])
168 if (cbCommon
.QuadPart
> MAXDWORD
)
170 ConPrintf(StdOut
, L
"%016I64X: %02X %02X\n", ib
.QuadPart
,
171 pb1
[ibView
], pb2
[ibView
]);
175 ConPrintf(StdOut
, L
"%08lX: %02X %02X\n", ib
.LowPart
,
176 pb1
[ibView
], pb2
[ibView
]);
179 UnmapViewOfFile(pb1
);
180 UnmapViewOfFile(pb2
);
183 if (ret
!= FCRET_IDENTICAL
)
187 if (cb1
.QuadPart
< cb2
.QuadPart
)
188 ret
= LongerThan(pFC
->file2
, pFC
->file1
);
189 else if (cb1
.QuadPart
> cb2
.QuadPart
)
190 ret
= LongerThan(pFC
->file1
, pFC
->file2
);
192 ret
= Different(pFC
->file1
, pFC
->file2
);
194 ret
= NoDifference();
197 UnmapViewOfFile(pb1
);
198 UnmapViewOfFile(pb2
);
199 CloseHandle(hMapping1
);
200 CloseHandle(hMapping2
);
207 UnicodeTextCompare(FILECOMPARE
*pFC
, HANDLE hMapping1
, const LARGE_INTEGER
*pcb1
,
208 HANDLE hMapping2
, const LARGE_INTEGER
*pcb2
)
211 BOOL fIgnoreCase
= !!(pFC
->dwFlags
& FLAG_C
);
212 DWORD dwCmpFlags
= (fIgnoreCase
? NORM_IGNORECASE
: 0);
214 LARGE_INTEGER cch1
= { .QuadPart
= pcb1
->QuadPart
/ sizeof(WCHAR
) };
215 LARGE_INTEGER cch2
= { .QuadPart
= pcb1
->QuadPart
/ sizeof(WCHAR
) };
219 psz1
= MapViewOfFile(hMapping1
, FILE_MAP_READ
, 0, 0, pcb1
->LowPart
);
220 psz2
= MapViewOfFile(hMapping2
, FILE_MAP_READ
, 0, 0, pcb2
->LowPart
);
226 if (cch1
.QuadPart
< MAXLONG
&& cch2
.QuadPart
< MAXLONG
)
228 if (CompareStringW(0, dwCmpFlags
, psz1
, cch1
.LowPart
,
229 psz2
, cch2
.LowPart
) == CSTR_EQUAL
)
231 ret
= NoDifference();
235 // TODO: compare each lines
236 // TODO: large file support
237 ret
= Different(pFC
->file1
, pFC
->file2
);
240 UnmapViewOfFile(psz1
);
241 UnmapViewOfFile(psz2
);
246 AnsiTextCompare(FILECOMPARE
*pFC
, HANDLE hMapping1
, const LARGE_INTEGER
*pcb1
,
247 HANDLE hMapping2
, const LARGE_INTEGER
*pcb2
)
250 BOOL fIgnoreCase
= !!(pFC
->dwFlags
& FLAG_C
);
251 DWORD dwCmpFlags
= (fIgnoreCase
? NORM_IGNORECASE
: 0);
256 psz1
= MapViewOfFile(hMapping1
, FILE_MAP_READ
, 0, 0, pcb1
->LowPart
);
257 psz2
= MapViewOfFile(hMapping2
, FILE_MAP_READ
, 0, 0, pcb2
->LowPart
);
263 if (pcb1
->QuadPart
< MAXLONG
&& pcb2
->QuadPart
< MAXLONG
)
265 if (CompareStringA(0, dwCmpFlags
, psz1
, pcb1
->LowPart
,
266 psz2
, pcb2
->LowPart
) == CSTR_EQUAL
)
268 ret
= NoDifference();
272 // TODO: compare each lines
273 // TODO: large file support
274 ret
= Different(pFC
->file1
, pFC
->file2
);
277 UnmapViewOfFile(psz1
);
278 UnmapViewOfFile(psz2
);
282 static FCRET
TextFileCompare(FILECOMPARE
*pFC
)
285 HANDLE hFile1
, hFile2
, hMapping1
= NULL
, hMapping2
= NULL
;
286 LARGE_INTEGER cb1
, cb2
;
287 BOOL fUnicode
= !!(pFC
->dwFlags
& FLAG_U
);
289 hFile1
= DoOpenFileForInput(pFC
->file1
);
290 if (hFile1
== INVALID_HANDLE_VALUE
)
291 return FCRET_CANT_FIND
;
292 hFile2
= DoOpenFileForInput(pFC
->file2
);
293 if (hFile2
== INVALID_HANDLE_VALUE
)
296 return FCRET_CANT_FIND
;
301 if (_wcsicmp(pFC
->file1
, pFC
->file2
) == 0)
303 ret
= NoDifference();
306 if (!GetFileSizeEx(hFile1
, &cb1
))
308 ret
= CannotRead(pFC
->file1
);
311 if (!GetFileSizeEx(hFile2
, &cb2
))
313 ret
= CannotRead(pFC
->file2
);
316 if (cb1
.QuadPart
== 0 && cb2
.QuadPart
== 0)
318 ret
= NoDifference();
321 hMapping1
= CreateFileMappingW(hFile1
, NULL
, PAGE_READONLY
,
322 cb1
.HighPart
, cb1
.LowPart
, NULL
);
323 if (hMapping1
== NULL
)
325 ret
= CannotRead(pFC
->file1
);
328 hMapping2
= CreateFileMappingW(hFile2
, NULL
, PAGE_READONLY
,
329 cb2
.HighPart
, cb2
.LowPart
, NULL
);
330 if (hMapping2
== NULL
)
332 ret
= CannotRead(pFC
->file2
);
337 ret
= UnicodeTextCompare(pFC
, hMapping1
, &cb1
, hMapping2
, &cb2
);
339 ret
= AnsiTextCompare(pFC
, hMapping1
, &cb1
, hMapping2
, &cb2
);
342 CloseHandle(hMapping1
);
343 CloseHandle(hMapping2
);
349 static BOOL
IsBinaryExt(LPCWSTR filename
)
351 // Don't change this array. This is by design.
352 // See also: https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/fc
353 static const LPCWSTR s_exts
[] = { L
"EXE", L
"COM", L
"SYS", L
"OBJ", L
"LIB", L
"BIN" };
355 LPCWSTR pch
, ext
, pch1
= wcsrchr(filename
, L
'\\'), pch2
= wcsrchr(filename
, L
'/');
358 else if (!pch1
&& pch2
)
360 else if (pch1
&& !pch2
)
362 else if (pch1
< pch2
)
367 ext
= wcsrchr(pch
, L
'.');
371 for (iext
= 0; iext
< _countof(s_exts
); ++iext
)
373 if (_wcsicmp(ext
, s_exts
[iext
]) == 0)
380 #define HasWildcard(filename) \
381 ((wcschr((filename), L'*') != NULL) || (wcschr((filename), L'?') != NULL))
383 static FCRET
FileCompare(FILECOMPARE
*pFC
)
385 ConResPrintf(StdOut
, IDS_COMPARING
, pFC
->file1
, pFC
->file2
);
387 if (!(pFC
->dwFlags
& FLAG_L
) &&
388 ((pFC
->dwFlags
& FLAG_B
) || IsBinaryExt(pFC
->file1
) || IsBinaryExt(pFC
->file2
)))
390 return BinaryFileCompare(pFC
);
392 return TextFileCompare(pFC
);
395 static FCRET
WildcardFileCompare(FILECOMPARE
*pFC
)
397 if (pFC
->dwFlags
& FLAG_HELP
)
399 ConResPuts(StdOut
, IDS_USAGE
);
400 return FCRET_INVALID
;
403 if (!pFC
->file1
|| !pFC
->file2
)
405 ConResPuts(StdErr
, IDS_NEEDS_FILES
);
406 return FCRET_INVALID
;
409 if (HasWildcard(pFC
->file1
) || HasWildcard(pFC
->file2
))
412 ConResPuts(StdErr
, IDS_CANT_USE_WILDCARD
);
415 return FileCompare(pFC
);
418 int wmain(int argc
, WCHAR
**argv
)
420 FILECOMPARE fc
= { .dwFlags
= 0, .n
= 100, .nnnn
= 2 };
424 /* Initialize the Console Standard Streams */
427 for (i
= 1; i
< argc
; ++i
)
429 if (argv
[i
][0] != L
'/')
436 return InvalidSwitch();
439 switch (towupper(argv
[i
][1]))
442 fc
.dwFlags
|= FLAG_A
;
445 fc
.dwFlags
|= FLAG_B
;
448 fc
.dwFlags
|= FLAG_C
;
451 if (_wcsicmp(argv
[i
], L
"/L") == 0)
453 fc
.dwFlags
|= FLAG_L
;
455 else if (towupper(argv
[i
][2]) == L
'B')
457 if (iswdigit(argv
[i
][3]))
459 fc
.dwFlags
|= FLAG_LBn
;
460 fc
.n
= wcstoul(&argv
[i
][3], &endptr
, 10);
461 if (endptr
== NULL
|| *endptr
!= 0)
462 return InvalidSwitch();
466 return InvalidSwitch();
471 fc
.dwFlags
|= FLAG_N
;
474 if (_wcsicmp(argv
[i
], L
"/OFF") == 0 || _wcsicmp(argv
[i
], L
"/OFFLINE") == 0)
476 fc
.dwFlags
|= FLAG_OFFLINE
;
480 fc
.dwFlags
|= FLAG_T
;
483 fc
.dwFlags
|= FLAG_W
;
485 case L
'0': case L
'1': case L
'2': case L
'3': case L
'4':
486 case L
'5': case L
'6': case L
'7': case L
'8': case L
'9':
487 fc
.nnnn
= wcstoul(&argv
[i
][1], &endptr
, 10);
488 if (endptr
== NULL
|| *endptr
!= 0)
489 return InvalidSwitch();
490 fc
.dwFlags
|= FLAG_nnnn
;
493 fc
.dwFlags
|= FLAG_HELP
;
496 return InvalidSwitch();
499 return WildcardFileCompare(&fc
);