[FIND] Improvements / bug-fixes. (#1553)

- Only include the strictly necessary headers.
- Get rid of the dependency on shell and user DLLs.
- fgetws() gets the string buffer size in number of characters.
- We can use the CRT functions for lengths of the arguments etc.

- The cFileName member of the WIN32_FIND_DATAW structure does not
  contain the full PATH to the enumerated file, but only its name.
  In order to use _wfopen(), build a full file path out of the
  directory part of the file specification and the full file name.

- Simplify a ConPrintf() call to make it "atomic".
- Fix the "confusion" lLineCount vs. lLineNumber vocable in the code.
- Do not emit an extra newline after having displayed the results for
  a given file.
- Uppercase the switches for performing the comparisons.
- Send the errors to the StdErr stream.
- Remove trailing whitespace.
This commit is contained in:
Hermès Bélusca-Maïto 2019-05-12 01:05:53 +02:00
parent 1bd330cd81
commit 7a133609e7
No known key found for this signature in database
GPG key ID: 3B2539C65E7B93D0
2 changed files with 159 additions and 78 deletions

View file

@ -4,5 +4,5 @@ include_directories(${REACTOS_SOURCE_DIR}/sdk/lib/conutils)
add_executable(find find.c find.rc) add_executable(find find.c find.rc)
set_module_type(find win32cui UNICODE) set_module_type(find win32cui UNICODE)
target_link_libraries(find conutils ${PSEH_LIB}) 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) add_cd_file(TARGET find DESTINATION reactos/system32 FOR all)

View file

@ -4,12 +4,19 @@
* PURPOSE: Prints all lines of a file that contain a string. * PURPOSE: Prints all lines of a file that contain a string.
* COPYRIGHT: Copyright 1994-2002 Jim Hall (jhall@freedos.org) * COPYRIGHT: Copyright 1994-2002 Jim Hall (jhall@freedos.org)
* Copyright 2019 Paweł Cholewa (DaMcpg@protonmail.com) * Copyright 2019 Paweł Cholewa (DaMcpg@protonmail.com)
* Copyright 2019 Hermes Belusca-Maito
*/ */
#include <windows.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h>
#include <windef.h>
#include <winbase.h>
#include <winnls.h>
#include <winuser.h>
#include <conutils.h> #include <conutils.h>
#include <shlwapi.h> /* StrStrW and StrStrIW */ #include <strsafe.h>
#include "resource.h" #include "resource.h"
@ -21,65 +28,106 @@ static BOOL bDisplayLineNumbers = FALSE;
static BOOL bIgnoreCase = FALSE; static BOOL bIgnoreCase = FALSE;
static BOOL bDoNotSkipOfflineFiles = 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 * @name FindString
* @implemented * @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 lLineCount = 0;
LONG lLineNumber = 0; LONG lLineNumber = 0;
BOOL bSubstringFound; BOOL bSubstringFound;
int iReturnValue = 1; 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 */ /* Print the file's header */
ConPrintf(StdOut, L"\n---------- %s", szFilePath); ConPrintf(StdOut, L"\n---------- %s%s",
pszFilePath, bCountLines ? L": " : L"\n");
if (bCountLines)
{
ConPrintf(StdOut, L": ");
}
else
{
ConPrintf(StdOut, L"\n");
}
} }
/* Loop through every line in the file */ /* 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 = (StrStrCase(szLineBuffer, pszSearchString, bIgnoreCase) != NULL);
{
bSubstringFound = StrStrIW(szLineBuffer, szSearchedString) != NULL;
}
else
{
bSubstringFound = StrStrW(szLineBuffer, szSearchedString) != NULL;
}
/* Check if this line can be counted */ /* Check if this line can be counted */
if (bSubstringFound != bInvertSearch) if (bSubstringFound != bInvertSearch)
@ -88,14 +136,14 @@ static int FindString(FILE* pStream, LPWSTR szFilePath, LPWSTR szSearchedString)
if (bCountLines) if (bCountLines)
{ {
lLineNumber++; ++lLineCount;
} }
else else
{ {
/* Display the line on the screen */ /* Display the line number if needed */
if (bDisplayLineNumbers) if (bDisplayLineNumbers)
{ {
ConPrintf(StdOut, L"[%ld]", lLineCount); ConPrintf(StdOut, L"[%ld]", lLineNumber);
} }
ConPrintf(StdOut, L"%s", szLineBuffer); ConPrintf(StdOut, L"%s", szLineBuffer);
} }
@ -105,13 +153,15 @@ static int FindString(FILE* pStream, LPWSTR szFilePath, LPWSTR szSearchedString)
if (bCountLines) if (bCountLines)
{ {
/* Print the matching line count */ /* 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 */ /* Print a newline for formatting */
ConPrintf(StdOut, L"\n"); ConPrintf(StdOut, L"\n");
} }
#endif
return iReturnValue; return iReturnValue;
} }
@ -121,14 +171,14 @@ int wmain(int argc, WCHAR* argv[])
int i; int i;
int iReturnValue = 2; int iReturnValue = 2;
int iSearchedStringIndex = -1; int iSearchedStringIndex = -1;
BOOL bFoundFileParameter = FALSE; BOOL bFoundFileParameter = FALSE;
HANDLE hFindFile;
HANDLE hFindFileHandle;
WIN32_FIND_DATAW FindData; WIN32_FIND_DATAW FindData;
FILE* pOpenedFile; FILE* pOpenedFile;
PWCHAR ptr;
WCHAR szFullFilePath[MAX_PATH];
/* Initialize the Console Standard Streams */
ConInitStdStreams(); ConInitStdStreams();
if (argc == 1) if (argc == 1)
@ -139,49 +189,45 @@ int wmain(int argc, WCHAR* argv[])
} }
/* Parse the command line arguments */ /* Parse the command line arguments */
for (i = 1; i < argc; i++) for (i = 1; i < argc; ++i)
{ {
/* Check if this argument contains a switch */ /* 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'?': case L'?':
ConResPuts(StdOut, IDS_USAGE); ConResPuts(StdOut, IDS_USAGE);
return 0; return 0;
case L'v':
case L'V': case L'V':
bInvertSearch = TRUE; bInvertSearch = TRUE;
break; break;
case L'c':
case L'C': case L'C':
bCountLines = TRUE; bCountLines = TRUE;
break; break;
case L'n':
case L'N': case L'N':
bDisplayLineNumbers = TRUE; bDisplayLineNumbers = TRUE;
break; break;
case L'i':
case L'I': case L'I':
bIgnoreCase = TRUE; bIgnoreCase = TRUE;
break; break;
default: default:
/* Report invalid switch error */ /* Report invalid switch error */
ConResPuts(StdOut, IDS_INVALID_SWITCH); ConResPuts(StdErr, IDS_INVALID_SWITCH);
return 2; 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 */ /* 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; bDoNotSkipOfflineFiles = TRUE;
} }
else else
{ {
/* Report invalid switch error */ /* Report invalid switch error */
ConResPuts(StdOut, IDS_INVALID_SWITCH); ConResPuts(StdErr, IDS_INVALID_SWITCH);
return 2; return 2;
} }
} }
@ -202,28 +248,28 @@ int wmain(int argc, WCHAR* argv[])
if (iSearchedStringIndex == -1) if (iSearchedStringIndex == -1)
{ {
/* User didn't provide the string to search for, display program usage and exit */ /* User didn't provide the string to search for, display program usage and exit */
ConResPuts(StdOut, IDS_USAGE); ConResPuts(StdErr, IDS_USAGE);
return 2; return 2;
} }
if (bFoundFileParameter) if (bFoundFileParameter)
{ {
/* After the command line arguments were parsed, iterate through them again to get the filenames */ /* 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 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; continue;
} }
hFindFileHandle = FindFirstFileW(argv[i], &FindData); hFindFile = FindFirstFileW(argv[i], &FindData);
if (hFindFileHandle == INVALID_HANDLE_VALUE) if (hFindFile == INVALID_HANDLE_VALUE)
{ {
ConResPrintf(StdOut, IDS_NO_SUCH_FILE, argv[i]); ConResPrintf(StdErr, IDS_NO_SUCH_FILE, argv[i]);
continue; continue;
} }
do do
{ {
/* Check if the file contains offline attribute and should be skipped */ /* Check if the file contains offline attribute and should be skipped */
@ -232,14 +278,49 @@ int wmain(int argc, WCHAR* argv[])
continue; continue;
} }
pOpenedFile = _wfopen(FindData.cFileName, L"r"); /* Skip directory */
if (pOpenedFile == NULL) // FIXME: Implement recursivity?
if (FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{ {
ConResPrintf(StdOut, IDS_CANNOT_OPEN, FindData.cFileName);
continue; 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; iReturnValue = 0;
} }
@ -249,15 +330,15 @@ int wmain(int argc, WCHAR* argv[])
} }
fclose(pOpenedFile); fclose(pOpenedFile);
} while (FindNextFileW(hFindFileHandle, &FindData)); } while (FindNextFileW(hFindFile, &FindData));
FindClose(hFindFileHandle); FindClose(hFindFile);
} }
} }
else else
{ {
FindString(stdin, NULL, argv[iSearchedStringIndex]); FindString(stdin, NULL, argv[iSearchedStringIndex]);
} }
return iReturnValue; return iReturnValue;
} }