diff --git a/base/applications/cmdutils/find/CMakeLists.txt b/base/applications/cmdutils/find/CMakeLists.txt index 1df4d1b98df..e39eb506097 100644 --- a/base/applications/cmdutils/find/CMakeLists.txt +++ b/base/applications/cmdutils/find/CMakeLists.txt @@ -4,5 +4,5 @@ include_directories(${REACTOS_SOURCE_DIR}/sdk/lib/conutils) add_executable(find find.c find.rc) set_module_type(find win32cui UNICODE) target_link_libraries(find conutils ${PSEH_LIB}) -add_importlibs(find user32 msvcrt kernel32 shlwapi) +add_importlibs(find msvcrt kernel32) add_cd_file(TARGET find DESTINATION reactos/system32 FOR all) diff --git a/base/applications/cmdutils/find/find.c b/base/applications/cmdutils/find/find.c index 66cb2ef6245..9dd003fc07f 100644 --- a/base/applications/cmdutils/find/find.c +++ b/base/applications/cmdutils/find/find.c @@ -4,12 +4,19 @@ * PURPOSE: Prints all lines of a file that contain a string. * COPYRIGHT: Copyright 1994-2002 Jim Hall (jhall@freedos.org) * Copyright 2019 Paweł Cholewa (DaMcpg@protonmail.com) + * Copyright 2019 Hermes Belusca-Maito */ -#include #include +#include + +#include +#include +#include +#include + #include -#include /* StrStrW and StrStrIW */ +#include #include "resource.h" @@ -21,65 +28,106 @@ static BOOL bDisplayLineNumbers = FALSE; static BOOL bIgnoreCase = FALSE; static BOOL bDoNotSkipOfflineFiles = FALSE; +/** + * @name StrStrCase + * @implemented + * + * Locates a substring inside a NULL-terminated wide string. + * + * @param[in] pszStr + * The NULL-terminated string to be scanned. + * + * @param[in] pszSearch + * The NULL-terminated string to search for. + * + * @param[in] bIgnoreCase + * TRUE if case has to be ignored, FALSE otherwise. + * + * @return + * Returns a pointer to the first occurrence of pszSearch in pszStr, + * or NULL if pszSearch does not appear in pszStr. If pszSearch points + * to a string of zero length, the function returns pszStr. + */ +static PWSTR +StrStrCase( + IN PCWSTR pszStr, + IN PCWSTR pszSearch, + IN BOOL bIgnoreCase) +{ + if (bIgnoreCase) + { + LCID LocaleId; + INT i, cch1, cch2; + + LocaleId = GetThreadLocale(); + + cch1 = wcslen(pszStr); + cch2 = wcslen(pszSearch); + + if (cch2 == 0) + return (PWSTR)pszStr; + + for (i = 0; i <= cch1 - cch2; ++i) + { + if (CompareStringW(LocaleId /* LOCALE_SYSTEM_DEFAULT */, + NORM_IGNORECASE /* | NORM_LINGUISTIC_CASING */, + pszStr + i, cch2, pszSearch, cch2) == CSTR_EQUAL) + { + return (PWSTR)(pszStr + i); + } + } + return NULL; + } + else + { + return wcsstr(pszStr, pszSearch); + } +} + /** * @name FindString * @implemented - * - * Prints all lines of the stream that contain a string. - * - * @param pStream - * Stream to read from. - * - * @param szFilePath - * Filename to print in console. Can be NULL. - * - * @param szSearchedString - * String to search for. - * - * @return - * 0 if the string was found at least once, 1 otherwise. * + * Prints all lines of the stream that contain a string. + * + * @param[in] pStream + * The stream to read from. + * + * @param[in] pszFilePath + * The file name to print out. Can be NULL. + * + * @param[in] pszSearchString + * The NULL-terminated string to search for. + * + * @return + * 0 if the string was found at least once, 1 otherwise. */ -static int FindString(FILE* pStream, LPWSTR szFilePath, LPWSTR szSearchedString) +static int +FindString( + IN FILE* pStream, + IN PCWSTR pszFilePath OPTIONAL, + IN PCWSTR pszSearchString) { - WCHAR szLineBuffer[FIND_LINE_BUFFER_SIZE]; LONG lLineCount = 0; LONG lLineNumber = 0; BOOL bSubstringFound; int iReturnValue = 1; + WCHAR szLineBuffer[FIND_LINE_BUFFER_SIZE]; - if (szFilePath != NULL) + if (pszFilePath != NULL) { - /* Convert the filename to uppercase (for formatting) */ - CharUpperW(szFilePath); - /* Print the file's header */ - ConPrintf(StdOut, L"\n---------- %s", szFilePath); - - if (bCountLines) - { - ConPrintf(StdOut, L": "); - } - else - { - ConPrintf(StdOut, L"\n"); - } + ConPrintf(StdOut, L"\n---------- %s%s", + pszFilePath, bCountLines ? L": " : L"\n"); } /* Loop through every line in the file */ - while (fgetws(szLineBuffer, sizeof(szLineBuffer), pStream) != NULL) + // FIXME: What if the string we search for crosses the boundary of our szLineBuffer ? + while (fgetws(szLineBuffer, _countof(szLineBuffer), pStream) != NULL) { - lLineCount++; + ++lLineNumber; - if (bIgnoreCase) - { - bSubstringFound = StrStrIW(szLineBuffer, szSearchedString) != NULL; - } - else - { - bSubstringFound = StrStrW(szLineBuffer, szSearchedString) != NULL; - } - + bSubstringFound = (StrStrCase(szLineBuffer, pszSearchString, bIgnoreCase) != NULL); /* Check if this line can be counted */ if (bSubstringFound != bInvertSearch) @@ -88,14 +136,14 @@ static int FindString(FILE* pStream, LPWSTR szFilePath, LPWSTR szSearchedString) if (bCountLines) { - lLineNumber++; + ++lLineCount; } else { - /* Display the line on the screen */ + /* Display the line number if needed */ if (bDisplayLineNumbers) { - ConPrintf(StdOut, L"[%ld]", lLineCount); + ConPrintf(StdOut, L"[%ld]", lLineNumber); } ConPrintf(StdOut, L"%s", szLineBuffer); } @@ -105,13 +153,15 @@ static int FindString(FILE* pStream, LPWSTR szFilePath, LPWSTR szSearchedString) if (bCountLines) { /* Print the matching line count */ - ConPrintf(StdOut, L"%ld\n", lLineNumber); + ConPrintf(StdOut, L"%ld\n", lLineCount); } - else if (szFilePath != NULL && iReturnValue == 0) +#if 0 + else if (pszFilePath != NULL && iReturnValue == 0) { /* Print a newline for formatting */ ConPrintf(StdOut, L"\n"); } +#endif return iReturnValue; } @@ -121,14 +171,14 @@ int wmain(int argc, WCHAR* argv[]) int i; int iReturnValue = 2; int iSearchedStringIndex = -1; - BOOL bFoundFileParameter = FALSE; - - HANDLE hFindFileHandle; + HANDLE hFindFile; WIN32_FIND_DATAW FindData; - FILE* pOpenedFile; + PWCHAR ptr; + WCHAR szFullFilePath[MAX_PATH]; + /* Initialize the Console Standard Streams */ ConInitStdStreams(); if (argc == 1) @@ -139,49 +189,45 @@ int wmain(int argc, WCHAR* argv[]) } /* Parse the command line arguments */ - for (i = 1; i < argc; i++) + for (i = 1; i < argc; ++i) { /* Check if this argument contains a switch */ - if (lstrlenW(argv[i]) == 2 && argv[i][0] == L'/') + if (wcslen(argv[i]) == 2 && argv[i][0] == L'/') { - switch (argv[i][1]) + switch (towupper(argv[i][1])) { case L'?': ConResPuts(StdOut, IDS_USAGE); return 0; - case L'v': case L'V': bInvertSearch = TRUE; break; - case L'c': case L'C': bCountLines = TRUE; break; - case L'n': case L'N': bDisplayLineNumbers = TRUE; break; - case L'i': case L'I': bIgnoreCase = TRUE; break; default: /* Report invalid switch error */ - ConResPuts(StdOut, IDS_INVALID_SWITCH); + ConResPuts(StdErr, IDS_INVALID_SWITCH); return 2; } } - else if (lstrlenW(argv[i]) > 2 && argv[i][0] == L'/') + else if (wcslen(argv[i]) > 2 && argv[i][0] == L'/') { /* Check if this parameter is /OFF or /OFFLINE */ - if (lstrcmpiW(argv[i], L"/off") == 0 || lstrcmpiW(argv[i], L"/offline") == 0) + if (_wcsicmp(argv[i], L"/off") == 0 || _wcsicmp(argv[i], L"/offline") == 0) { bDoNotSkipOfflineFiles = TRUE; } else { /* Report invalid switch error */ - ConResPuts(StdOut, IDS_INVALID_SWITCH); + ConResPuts(StdErr, IDS_INVALID_SWITCH); return 2; } } @@ -202,28 +248,28 @@ int wmain(int argc, WCHAR* argv[]) if (iSearchedStringIndex == -1) { /* User didn't provide the string to search for, display program usage and exit */ - ConResPuts(StdOut, IDS_USAGE); + ConResPuts(StdErr, IDS_USAGE); return 2; } if (bFoundFileParameter) { /* After the command line arguments were parsed, iterate through them again to get the filenames */ - for (i = 1; i < argc; i++) + for (i = 1; i < argc; ++i) { /* If the value is a switch or the searched string, continue */ - if ((lstrlenW(argv[i]) > 0 && argv[i][0] == L'/') || i == iSearchedStringIndex) + if ((wcslen(argv[i]) > 0 && argv[i][0] == L'/') || i == iSearchedStringIndex) { continue; } - hFindFileHandle = FindFirstFileW(argv[i], &FindData); - if (hFindFileHandle == INVALID_HANDLE_VALUE) + hFindFile = FindFirstFileW(argv[i], &FindData); + if (hFindFile == INVALID_HANDLE_VALUE) { - ConResPrintf(StdOut, IDS_NO_SUCH_FILE, argv[i]); + ConResPrintf(StdErr, IDS_NO_SUCH_FILE, argv[i]); continue; } - + do { /* Check if the file contains offline attribute and should be skipped */ @@ -232,14 +278,49 @@ int wmain(int argc, WCHAR* argv[]) continue; } - pOpenedFile = _wfopen(FindData.cFileName, L"r"); - if (pOpenedFile == NULL) + /* Skip directory */ + // FIXME: Implement recursivity? + if (FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { - ConResPrintf(StdOut, IDS_CANNOT_OPEN, FindData.cFileName); continue; } - if (FindString(pOpenedFile, FindData.cFileName, argv[iSearchedStringIndex]) == 0) + /* + * Build the full file path from the file specification pattern. + * + * Note that we could use GetFullPathName() instead, however + * we want to keep compatibility with Windows' find.exe utility + * that does not use this function as it keeps the file name + * directly based on the pattern. + */ + ptr = wcsrchr(argv[i], L'\\'); // Check for last directory. + if (!ptr) + ptr = wcsrchr(argv[i], L':'); // Check for drive. + if (ptr) + { + /* The pattern contains a drive or directory part: keep it and concatenate the full file name */ + StringCchCopyNW(szFullFilePath, _countof(szFullFilePath), + argv[i], ptr + 1 - argv[i]); + StringCchCatW(szFullFilePath, _countof(szFullFilePath), + FindData.cFileName); + } + else + { + /* The pattern does not contain any drive or directory part: just copy the full file name */ + StringCchCopyW(szFullFilePath, _countof(szFullFilePath), + FindData.cFileName); + } + + // FIXME: Windows' find.exe supports searching inside binary files. + pOpenedFile = _wfopen(szFullFilePath, L"r"); + if (pOpenedFile == NULL) + { + ConResPrintf(StdErr, IDS_CANNOT_OPEN, szFullFilePath); + continue; + } + + /* NOTE: Convert the file path to uppercase for formatting */ + if (FindString(pOpenedFile, _wcsupr(szFullFilePath), argv[iSearchedStringIndex]) == 0) { iReturnValue = 0; } @@ -249,15 +330,15 @@ int wmain(int argc, WCHAR* argv[]) } fclose(pOpenedFile); - } while (FindNextFileW(hFindFileHandle, &FindData)); + } while (FindNextFileW(hFindFile, &FindData)); - FindClose(hFindFileHandle); + FindClose(hFindFile); } } else { FindString(stdin, NULL, argv[iSearchedStringIndex]); } - + return iReturnValue; }