1 /* 2 * PROJECT: ReactOS Find Command 3 * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) 4 * PURPOSE: Prints all lines of a file that contain a string. 5 * COPYRIGHT: Copyright 1994-2002 Jim Hall (jhall@freedos.org) 6 * Copyright 2019 Paweł Cholewa (DaMcpg@protonmail.com) 7 * Copyright 2019 Hermes Belusca-Maito 8 */ 9 10 #include <stdio.h> 11 #include <stdlib.h> 12 13 #include <windef.h> 14 #include <winbase.h> 15 #include <winnls.h> 16 #include <winuser.h> 17 18 #include <conutils.h> 19 #include <strsafe.h> 20 21 #include "resource.h" 22 23 #define FIND_LINE_BUFFER_SIZE 4096 24 25 static BOOL bInvertSearch = FALSE; 26 static BOOL bCountLines = FALSE; 27 static BOOL bDisplayLineNumbers = FALSE; 28 static BOOL bIgnoreCase = FALSE; 29 static BOOL bDoNotSkipOfflineFiles = FALSE; 30 31 /** 32 * @name StrStrCase 33 * @implemented 34 * 35 * Locates a substring inside a NULL-terminated wide string. 36 * 37 * @param[in] pszStr 38 * The NULL-terminated string to be scanned. 39 * 40 * @param[in] pszSearch 41 * The NULL-terminated string to search for. 42 * 43 * @param[in] bIgnoreCase 44 * TRUE if case has to be ignored, FALSE otherwise. 45 * 46 * @return 47 * Returns a pointer to the first occurrence of pszSearch in pszStr, 48 * or NULL if pszSearch does not appear in pszStr. If pszSearch points 49 * to a string of zero length, the function returns pszStr. 50 */ 51 static PWSTR 52 StrStrCase( 53 IN PCWSTR pszStr, 54 IN PCWSTR pszSearch, 55 IN BOOL bIgnoreCase) 56 { 57 if (bIgnoreCase) 58 { 59 LCID LocaleId; 60 INT i, cch1, cch2; 61 62 LocaleId = GetThreadLocale(); 63 64 cch1 = wcslen(pszStr); 65 cch2 = wcslen(pszSearch); 66 67 if (cch2 == 0) 68 return (PWSTR)pszStr; 69 70 for (i = 0; i <= cch1 - cch2; ++i) 71 { 72 if (CompareStringW(LocaleId /* LOCALE_SYSTEM_DEFAULT */, 73 NORM_IGNORECASE /* | NORM_LINGUISTIC_CASING */, 74 pszStr + i, cch2, pszSearch, cch2) == CSTR_EQUAL) 75 { 76 return (PWSTR)(pszStr + i); 77 } 78 } 79 return NULL; 80 } 81 else 82 { 83 return wcsstr(pszStr, pszSearch); 84 } 85 } 86 87 /** 88 * @name FindString 89 * @implemented 90 * 91 * Prints all lines of the stream that contain a string. 92 * 93 * @param[in] pStream 94 * The stream to read from. 95 * 96 * @param[in] pszFilePath 97 * The file name to print out. Can be NULL. 98 * 99 * @param[in] pszSearchString 100 * The NULL-terminated string to search for. 101 * 102 * @return 103 * 0 if the string was found at least once, 1 otherwise. 104 */ 105 static int 106 FindString( 107 IN FILE* pStream, 108 IN PCWSTR pszFilePath OPTIONAL, 109 IN PCWSTR pszSearchString) 110 { 111 LONG lLineCount = 0; 112 LONG lLineNumber = 0; 113 BOOL bSubstringFound; 114 int iReturnValue = 1; 115 WCHAR szLineBuffer[FIND_LINE_BUFFER_SIZE]; 116 117 if (pszFilePath != NULL) 118 { 119 /* Print the file's header */ 120 ConPrintf(StdOut, L"\n---------- %s%s", 121 pszFilePath, bCountLines ? L": " : L"\n"); 122 } 123 124 /* Loop through every line in the file */ 125 // FIXME: What if the string we search for crosses the boundary of our szLineBuffer ? 126 while (fgetws(szLineBuffer, _countof(szLineBuffer), pStream) != NULL) 127 { 128 ++lLineNumber; 129 130 bSubstringFound = (StrStrCase(szLineBuffer, pszSearchString, bIgnoreCase) != NULL); 131 132 /* Check if this line can be counted */ 133 if (bSubstringFound != bInvertSearch) 134 { 135 iReturnValue = 0; 136 137 if (bCountLines) 138 { 139 ++lLineCount; 140 } 141 else 142 { 143 /* Display the line number if needed */ 144 if (bDisplayLineNumbers) 145 { 146 ConPrintf(StdOut, L"[%ld]", lLineNumber); 147 } 148 ConPrintf(StdOut, L"%s", szLineBuffer); 149 } 150 } 151 } 152 153 if (bCountLines) 154 { 155 /* Print the matching line count */ 156 ConPrintf(StdOut, L"%ld\n", lLineCount); 157 } 158 #if 0 159 else if (pszFilePath != NULL && iReturnValue == 0) 160 { 161 /* Print a newline for formatting */ 162 ConPrintf(StdOut, L"\n"); 163 } 164 #endif 165 166 return iReturnValue; 167 } 168 169 int wmain(int argc, WCHAR* argv[]) 170 { 171 int i; 172 int iReturnValue = 2; 173 int iSearchedStringIndex = -1; 174 BOOL bFoundFileParameter = FALSE; 175 HANDLE hFindFile; 176 WIN32_FIND_DATAW FindData; 177 FILE* pOpenedFile; 178 PWCHAR ptr; 179 WCHAR szFullFilePath[MAX_PATH]; 180 181 /* Initialize the Console Standard Streams */ 182 ConInitStdStreams(); 183 184 if (argc == 1) 185 { 186 /* If no argument were provided by the user, display program usage and exit */ 187 ConResPuts(StdOut, IDS_USAGE); 188 return 0; 189 } 190 191 /* Parse the command line arguments */ 192 for (i = 1; i < argc; ++i) 193 { 194 /* Check if this argument contains a switch */ 195 if (wcslen(argv[i]) == 2 && argv[i][0] == L'/') 196 { 197 switch (towupper(argv[i][1])) 198 { 199 case L'?': 200 ConResPuts(StdOut, IDS_USAGE); 201 return 0; 202 case L'V': 203 bInvertSearch = TRUE; 204 break; 205 case L'C': 206 bCountLines = TRUE; 207 break; 208 case L'N': 209 bDisplayLineNumbers = TRUE; 210 break; 211 case L'I': 212 bIgnoreCase = TRUE; 213 break; 214 default: 215 /* Report invalid switch error */ 216 ConResPuts(StdErr, IDS_INVALID_SWITCH); 217 return 2; 218 } 219 } 220 else if (wcslen(argv[i]) > 2 && argv[i][0] == L'/') 221 { 222 /* Check if this parameter is /OFF or /OFFLINE */ 223 if (_wcsicmp(argv[i], L"/off") == 0 || _wcsicmp(argv[i], L"/offline") == 0) 224 { 225 bDoNotSkipOfflineFiles = TRUE; 226 } 227 else 228 { 229 /* Report invalid switch error */ 230 ConResPuts(StdErr, IDS_INVALID_SWITCH); 231 return 2; 232 } 233 } 234 else 235 { 236 if (iSearchedStringIndex == -1) 237 { 238 iSearchedStringIndex = i; 239 } 240 else 241 { 242 /* There's a file specified in the parameters, no need to read from stdin */ 243 bFoundFileParameter = TRUE; 244 } 245 } 246 } 247 248 if (iSearchedStringIndex == -1) 249 { 250 /* User didn't provide the string to search for, display program usage and exit */ 251 ConResPuts(StdErr, IDS_USAGE); 252 return 2; 253 } 254 255 if (bFoundFileParameter) 256 { 257 /* After the command line arguments were parsed, iterate through them again to get the filenames */ 258 for (i = 1; i < argc; ++i) 259 { 260 /* If the value is a switch or the searched string, continue */ 261 if ((wcslen(argv[i]) > 0 && argv[i][0] == L'/') || i == iSearchedStringIndex) 262 { 263 continue; 264 } 265 266 hFindFile = FindFirstFileW(argv[i], &FindData); 267 if (hFindFile == INVALID_HANDLE_VALUE) 268 { 269 ConResPrintf(StdErr, IDS_NO_SUCH_FILE, argv[i]); 270 continue; 271 } 272 273 do 274 { 275 /* Check if the file contains offline attribute and should be skipped */ 276 if ((FindData.dwFileAttributes & FILE_ATTRIBUTE_OFFLINE) && !bDoNotSkipOfflineFiles) 277 { 278 continue; 279 } 280 281 /* Skip directory */ 282 // FIXME: Implement recursivity? 283 if (FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) 284 { 285 continue; 286 } 287 288 /* 289 * Build the full file path from the file specification pattern. 290 * 291 * Note that we could use GetFullPathName() instead, however 292 * we want to keep compatibility with Windows' find.exe utility 293 * that does not use this function as it keeps the file name 294 * directly based on the pattern. 295 */ 296 ptr = wcsrchr(argv[i], L'\\'); // Check for last directory. 297 if (!ptr) 298 ptr = wcsrchr(argv[i], L':'); // Check for drive. 299 if (ptr) 300 { 301 /* The pattern contains a drive or directory part: keep it and concatenate the full file name */ 302 StringCchCopyNW(szFullFilePath, _countof(szFullFilePath), 303 argv[i], ptr + 1 - argv[i]); 304 StringCchCatW(szFullFilePath, _countof(szFullFilePath), 305 FindData.cFileName); 306 } 307 else 308 { 309 /* The pattern does not contain any drive or directory part: just copy the full file name */ 310 StringCchCopyW(szFullFilePath, _countof(szFullFilePath), 311 FindData.cFileName); 312 } 313 314 // FIXME: Windows' find.exe supports searching inside binary files. 315 pOpenedFile = _wfopen(szFullFilePath, L"r"); 316 if (pOpenedFile == NULL) 317 { 318 ConResPrintf(StdErr, IDS_CANNOT_OPEN, szFullFilePath); 319 continue; 320 } 321 322 /* NOTE: Convert the file path to uppercase for formatting */ 323 if (FindString(pOpenedFile, _wcsupr(szFullFilePath), argv[iSearchedStringIndex]) == 0) 324 { 325 iReturnValue = 0; 326 } 327 else if (iReturnValue != 0) 328 { 329 iReturnValue = 1; 330 } 331 332 fclose(pOpenedFile); 333 } while (FindNextFileW(hFindFile, &FindData)); 334 335 FindClose(hFindFile); 336 } 337 } 338 else 339 { 340 FindString(stdin, NULL, argv[iSearchedStringIndex]); 341 } 342 343 return iReturnValue; 344 } 345