mirror of
https://github.com/reactos/reactos.git
synced 2025-07-31 21:11:54 +00:00
[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:
parent
1bd330cd81
commit
7a133609e7
2 changed files with 159 additions and 78 deletions
|
@ -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)
|
||||
|
|
|
@ -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 <windows.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <windef.h>
|
||||
#include <winbase.h>
|
||||
#include <winnls.h>
|
||||
#include <winuser.h>
|
||||
|
||||
#include <conutils.h>
|
||||
#include <shlwapi.h> /* StrStrW and StrStrIW */
|
||||
#include <strsafe.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue