https://git.reactos.org/?p=reactos.git;a=commitdiff;h=d2c47132ad3d7c39e88e11...
commit d2c47132ad3d7c39e88e117c0273623726732015 Author: Katayama Hirofumi MZ katayama.hirofumi.mz@gmail.com AuthorDate: Tue May 4 18:05:57 2021 +0900 Commit: GitHub noreply@github.com CommitDate: Tue May 4 18:05:57 2021 +0900
[CMDUTILS][FC] Implement text file comparison (#3625)
Implement text file comparison by using file mappings (both Unicode and ANSI). CORE-17500 --- base/applications/cmdutils/fc/CMakeLists.txt | 6 +- base/applications/cmdutils/fc/fc.c | 381 ++++++++-------- base/applications/cmdutils/fc/fc.h | 97 +++++ base/applications/cmdutils/fc/text.h | 620 +++++++++++++++++++++++++++ base/applications/cmdutils/fc/texta.c | 6 + base/applications/cmdutils/fc/textw.c | 8 + 6 files changed, 910 insertions(+), 208 deletions(-)
diff --git a/base/applications/cmdutils/fc/CMakeLists.txt b/base/applications/cmdutils/fc/CMakeLists.txt index fec64446ae1..4da877ca403 100644 --- a/base/applications/cmdutils/fc/CMakeLists.txt +++ b/base/applications/cmdutils/fc/CMakeLists.txt @@ -1,7 +1,7 @@ include_directories(${REACTOS_SOURCE_DIR}/sdk/lib/conutils)
-add_executable(fc fc.c fc.rc) +add_executable(fc fc.c texta.c textw.c fc.rc) set_module_type(fc win32cui UNICODE) -target_link_libraries(fc conutils ${PSEH_LIB}) -add_importlibs(fc msvcrt user32 kernel32) +target_link_libraries(fc conutils wine ${PSEH_LIB}) +add_importlibs(fc msvcrt user32 kernel32 ntdll) add_cd_file(TARGET fc DESTINATION reactos/system32 FOR all) diff --git a/base/applications/cmdutils/fc/fc.c b/base/applications/cmdutils/fc/fc.c index e9ec950bc2d..9350bc1a957 100644 --- a/base/applications/cmdutils/fc/fc.c +++ b/base/applications/cmdutils/fc/fc.c @@ -4,87 +4,116 @@ * PURPOSE: Comparing files * COPYRIGHT: Copyright 2021 Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com) */ -#include <stdlib.h> -#include <string.h> -#include <ctype.h> -#include <windef.h> -#include <winbase.h> -#include <winuser.h> -#include <winnls.h> -#include <conutils.h> -#include "resource.h" - -// See also: https://stackoverflow.com/questions/33125766/compare-files-with-a-cmd -typedef enum FCRET { // return code of FC command - FCRET_INVALID = -1, - FCRET_IDENTICAL = 0, - FCRET_DIFFERENT = 1, - FCRET_CANT_FIND = 2 -} FCRET; - -#ifdef _WIN64 - #define MAX_VIEW_SIZE (256 * 1024 * 1024) // 256 MB +#include "fc.h" + +#ifdef __REACTOS__ + #include <conutils.h> #else - #define MAX_VIEW_SIZE (64 * 1024 * 1024) // 64 MB + #include <stdio.h> + #define ConInitStdStreams() /* empty */ + #define StdOut stdout + #define StdErr stderr + void ConPuts(FILE *fp, LPCWSTR psz) + { + fputws(psz, fp); + } + void ConPrintf(FILE *fp, LPCWSTR psz, ...) + { + va_list va; + va_start(va, psz); + vfwprintf(fp, psz, va); + va_end(va); + } + void ConResPuts(FILE *fp, UINT nID) + { + WCHAR sz[MAX_PATH]; + LoadStringW(NULL, nID, sz, _countof(sz)); + fputws(sz, fp); + } + void ConResPrintf(FILE *fp, UINT nID, ...) + { + va_list va; + WCHAR sz[MAX_PATH]; + va_start(va, nID); + LoadStringW(NULL, nID, sz, _countof(sz)); + vfwprintf(fp, sz, va); + va_end(va); + } #endif
-#define FLAG_A (1 << 0) -#define FLAG_B (1 << 1) -#define FLAG_C (1 << 2) -#define FLAG_L (1 << 3) -#define FLAG_LBn (1 << 4) -#define FLAG_N (1 << 5) -#define FLAG_OFFLINE (1 << 6) -#define FLAG_T (1 << 7) -#define FLAG_U (1 << 8) -#define FLAG_W (1 << 9) -#define FLAG_nnnn (1 << 10) -#define FLAG_HELP (1 << 11) - -typedef struct FILECOMPARE -{ - DWORD dwFlags; // FLAG_... - INT n, nnnn; - LPCWSTR file1, file2; -} FILECOMPARE; - -static FCRET NoDifference(VOID) +FCRET NoDifference(VOID) { ConResPuts(StdOut, IDS_NO_DIFFERENCE); return FCRET_IDENTICAL; }
-static FCRET Different(LPCWSTR file1, LPCWSTR file2) +FCRET Different(LPCWSTR file0, LPCWSTR file1) { - ConResPrintf(StdOut, IDS_DIFFERENT, file1, file2); + ConResPrintf(StdOut, IDS_DIFFERENT, file0, file1); return FCRET_DIFFERENT; }
-static FCRET LongerThan(LPCWSTR file1, LPCWSTR file2) +FCRET LongerThan(LPCWSTR file0, LPCWSTR file1) { - ConResPrintf(StdOut, IDS_LONGER_THAN, file1, file2); + ConResPrintf(StdOut, IDS_LONGER_THAN, file0, file1); return FCRET_DIFFERENT; }
-static FCRET OutOfMemory(VOID) +FCRET OutOfMemory(VOID) { ConResPuts(StdErr, IDS_OUT_OF_MEMORY); return FCRET_INVALID; }
-static FCRET CannotRead(LPCWSTR file) +FCRET CannotRead(LPCWSTR file) { ConResPrintf(StdErr, IDS_CANNOT_READ, file); return FCRET_INVALID; }
-static FCRET InvalidSwitch(VOID) +FCRET InvalidSwitch(VOID) { ConResPuts(StdErr, IDS_INVALID_SWITCH); return FCRET_INVALID; }
-static HANDLE DoOpenFileForInput(LPCWSTR file) +FCRET ResyncFailed(VOID) +{ + ConResPuts(StdOut, IDS_RESYNC_FAILED); + return FCRET_DIFFERENT; +} + +VOID PrintCaption(LPCWSTR file) +{ + ConPrintf(StdOut, L"***** %ls\n", file); +} + +VOID PrintEndOfDiff(VOID) +{ + ConPuts(StdOut, L"*****\n\n"); +} + +VOID PrintDots(VOID) +{ + ConPuts(StdOut, L"...\n"); +} + +VOID PrintLineW(const FILECOMPARE *pFC, DWORD lineno, LPCWSTR psz) +{ + if (pFC->dwFlags & FLAG_N) + ConPrintf(StdOut, L"%5d: %ls\n", lineno, psz); + else + ConPrintf(StdOut, L"%ls\n", psz); +} +VOID PrintLineA(const FILECOMPARE *pFC, DWORD lineno, LPCSTR psz) +{ + if (pFC->dwFlags & FLAG_N) + ConPrintf(StdOut, L"%5d: %hs\n", lineno, psz); + else + ConPrintf(StdOut, L"%hs\n", psz); +} + +HANDLE DoOpenFileForInput(LPCWSTR file) { HANDLE hFile = CreateFileW(file, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); if (hFile == INVALID_HANDLE_VALUE) @@ -97,54 +126,54 @@ static HANDLE DoOpenFileForInput(LPCWSTR file) static FCRET BinaryFileCompare(FILECOMPARE *pFC) { FCRET ret; - HANDLE hFile1, hFile2, hMapping1 = NULL, hMapping2 = NULL; - LPBYTE pb1 = NULL, pb2 = NULL; - LARGE_INTEGER ib, cb1, cb2, cbCommon; + HANDLE hFile0, hFile1, hMapping0 = NULL, hMapping1 = NULL; + LPBYTE pb0 = NULL, pb1 = NULL; + LARGE_INTEGER ib, cb0, cb1, cbCommon; DWORD cbView, ibView; BOOL fDifferent = FALSE;
- hFile1 = DoOpenFileForInput(pFC->file1); - if (hFile1 == INVALID_HANDLE_VALUE) + hFile0 = DoOpenFileForInput(pFC->file[0]); + if (hFile0 == INVALID_HANDLE_VALUE) return FCRET_CANT_FIND; - hFile2 = DoOpenFileForInput(pFC->file2); - if (hFile2 == INVALID_HANDLE_VALUE) + hFile1 = DoOpenFileForInput(pFC->file[1]); + if (hFile1 == INVALID_HANDLE_VALUE) { - CloseHandle(hFile1); + CloseHandle(hFile0); return FCRET_CANT_FIND; }
do { - if (_wcsicmp(pFC->file1, pFC->file2) == 0) + if (_wcsicmp(pFC->file[0], pFC->file[1]) == 0) { ret = NoDifference(); break; } - if (!GetFileSizeEx(hFile1, &cb1)) + if (!GetFileSizeEx(hFile0, &cb0)) { - ret = CannotRead(pFC->file1); + ret = CannotRead(pFC->file[0]); break; } - if (!GetFileSizeEx(hFile2, &cb2)) + if (!GetFileSizeEx(hFile1, &cb1)) { - ret = CannotRead(pFC->file2); + ret = CannotRead(pFC->file[1]); break; } - cbCommon.QuadPart = min(cb1.QuadPart, cb2.QuadPart); + cbCommon.QuadPart = min(cb0.QuadPart, cb1.QuadPart); if (cbCommon.QuadPart > 0) { - hMapping1 = CreateFileMappingW(hFile1, NULL, PAGE_READONLY, - cb1.HighPart, cb1.LowPart, NULL); - if (hMapping1 == NULL) + hMapping0 = CreateFileMappingW(hFile0, NULL, PAGE_READONLY, + cb0.HighPart, cb0.LowPart, NULL); + if (hMapping0 == NULL) { - ret = CannotRead(pFC->file1); + ret = CannotRead(pFC->file[0]); break; } - hMapping2 = CreateFileMappingW(hFile2, NULL, PAGE_READONLY, - cb2.HighPart, cb2.LowPart, NULL); - if (hMapping2 == NULL) + hMapping1 = CreateFileMappingW(hFile1, NULL, PAGE_READONLY, + cb1.HighPart, cb1.LowPart, NULL); + if (hMapping1 == NULL) { - ret = CannotRead(pFC->file2); + ret = CannotRead(pFC->file[1]); break; }
@@ -152,197 +181,121 @@ static FCRET BinaryFileCompare(FILECOMPARE *pFC) for (ib.QuadPart = 0; ib.QuadPart < cbCommon.QuadPart; ) { cbView = (DWORD)min(cbCommon.QuadPart - ib.QuadPart, MAX_VIEW_SIZE); + pb0 = MapViewOfFile(hMapping0, FILE_MAP_READ, ib.HighPart, ib.LowPart, cbView); pb1 = MapViewOfFile(hMapping1, FILE_MAP_READ, ib.HighPart, ib.LowPart, cbView); - pb2 = MapViewOfFile(hMapping2, FILE_MAP_READ, ib.HighPart, ib.LowPart, cbView); - if (!pb1 || !pb2) + if (!pb0 || !pb1) { ret = OutOfMemory(); break; } for (ibView = 0; ibView < cbView; ++ib.QuadPart, ++ibView) { - if (pb1[ibView] == pb2[ibView]) + if (pb0[ibView] == pb1[ibView]) continue;
fDifferent = TRUE; if (cbCommon.QuadPart > MAXDWORD) { ConPrintf(StdOut, L"%016I64X: %02X %02X\n", ib.QuadPart, - pb1[ibView], pb2[ibView]); + pb0[ibView], pb1[ibView]); } else { ConPrintf(StdOut, L"%08lX: %02X %02X\n", ib.LowPart, - pb1[ibView], pb2[ibView]); + pb0[ibView], pb1[ibView]); } } + UnmapViewOfFile(pb0); UnmapViewOfFile(pb1); - UnmapViewOfFile(pb2); - pb1 = pb2 = NULL; + pb0 = pb1 = NULL; } if (ret != FCRET_IDENTICAL) break; }
- if (cb1.QuadPart < cb2.QuadPart) - ret = LongerThan(pFC->file2, pFC->file1); - else if (cb1.QuadPart > cb2.QuadPart) - ret = LongerThan(pFC->file1, pFC->file2); + if (cb0.QuadPart < cb1.QuadPart) + ret = LongerThan(pFC->file[1], pFC->file[0]); + else if (cb0.QuadPart > cb1.QuadPart) + ret = LongerThan(pFC->file[0], pFC->file[1]); else if (fDifferent) - ret = Different(pFC->file1, pFC->file2); + ret = Different(pFC->file[0], pFC->file[1]); else ret = NoDifference(); } while (0);
+ UnmapViewOfFile(pb0); UnmapViewOfFile(pb1); - UnmapViewOfFile(pb2); + CloseHandle(hMapping0); CloseHandle(hMapping1); - CloseHandle(hMapping2); + CloseHandle(hFile0); CloseHandle(hFile1); - CloseHandle(hFile2); - return ret; -} - -static FCRET -UnicodeTextCompare(FILECOMPARE *pFC, HANDLE hMapping1, const LARGE_INTEGER *pcb1, - HANDLE hMapping2, const LARGE_INTEGER *pcb2) -{ - FCRET ret; - BOOL fIgnoreCase = !!(pFC->dwFlags & FLAG_C); - DWORD dwCmpFlags = (fIgnoreCase ? NORM_IGNORECASE : 0); - LPCWSTR psz1, psz2; - LARGE_INTEGER cch1 = { .QuadPart = pcb1->QuadPart / sizeof(WCHAR) }; - LARGE_INTEGER cch2 = { .QuadPart = pcb1->QuadPart / sizeof(WCHAR) }; - - do - { - psz1 = MapViewOfFile(hMapping1, FILE_MAP_READ, 0, 0, pcb1->LowPart); - psz2 = MapViewOfFile(hMapping2, FILE_MAP_READ, 0, 0, pcb2->LowPart); - if (!psz1 || !psz2) - { - ret = OutOfMemory(); - break; - } - if (cch1.QuadPart < MAXLONG && cch2.QuadPart < MAXLONG) - { - if (CompareStringW(0, dwCmpFlags, psz1, cch1.LowPart, - psz2, cch2.LowPart) == CSTR_EQUAL) - { - ret = NoDifference(); - break; - } - } - // TODO: compare each lines - // TODO: large file support - ret = Different(pFC->file1, pFC->file2); - } while (0); - - UnmapViewOfFile(psz1); - UnmapViewOfFile(psz2); - return ret; -} - -static FCRET -AnsiTextCompare(FILECOMPARE *pFC, HANDLE hMapping1, const LARGE_INTEGER *pcb1, - HANDLE hMapping2, const LARGE_INTEGER *pcb2) -{ - FCRET ret; - BOOL fIgnoreCase = !!(pFC->dwFlags & FLAG_C); - DWORD dwCmpFlags = (fIgnoreCase ? NORM_IGNORECASE : 0); - LPSTR psz1, psz2; - - do - { - psz1 = MapViewOfFile(hMapping1, FILE_MAP_READ, 0, 0, pcb1->LowPart); - psz2 = MapViewOfFile(hMapping2, FILE_MAP_READ, 0, 0, pcb2->LowPart); - if (!psz1 || !psz2) - { - ret = OutOfMemory(); - break; - } - if (pcb1->QuadPart < MAXLONG && pcb2->QuadPart < MAXLONG) - { - if (CompareStringA(0, dwCmpFlags, psz1, pcb1->LowPart, - psz2, pcb2->LowPart) == CSTR_EQUAL) - { - ret = NoDifference(); - break; - } - } - // TODO: compare each lines - // TODO: large file support - ret = Different(pFC->file1, pFC->file2); - } while (0); - - UnmapViewOfFile(psz1); - UnmapViewOfFile(psz2); return ret; }
static FCRET TextFileCompare(FILECOMPARE *pFC) { FCRET ret; - HANDLE hFile1, hFile2, hMapping1 = NULL, hMapping2 = NULL; - LARGE_INTEGER cb1, cb2; + HANDLE hFile0, hFile1, hMapping0 = NULL, hMapping1 = NULL; + LARGE_INTEGER cb0, cb1; BOOL fUnicode = !!(pFC->dwFlags & FLAG_U);
- hFile1 = DoOpenFileForInput(pFC->file1); - if (hFile1 == INVALID_HANDLE_VALUE) + hFile0 = DoOpenFileForInput(pFC->file[0]); + if (hFile0 == INVALID_HANDLE_VALUE) return FCRET_CANT_FIND; - hFile2 = DoOpenFileForInput(pFC->file2); - if (hFile2 == INVALID_HANDLE_VALUE) + hFile1 = DoOpenFileForInput(pFC->file[1]); + if (hFile1 == INVALID_HANDLE_VALUE) { - CloseHandle(hFile1); + CloseHandle(hFile0); return FCRET_CANT_FIND; }
do { - if (_wcsicmp(pFC->file1, pFC->file2) == 0) + if (_wcsicmp(pFC->file[0], pFC->file[1]) == 0) { ret = NoDifference(); break; } - if (!GetFileSizeEx(hFile1, &cb1)) + if (!GetFileSizeEx(hFile0, &cb0)) { - ret = CannotRead(pFC->file1); + ret = CannotRead(pFC->file[0]); break; } - if (!GetFileSizeEx(hFile2, &cb2)) + if (!GetFileSizeEx(hFile1, &cb1)) { - ret = CannotRead(pFC->file2); + ret = CannotRead(pFC->file[1]); break; } - if (cb1.QuadPart == 0 && cb2.QuadPart == 0) + if (cb0.QuadPart == 0 && cb1.QuadPart == 0) { ret = NoDifference(); break; } - hMapping1 = CreateFileMappingW(hFile1, NULL, PAGE_READONLY, - cb1.HighPart, cb1.LowPart, NULL); - if (hMapping1 == NULL) + hMapping0 = CreateFileMappingW(hFile0, NULL, PAGE_READONLY, + cb0.HighPart, cb0.LowPart, NULL); + if (hMapping0 == NULL) { - ret = CannotRead(pFC->file1); + ret = CannotRead(pFC->file[0]); break; } - hMapping2 = CreateFileMappingW(hFile2, NULL, PAGE_READONLY, - cb2.HighPart, cb2.LowPart, NULL); - if (hMapping2 == NULL) + hMapping1 = CreateFileMappingW(hFile1, NULL, PAGE_READONLY, + cb1.HighPart, cb1.LowPart, NULL); + if (hMapping1 == NULL) { - ret = CannotRead(pFC->file2); + ret = CannotRead(pFC->file[1]); break; }
if (fUnicode) - ret = UnicodeTextCompare(pFC, hMapping1, &cb1, hMapping2, &cb2); + ret = TextCompareW(pFC, &hMapping0, &cb0, &hMapping1, &cb1); else - ret = AnsiTextCompare(pFC, hMapping1, &cb1, hMapping2, &cb2); + ret = TextCompareA(pFC, &hMapping0, &cb0, &hMapping1, &cb1); } while (0);
+ CloseHandle(hMapping0); CloseHandle(hMapping1); - CloseHandle(hMapping2); + CloseHandle(hFile0); CloseHandle(hFile1); - CloseHandle(hFile2); return ret; }
@@ -352,17 +305,17 @@ static BOOL IsBinaryExt(LPCWSTR filename) // See also: https://docs.microsoft.com/en-us/windows-server/administration/windows-comma... static const LPCWSTR s_exts[] = { L"EXE", L"COM", L"SYS", L"OBJ", L"LIB", L"BIN" }; size_t iext; - LPCWSTR pch, ext, pch1 = wcsrchr(filename, L'\'), pch2 = wcsrchr(filename, L'/'); - if (!pch1 && !pch2) + LPCWSTR pch, ext, pch0 = wcsrchr(filename, L'\'), pch1 = wcsrchr(filename, L'/'); + if (!pch0 && !pch1) pch = filename; - else if (!pch1 && pch2) - pch = pch2; - else if (pch1 && !pch2) + else if (!pch0 && pch1) pch = pch1; - else if (pch1 < pch2) - pch = pch2; - else + else if (pch0 && !pch1) + pch = pch0; + else if (pch0 < pch1) pch = pch1; + else + pch = pch0;
ext = wcsrchr(pch, L'.'); if (ext) @@ -382,10 +335,10 @@ static BOOL IsBinaryExt(LPCWSTR filename)
static FCRET FileCompare(FILECOMPARE *pFC) { - ConResPrintf(StdOut, IDS_COMPARING, pFC->file1, pFC->file2); + ConResPrintf(StdOut, IDS_COMPARING, pFC->file[0], pFC->file[1]);
if (!(pFC->dwFlags & FLAG_L) && - ((pFC->dwFlags & FLAG_B) || IsBinaryExt(pFC->file1) || IsBinaryExt(pFC->file2))) + ((pFC->dwFlags & FLAG_B) || IsBinaryExt(pFC->file[0]) || IsBinaryExt(pFC->file[1]))) { return BinaryFileCompare(pFC); } @@ -394,25 +347,29 @@ static FCRET FileCompare(FILECOMPARE *pFC)
static FCRET WildcardFileCompare(FILECOMPARE *pFC) { + FCRET ret; + if (pFC->dwFlags & FLAG_HELP) { ConResPuts(StdOut, IDS_USAGE); return FCRET_INVALID; }
- if (!pFC->file1 || !pFC->file2) + if (!pFC->file[0] || !pFC->file[1]) { ConResPuts(StdErr, IDS_NEEDS_FILES); return FCRET_INVALID; }
- if (HasWildcard(pFC->file1) || HasWildcard(pFC->file2)) + if (HasWildcard(pFC->file[0]) || HasWildcard(pFC->file[1])) { // TODO: wildcard ConResPuts(StdErr, IDS_CANT_USE_WILDCARD); }
- return FileCompare(pFC); + ret = FileCompare(pFC); + ConPuts(StdOut, L"\n"); + return ret; }
int wmain(int argc, WCHAR **argv) @@ -428,10 +385,10 @@ int wmain(int argc, WCHAR **argv) { if (argv[i][0] != L'/') { - if (!fc.file1) - fc.file1 = argv[i]; - else if (!fc.file2) - fc.file2 = argv[i]; + if (!fc.file[0]) + fc.file[0] = argv[i]; + else if (!fc.file[1]) + fc.file[1] = argv[i]; else return InvalidSwitch(); continue; @@ -479,6 +436,9 @@ int wmain(int argc, WCHAR **argv) case L'T': fc.dwFlags |= FLAG_T; break; + case L'U': + fc.dwFlags |= FLAG_U; + break; case L'W': fc.dwFlags |= FLAG_W; break; @@ -498,3 +458,14 @@ int wmain(int argc, WCHAR **argv) } return WildcardFileCompare(&fc); } + +#ifndef __REACTOS__ +int main(int argc, char **argv) +{ + INT my_argc; + LPWSTR *my_argv = CommandLineToArgvW(GetCommandLineW(), &my_argc); + INT ret = wmain(my_argc, my_argv); + LocalFree(my_argv); + return ret; +} +#endif diff --git a/base/applications/cmdutils/fc/fc.h b/base/applications/cmdutils/fc/fc.h new file mode 100644 index 00000000000..3d8772e78c7 --- /dev/null +++ b/base/applications/cmdutils/fc/fc.h @@ -0,0 +1,97 @@ +/* + * PROJECT: ReactOS FC Command + * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) + * PURPOSE: Comparing files + * COPYRIGHT: Copyright 2021 Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com) + */ +#pragma once +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#ifdef __REACTOS__ + #include <windef.h> + #include <winbase.h> + #include <winuser.h> + #include <winnls.h> +#else + #include <windows.h> +#endif +#include <wine/list.h> +#include "resource.h" + +// See also: https://stackoverflow.com/questions/33125766/compare-files-with-a-cmd +typedef enum FCRET // return code of FC command +{ + FCRET_INVALID = -1, + FCRET_IDENTICAL = 0, + FCRET_DIFFERENT = 1, + FCRET_CANT_FIND = 2, + FCRET_NO_MORE_DATA = 3 // (extension) +} FCRET; + +typedef struct NODE_W +{ + struct list entry; + LPWSTR pszLine; + LPWSTR pszComp; // compressed + DWORD lineno; + DWORD hash; +} NODE_W; +typedef struct NODE_A +{ + struct list entry; + LPSTR pszLine; + LPSTR pszComp; // compressed + DWORD lineno; + DWORD hash; +} NODE_A; + +#define FLAG_A (1 << 0) // abbreviation +#define FLAG_B (1 << 1) // binary +#define FLAG_C (1 << 2) // ignore cases +#define FLAG_L (1 << 3) // ASCII mode +#define FLAG_LBn (1 << 4) // line buffers +#define FLAG_N (1 << 5) // show line numbers +#define FLAG_OFFLINE (1 << 6) // ??? +#define FLAG_T (1 << 7) // prevent fc from converting tabs to spaces +#define FLAG_U (1 << 8) // Unicode +#define FLAG_W (1 << 9) // compress white space +#define FLAG_nnnn (1 << 10) // ??? +#define FLAG_HELP (1 << 11) // show usage + +typedef struct FILECOMPARE +{ + DWORD dwFlags; // FLAG_... + INT n; // # of line buffers + INT nnnn; // retry count before resynch + LPCWSTR file[2]; + struct list list[2]; +} FILECOMPARE; + +// text.h +FCRET TextCompareW(FILECOMPARE *pFC, + HANDLE *phMapping0, const LARGE_INTEGER *pcb0, + HANDLE *phMapping1, const LARGE_INTEGER *pcb1); +FCRET TextCompareA(FILECOMPARE *pFC, + HANDLE *phMapping0, const LARGE_INTEGER *pcb0, + HANDLE *phMapping1, const LARGE_INTEGER *pcb1); +// fc.c +VOID PrintLineW(const FILECOMPARE *pFC, DWORD lineno, LPCWSTR psz); +VOID PrintLineA(const FILECOMPARE *pFC, DWORD lineno, LPCSTR psz); +VOID PrintCaption(LPCWSTR file); +VOID PrintEndOfDiff(VOID); +VOID PrintDots(VOID); +FCRET NoDifference(VOID); +FCRET Different(LPCWSTR file0, LPCWSTR file1); +FCRET LongerThan(LPCWSTR file0, LPCWSTR file1); +FCRET OutOfMemory(VOID); +FCRET CannotRead(LPCWSTR file); +FCRET InvalidSwitch(VOID); +FCRET ResyncFailed(VOID); +HANDLE DoOpenFileForInput(LPCWSTR file); + +#ifdef _WIN64 + #define MAX_VIEW_SIZE (256 * 1024 * 1024) // 256 MB +#else + #define MAX_VIEW_SIZE (64 * 1024 * 1024) // 64 MB +#endif diff --git a/base/applications/cmdutils/fc/text.h b/base/applications/cmdutils/fc/text.h new file mode 100644 index 00000000000..d0f2363f6f9 --- /dev/null +++ b/base/applications/cmdutils/fc/text.h @@ -0,0 +1,620 @@ +/* + * PROJECT: ReactOS FC Command + * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) + * PURPOSE: Comparing text files + * COPYRIGHT: Copyright 2021 Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com) + */ +#include "fc.h" +#include <stdio.h> + +#ifdef __REACTOS__ + #include <wine/debug.h> + WINE_DEFAULT_DEBUG_CHANNEL(fc); +#else + #define ERR /*empty*/ + #define WARN /*empty*/ + #define TRACE /*empty*/ +#endif + +#define IS_SPACE(ch) ((ch) == TEXT(' ') || (ch) == TEXT('\t')) + +#ifdef UNICODE + #define NODE NODE_W + #define PrintLine PrintLineW + #define TextCompare TextCompareW +#else + #define NODE NODE_A + #define PrintLine PrintLineA + #define TextCompare TextCompareA +#endif + +static LPTSTR AllocLine(LPCTSTR pch, DWORD cch) +{ + LPTSTR pszNew = malloc((cch + 1) * sizeof(TCHAR)); + if (!pszNew) + return NULL; + memcpy(pszNew, pch, cch * sizeof(TCHAR)); + pszNew[cch] = 0; + return pszNew; +} + +static NODE *AllocNode(LPTSTR psz, DWORD lineno) +{ + NODE *node; + if (!psz) + return NULL; + node = calloc(1, sizeof(NODE)); + if (!node) + { + free(psz); + return NULL; + } + node->pszLine = psz; + node->lineno = lineno; + return node; +} + +static __inline VOID DeleteNode(NODE *node) +{ + if (node) + { + free(node->pszLine); + free(node->pszComp); + free(node); + } +} + +static VOID DeleteList(struct list *list) +{ + struct list *ptr; + NODE *node; + while ((ptr = list_head(list)) != NULL) + { + list_remove(ptr); + node = LIST_ENTRY(ptr, NODE, entry); + DeleteNode(node); + } +} + +static __inline LPCTSTR SkipSpace(LPCTSTR pch) +{ + while (IS_SPACE(*pch)) + ++pch; + return pch; +} + +static __inline LPCTSTR FindLastNonSpace(LPCTSTR pch) +{ + LPCTSTR pchLast = NULL; + while (*pch) + { + if (!IS_SPACE(*pch)) + pchLast = pch; + ++pch; + } + return pchLast; +} + +static VOID DeleteDuplicateSpaces(LPTSTR psz) +{ + LPTSTR pch0, pch1; + for (pch0 = pch1 = psz; *pch0; ++pch0) + { + *pch1++ = *pch0; + if (IS_SPACE(*pch0)) + { + do + { + ++pch0; + } while (IS_SPACE(*pch0)); + --pch0; + } + } + *pch1 = 0; +} + +static LPTSTR CompressSpace(LPCTSTR line) +{ + LPTSTR pszNew; + LPCTSTR pchLast; + + line = SkipSpace(line); + pchLast = FindLastNonSpace(line); + if (pchLast == NULL) + return AllocLine(NULL, 0); + + pszNew = AllocLine(line, (DWORD)(pchLast - line) + 1); + if (!pszNew) + return NULL; + + DeleteDuplicateSpaces(pszNew); + return pszNew; +} + +#define TAB_WIDTH 8 + +static INT ExpandTabLength(LPCTSTR line) +{ + LPCTSTR pch; + INT cch = 0; + for (pch = line; *pch; ++pch) + { + if (*pch == TEXT('\t')) + cch += TAB_WIDTH - (cch % TAB_WIDTH); + else + ++cch; + } + return cch; +} + +static LPTSTR ExpandTab(LPCTSTR line) +{ + INT spaces, cch = ExpandTabLength(line), ich; + LPTSTR pszNew = malloc((cch + 1) * sizeof(TCHAR)); + LPCTSTR pch; + if (!pszNew) + return NULL; + ich = 0; + for (pch = line; *pch; ++pch) + { + if (*pch == TEXT('\t')) + { + spaces = TAB_WIDTH - (ich % TAB_WIDTH); + while (spaces-- > 0) + { + pszNew[ich++] = TEXT(' '); + } + } + else + { + pszNew[ich++] = *pch; + } + } + pszNew[ich] = 0; + return pszNew; +} + +#define HASH_EOF 0xFFFFFFFF +#define HASH_MASK 0x7FFFFFFF + +static DWORD GetHash(LPCTSTR psz, BOOL bIgnoreCase) +{ + DWORD ret = 0xDEADFACE; + while (*psz) + { + ret += (bIgnoreCase ? towupper(*psz) : *psz); + ret <<= 2; + ++psz; + } + return (ret & HASH_MASK); +} + +static NODE *AllocEOFNode(DWORD lineno) +{ + NODE *node = AllocNode(AllocLine(NULL, 0), 0); + if (node == NULL) + return NULL; + node->pszComp = AllocLine(NULL, 0); + if (node->pszComp == NULL) + { + DeleteNode(node); + return NULL; + } + node->lineno = lineno; + node->hash = HASH_EOF; + return node; +} + +static __inline BOOL IsEOFNode(NODE *node) +{ + return !node || node->hash == HASH_EOF; +} + +static BOOL ConvertNode(const FILECOMPARE *pFC, NODE *node) +{ + if (!(pFC->dwFlags & FLAG_T)) + { + LPTSTR tmp = ExpandTab(node->pszLine); + if (!tmp) + return FALSE; + free(node->pszLine); + node->pszLine = tmp; + if (!(pFC->dwFlags & FLAG_W)) + node->hash = GetHash(node->pszLine, !!(pFC->dwFlags & FLAG_C)); + } + if (pFC->dwFlags & FLAG_W) + { + node->pszComp = CompressSpace(node->pszLine); + if (!node->pszComp) + return FALSE; + node->hash = GetHash(node->pszComp, !!(pFC->dwFlags & FLAG_C)); + } + return TRUE; +} + +static FCRET CompareNode(const FILECOMPARE *pFC, const NODE *node0, const NODE *node1) +{ + DWORD dwCmpFlags; + LPTSTR psz0, psz1; + INT ret; + if (node0->hash != node1->hash) + return FCRET_DIFFERENT; + + psz0 = (pFC->dwFlags & FLAG_W) ? node0->pszComp : node0->pszLine; + psz1 = (pFC->dwFlags & FLAG_W) ? node1->pszComp : node1->pszLine; + dwCmpFlags = ((pFC->dwFlags & FLAG_C) ? NORM_IGNORECASE : 0); + ret = CompareString(LOCALE_USER_DEFAULT, dwCmpFlags, psz0, -1, psz1, -1); + return (ret == CSTR_EQUAL) ? FCRET_IDENTICAL : FCRET_DIFFERENT; +} + +static BOOL FindNextLine(LPCTSTR pch, DWORD ich, DWORD cch, LPDWORD pich) +{ + while (ich < cch) + { + if (pch[ich] == TEXT('\n') || pch[ich] == TEXT('\0')) + { + *pich = ich; + return TRUE; + } + ++ich; + } + *pich = cch; + return FALSE; +} + +static FCRET +ParseLines(const FILECOMPARE *pFC, HANDLE *phMapping, + LARGE_INTEGER *pib, const LARGE_INTEGER *pcb, struct list *list) +{ + DWORD lineno = 1, ich, cch, ichNext, cbView, cchNode; + LPTSTR psz, pszLine; + BOOL fLast, bCR; + NODE *node; + + if (*phMapping == NULL) + return FCRET_NO_MORE_DATA; + + if (pib->QuadPart >= pcb->QuadPart) + { + CloseHandle(*phMapping); + *phMapping = NULL; + return FCRET_NO_MORE_DATA; + } + + cbView = (DWORD)min(pcb->QuadPart - pib->QuadPart, MAX_VIEW_SIZE); + psz = MapViewOfFile(*phMapping, FILE_MAP_READ, pib->HighPart, pib->LowPart, cbView); + if (!psz) + { + return OutOfMemory(); + } + + ich = 0; + cch = cbView / sizeof(TCHAR); + fLast = (pib->QuadPart + cbView >= pcb->QuadPart); + while (ich < cch && + (FindNextLine(psz, ich, cch, &ichNext) || + (ichNext == cch && (fLast || ich == 0)))) + { + bCR = (ichNext > 0) && (psz[ichNext - 1] == TEXT('\r')); + cchNode = ichNext - ich - bCR; + TRACE("ich:%ld, cch:%ld, ichNext:%ld, cchNode:%ld\n", ich, cch, ichNext, cchNode); + pszLine = AllocLine(&psz[ich], cchNode); + node = AllocNode(pszLine, lineno++); + if (!node || !ConvertNode(pFC, node)) + { + DeleteNode(node); + UnmapViewOfFile(psz); + return OutOfMemory(); + } + list_add_tail(list, &node->entry); + ich = ichNext + 1; + } + + UnmapViewOfFile(psz); + + pib->QuadPart += ichNext * sizeof(WCHAR); + + if (pib->QuadPart < pcb->QuadPart) + return FCRET_IDENTICAL; + + // append EOF node + node = AllocEOFNode(lineno); + if (!node) + return OutOfMemory(); + list_add_tail(list, &node->entry); + + return FCRET_NO_MORE_DATA; +} + +static VOID +ShowDiff(FILECOMPARE *pFC, INT i, struct list *begin, struct list *end) +{ + NODE* node; + struct list *list = &pFC->list[i]; + struct list *first = NULL, *last = NULL; + PrintCaption(pFC->file[i]); + if (begin && end && list_prev(list, begin)) + begin = list_prev(list, begin); + while (begin != end) + { + node = LIST_ENTRY(begin, NODE, entry); + if (IsEOFNode(node)) + break; + if (!first) + first = begin; + last = begin; + if (!(pFC->dwFlags & FLAG_A)) + PrintLine(pFC, node->lineno, node->pszLine); + begin = list_next(list, begin); + } + if ((pFC->dwFlags & FLAG_A) && first) + { + node = LIST_ENTRY(first, NODE, entry); + PrintLine(pFC, node->lineno, node->pszLine); + first = list_next(list, first); + if (first != last) + { + if (list_next(list, first) == last) + { + node = LIST_ENTRY(first, NODE, entry); + PrintLine(pFC, node->lineno, node->pszLine); + } + else + { + PrintDots(); + } + } + node = LIST_ENTRY(last, NODE, entry); + PrintLine(pFC, node->lineno, node->pszLine); + } +} + +static VOID +SkipIdentical(FILECOMPARE *pFC, struct list **pptr0, struct list **pptr1) +{ + struct list *ptr0 = *pptr0, *ptr1 = *pptr1; + while (ptr0 && ptr1) + { + NODE *node0 = LIST_ENTRY(ptr0, NODE, entry); + NODE *node1 = LIST_ENTRY(ptr1, NODE, entry); + if (CompareNode(pFC, node0, node1) != FCRET_IDENTICAL) + break; + ptr0 = list_next(&pFC->list[0], ptr0); + ptr1 = list_next(&pFC->list[1], ptr1); + } + *pptr0 = ptr0; + *pptr1 = ptr1; +} + +static DWORD +SkipIdenticalN(FILECOMPARE *pFC, struct list **pptr0, struct list **pptr1, + DWORD nnnn, DWORD lineno0, DWORD lineno1) +{ + struct list *ptr0 = *pptr0, *ptr1 = *pptr1; + DWORD count = 0; + while (ptr0 && ptr1) + { + NODE *node0 = LIST_ENTRY(ptr0, NODE, entry); + NODE *node1 = LIST_ENTRY(ptr1, NODE, entry); + if (node0->lineno >= lineno0) + break; + if (node1->lineno >= lineno1) + break; + if (CompareNode(pFC, node0, node1) != FCRET_IDENTICAL) + break; + ptr0 = list_next(&pFC->list[0], ptr0); + ptr1 = list_next(&pFC->list[1], ptr1); + ++count; + if (count >= nnnn) + break; + } + *pptr0 = ptr0; + *pptr1 = ptr1; + return count; +} + +static FCRET +ScanDiff(FILECOMPARE *pFC, struct list **pptr0, struct list **pptr1, + DWORD lineno0, DWORD lineno1) +{ + struct list *ptr0 = *pptr0, *ptr1 = *pptr1, *tmp0, *tmp1; + NODE *node0, *node1; + INT count; + while (ptr0 && ptr1) + { + node0 = LIST_ENTRY(ptr0, NODE, entry); + node1 = LIST_ENTRY(ptr1, NODE, entry); + if (node0->lineno >= lineno0) + return FCRET_DIFFERENT; + if (node1->lineno >= lineno1) + return FCRET_DIFFERENT; + tmp0 = ptr0; + tmp1 = ptr1; + count = SkipIdenticalN(pFC, &tmp0, &tmp1, pFC->nnnn, lineno0, lineno1); + if (count >= pFC->nnnn) + break; + if (count > 0) + { + ptr0 = tmp0; + ptr1 = tmp1; + } + else + { + ptr0 = list_next(&pFC->list[0], ptr0); + ptr1 = list_next(&pFC->list[1], ptr1); + } + } + *pptr0 = ptr0; + *pptr1 = ptr1; + return FCRET_IDENTICAL; +} + +static FCRET +Resync(FILECOMPARE *pFC, struct list **pptr0, struct list **pptr1) +{ + FCRET ret; + struct list *ptr0, *ptr1, *save0 = NULL, *save1 = NULL; + NODE *node0, *node1; + struct list *list0 = &pFC->list[0], *list1 = &pFC->list[1]; + DWORD lineno0, lineno1; + INT penalty, i0, i1, min_penalty = MAXLONG; + + node0 = LIST_ENTRY(*pptr0, NODE, entry); + node1 = LIST_ENTRY(*pptr1, NODE, entry); + lineno0 = node0->lineno + pFC->n; + lineno1 = node1->lineno + pFC->n; + + // ``If the files that you are comparing have more than pFC->n consecutive + // differing lines, FC cancels the comparison,, + // ``If the number of matching lines in the files is less than pFC->nnnn, + // FC displays the matching lines as differences,, + for (ptr1 = list_next(list1, *pptr1), i1 = 0; ptr1; ptr1 = list_next(list1, ptr1), ++i1) + { + node1 = LIST_ENTRY(ptr1, NODE, entry); + if (node1->lineno >= lineno1) + break; + for (ptr0 = list_next(list0, *pptr0), i0 = 0; ptr0; ptr0 = list_next(list0, ptr0), ++i0) + { + node0 = LIST_ENTRY(ptr0, NODE, entry); + if (node0->lineno >= lineno0) + break; + if (CompareNode(pFC, node0, node1) == FCRET_IDENTICAL) + { + penalty = min(i0, i1) + abs(i1 - i0); + if (min_penalty > penalty) + { + min_penalty = penalty; + save0 = ptr0; + save1 = ptr1; + } + } + } + } + + if (save0 && save1) + { + *pptr0 = save0; + *pptr1 = save1; + ret = ScanDiff(pFC, &save0, &save1, lineno0, lineno1); + if (save0 && save1) + { + *pptr0 = save0; + *pptr1 = save1; + } + return ret; + } + + for (ptr0 = *pptr0; ptr0; ptr0 = list_next(list0, ptr0)) + { + node0 = LIST_ENTRY(ptr0, NODE, entry); + if (node0->lineno == lineno0) + break; + } + for (ptr1 = *pptr1; ptr1; ptr1 = list_next(list1, ptr1)) + { + node1 = LIST_ENTRY(ptr1, NODE, entry); + if (node1->lineno == lineno1) + break; + } + *pptr0 = ptr0; + *pptr1 = ptr1; + return FCRET_DIFFERENT; +} + +static FCRET +Finalize(FILECOMPARE* pFC, struct list *ptr0, struct list* ptr1, BOOL fDifferent) +{ + if (!ptr0 || !ptr1) + { + if (fDifferent) + return FCRET_DIFFERENT; + return NoDifference(); + } + else + { + ShowDiff(pFC, 0, ptr0, NULL); + ShowDiff(pFC, 1, ptr1, NULL); + PrintEndOfDiff(); + return FCRET_DIFFERENT; + } +} + +FCRET TextCompare(FILECOMPARE *pFC, HANDLE *phMapping0, const LARGE_INTEGER *pcb0, + HANDLE *phMapping1, const LARGE_INTEGER *pcb1) +{ + FCRET ret, ret0, ret1; + struct list *ptr0, *ptr1, *save0, *save1, *next0, *next1; + NODE* node0, * node1; + BOOL fDifferent = FALSE; + LARGE_INTEGER ib0 = { .QuadPart = 0 }, ib1 = { .QuadPart = 0 }; + struct list *list0 = &pFC->list[0], *list1 = &pFC->list[1]; + list_init(list0); + list_init(list1); + + do + { + ret0 = ParseLines(pFC, phMapping0, &ib0, pcb0, list0); + if (ret0 == FCRET_INVALID) + { + ret = ret0; + goto cleanup; + } + ret1 = ParseLines(pFC, phMapping1, &ib1, pcb1, list1); + if (ret1 == FCRET_INVALID) + { + ret = ret1; + goto cleanup; + } + + ptr0 = list_head(list0); + ptr1 = list_head(list1); + for (;;) + { + if (!ptr0 || !ptr1) + goto quit; + + // skip identical (sync'ed) + SkipIdentical(pFC, &ptr0, &ptr1); + if (ptr0 || ptr1) + fDifferent = TRUE; + node0 = LIST_ENTRY(ptr0, NODE, entry); + node1 = LIST_ENTRY(ptr1, NODE, entry); + if (IsEOFNode(node0) || IsEOFNode(node1)) + goto quit; + + // try to resync + save0 = ptr0; + save1 = ptr1; + ret = Resync(pFC, &ptr0, &ptr1); + if (ret == FCRET_INVALID) + goto cleanup; + if (ret == FCRET_DIFFERENT) + { + // resync failed + ret = ResyncFailed(); + // show the difference + ShowDiff(pFC, 0, save0, ptr0); + ShowDiff(pFC, 1, save1, ptr1); + PrintEndOfDiff(); + goto cleanup; + } + + // show the difference + fDifferent = TRUE; + next0 = ptr0 ? list_next(list0, ptr0) : ptr0; + next1 = ptr1 ? list_next(list1, ptr1) : ptr1; + ShowDiff(pFC, 0, save0, (next0 ? next0 : ptr0)); + ShowDiff(pFC, 1, save1, (next1 ? next1 : ptr1)); + PrintEndOfDiff(); + + // now resync'ed + } + } while (ret0 != FCRET_NO_MORE_DATA || ret1 != FCRET_NO_MORE_DATA); + +quit: + ret = Finalize(pFC, ptr0, ptr1, fDifferent); +cleanup: + DeleteList(list0); + DeleteList(list1); + return ret; +} diff --git a/base/applications/cmdutils/fc/texta.c b/base/applications/cmdutils/fc/texta.c new file mode 100644 index 00000000000..1f524f76f03 --- /dev/null +++ b/base/applications/cmdutils/fc/texta.c @@ -0,0 +1,6 @@ +#undef UNICODE +#undef _UNICODE +#ifndef _MBCS + #define _MBCS +#endif +#include "text.h" diff --git a/base/applications/cmdutils/fc/textw.c b/base/applications/cmdutils/fc/textw.c new file mode 100644 index 00000000000..a3fa9114819 --- /dev/null +++ b/base/applications/cmdutils/fc/textw.c @@ -0,0 +1,8 @@ +#ifndef UNICODE + #define UNICODE +#endif +#ifndef _UNICODE + #define _UNICODE +#endif +#undef _MBCS +#include "text.h"