[FIND] Improvements / bug-fixes. (#1553) 1553/head
authorHermès Bélusca-Maïto <hermes.belusca-maito@reactos.org>
Sat, 11 May 2019 23:05:53 +0000 (01:05 +0200)
committerHermès Bélusca-Maïto <hermes.belusca-maito@reactos.org>
Tue, 14 May 2019 18:37:46 +0000 (20:37 +0200)
- 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.

base/applications/cmdutils/find/CMakeLists.txt
base/applications/cmdutils/find/find.c

index 1df4d1b..e39eb50 100644 (file)
@@ -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)
index 66cb2ef..9dd003f 100644 (file)
@@ -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.
  *
+ * @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");
+                /* Skip directory */
+                // FIXME: Implement recursivity?
+                if (FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+                {
+                    continue;
+                }
+
+                /*
+                 * 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(StdOut, IDS_CANNOT_OPEN, FindData.cFileName);
+                    ConResPrintf(StdErr, IDS_CANNOT_OPEN, szFullFilePath);
                     continue;
                 }
 
-                if (FindString(pOpenedFile, FindData.cFileName, argv[iSearchedStringIndex]) == 0)
+                /* 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;
 }