1 /* 2 * PROJECT: ReactOS WHERE command 3 * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) 4 * PURPOSE: Search executable files 5 * COPYRIGHT: Copyright 2021 Katayama Hirofumi MZ <katayama.hirofumi.mz@gmail.com> 6 */ 7 8 #include <stdlib.h> 9 #include <windef.h> 10 #include <winbase.h> 11 #include <winnls.h> 12 #include <strsafe.h> 13 #include <conutils.h> 14 #include "strlist.h" // strlist_... 15 #include "resource.h" 16 17 #define FLAG_HELP (1 << 0) // "/?" 18 #define FLAG_R (1 << 1) // recursive directory 19 #define FLAG_Q (1 << 2) // quiet mode 20 #define FLAG_F (1 << 3) // double quote 21 #define FLAG_T (1 << 4) // detailed info 22 23 static DWORD s_dwFlags = 0; 24 static LPWSTR s_pszRecursiveDir = NULL; 25 static strlist_t s_patterns = strlist_default; 26 static strlist_t s_results = strlist_default; 27 static strlist_t s_pathext = strlist_default; 28 29 // is it either "." or ".."? 30 #define IS_DOTS(pch) \ 31 (*(pch) == L'.' && ((pch)[1] == 0 || ((pch)[1] == L'.' && (pch)[2] == 0))) 32 33 #define DEFAULT_PATHEXT L".com;.exe;.bat;.cmd" 34 35 typedef enum WRET // return code of WHERE command 36 { 37 WRET_SUCCESS = 0, 38 WRET_NOT_FOUND = 1, 39 WRET_ERROR = 2 40 } WRET; 41 42 static VOID WhereError(UINT nID) 43 { 44 if (!(s_dwFlags & FLAG_Q)) // not quiet mode? 45 ConResPuts(StdErr, nID); 46 } 47 48 typedef BOOL (CALLBACK *WHERE_CALLBACK)(LPCWSTR pattern, LPCWSTR path, PWIN32_FIND_DATAW data); 49 50 static BOOL 51 WhereSearchGeneric(LPCWSTR pattern, LPWSTR path, size_t path_len, BOOL bDir, 52 WHERE_CALLBACK callback) 53 { 54 LPWSTR pch; 55 size_t cch; 56 BOOL ret; 57 WIN32_FIND_DATAW data; 58 HANDLE hFind = FindFirstFileExW(path, FindExInfoStandard, &data, FindExSearchNameMatch, 59 NULL, 0); 60 if (hFind == INVALID_HANDLE_VALUE) 61 return TRUE; // not found 62 63 pch = wcsrchr(path, L'\\') + 1; 64 cch = path_len - (pch - path); 65 do 66 { 67 if (bDir != !!(data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) 68 continue; 69 if (bDir && IS_DOTS(data.cFileName)) 70 continue; // ignore "." and ".." 71 if (data.dwFileAttributes & FILE_ATTRIBUTE_VIRTUAL) 72 continue; // ignore virtual 73 74 StringCchCopyW(pch, cch, data.cFileName); // build full path 75 76 ret = callback(pattern, path, &data); 77 if (!ret) // out of memory 78 break; 79 } while (FindNextFileW(hFind, &data)); 80 FindClose(hFind); 81 return ret; 82 } 83 84 static BOOL CALLBACK WherePrintPath(LPCWSTR pattern, LPCWSTR path, PWIN32_FIND_DATAW data) 85 { 86 WCHAR szPath[MAX_PATH + 2], szDate[32], szTime[32]; 87 LARGE_INTEGER FileSize; 88 FILETIME ftLocal; 89 SYSTEMTIME st; 90 91 if (strlist_find_i(&s_results, path) >= 0) 92 return TRUE; // already exists 93 if (!strlist_add(&s_results, path)) 94 return FALSE; // out of memory 95 if (s_dwFlags & FLAG_Q) // quiet mode? 96 return TRUE; 97 98 if (s_dwFlags & FLAG_T) // print detailed info 99 { 100 // convert date/time 101 FileTimeToLocalFileTime(&data->ftLastWriteTime, &ftLocal); 102 FileTimeToSystemTime(&ftLocal, &st); 103 // get date/time strings 104 GetDateFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL, szDate, _countof(szDate)); 105 GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL, szTime, _countof(szTime)); 106 // set size 107 FileSize.LowPart = data->nFileSizeLow; 108 FileSize.HighPart = data->nFileSizeHigh; 109 // print 110 if (s_dwFlags & FLAG_F) // double quote 111 StringCchPrintfW(szPath, _countof(szPath), L"\"%s\"", path); 112 else 113 StringCchCopyW(szPath, _countof(szPath), path); 114 ConResPrintf(StdOut, IDS_FILE_INFO, FileSize.QuadPart, szDate, szTime, szPath); 115 } 116 else // print path only 117 { 118 if (s_dwFlags & FLAG_F) // double quote 119 ConPrintf(StdOut, L"\"%ls\"\n", path); 120 else 121 ConPrintf(StdOut, L"%ls\n", path); 122 } 123 return TRUE; // success 124 } 125 126 static BOOL WhereSearchFiles(LPCWSTR pattern, LPCWSTR dir) 127 { 128 INT iExt; 129 size_t cch; 130 WCHAR szPath[MAX_PATH]; 131 StringCchCopyW(szPath, _countof(szPath), dir); 132 StringCchCatW(szPath, _countof(szPath), L"\\"); 133 StringCchCatW(szPath, _countof(szPath), pattern); 134 cch = wcslen(szPath); 135 136 for (iExt = 0; iExt < s_pathext.count; ++iExt) 137 { 138 szPath[cch] = 0; // cut off extension 139 // append extension 140 StringCchCatW(szPath, _countof(szPath), strlist_get_at(&s_pathext, iExt)); 141 142 if (!WhereSearchGeneric(pattern, szPath, _countof(szPath), FALSE, WherePrintPath)) 143 return FALSE; 144 } 145 return TRUE; 146 } 147 148 static BOOL WhereSearchRecursive(LPCWSTR pattern, LPCWSTR dir); 149 150 static BOOL CALLBACK 151 WhereSearchRecursiveCallback(LPCWSTR pattern, LPCWSTR path, PWIN32_FIND_DATAW data) 152 { 153 return WhereSearchRecursive(pattern, path); 154 } 155 156 // FIXME: Too slow. Optimize for speed. 157 static BOOL WhereSearchRecursive(LPCWSTR pattern, LPCWSTR dir) 158 { 159 WCHAR szPath[MAX_PATH]; 160 if (!WhereSearchFiles(pattern, dir)) 161 return FALSE; // out of memory 162 163 // build path with wildcard 164 StringCchCopyW(szPath, _countof(szPath), dir); 165 StringCchCatW(szPath, _countof(szPath), L"\\*"); 166 return WhereSearchGeneric(pattern, szPath, _countof(szPath), TRUE, 167 WhereSearchRecursiveCallback); 168 } 169 170 static BOOL WhereSearch(LPCWSTR pattern, strlist_t *dirlist) 171 { 172 UINT iDir; 173 for (iDir = 0; iDir < dirlist->count; ++iDir) 174 { 175 if (!WhereSearchFiles(pattern, strlist_get_at(dirlist, iDir))) 176 return FALSE; 177 } 178 return TRUE; 179 } 180 181 static BOOL WhereGetVariable(LPCWSTR name, LPWSTR *value) 182 { 183 DWORD cch = GetEnvironmentVariableW(name, NULL, 0); 184 if (cch == 0) // variable not found 185 { 186 *value = NULL; 187 if (!(s_dwFlags & FLAG_Q)) // not quiet mode? 188 ConResPrintf(StdErr, IDS_BAD_ENVVAR, name); 189 return TRUE; // it is error, but continue the task 190 } 191 192 *value = malloc(cch * sizeof(WCHAR)); 193 if (!*value || !GetEnvironmentVariableW(name, *value, cch)) 194 { 195 free(*value); 196 *value = NULL; 197 return FALSE; // error 198 } 199 return TRUE; 200 } 201 202 static BOOL WhereDoOption(DWORD flag, LPCWSTR option) 203 { 204 if (s_dwFlags & flag) 205 { 206 ConResPrintf(StdErr, IDS_TOO_MANY, option, 1); 207 ConResPuts(StdErr, IDS_TYPE_HELP); 208 return FALSE; 209 } 210 s_dwFlags |= flag; 211 return TRUE; 212 } 213 214 static BOOL WhereParseCommandLine(INT argc, WCHAR** argv) 215 { 216 INT iArg; 217 for (iArg = 1; iArg < argc; ++iArg) 218 { 219 LPWSTR arg = argv[iArg]; 220 if (arg[0] == L'/' || arg[0] == L'-') 221 { 222 if (arg[2] == 0) 223 { 224 switch (towupper(arg[1])) 225 { 226 case L'?': 227 if (!WhereDoOption(FLAG_HELP, L"/?")) 228 return FALSE; 229 continue; 230 case L'F': 231 if (!WhereDoOption(FLAG_F, L"/F")) 232 return FALSE; 233 continue; 234 case L'Q': 235 if (!WhereDoOption(FLAG_Q, L"/Q")) 236 return FALSE; 237 continue; 238 case L'T': 239 if (!WhereDoOption(FLAG_T, L"/T")) 240 return FALSE; 241 continue; 242 case L'R': 243 { 244 if (!WhereDoOption(FLAG_R, L"/R")) 245 return FALSE; 246 if (iArg + 1 < argc) 247 { 248 ++iArg; 249 s_pszRecursiveDir = argv[iArg]; 250 continue; 251 } 252 ConResPrintf(StdErr, IDS_WANT_VALUE, L"/R"); 253 ConResPuts(StdErr, IDS_TYPE_HELP); 254 return FALSE; 255 } 256 } 257 } 258 ConResPrintf(StdErr, IDS_BAD_ARG, argv[iArg]); 259 ConResPuts(StdErr, IDS_TYPE_HELP); 260 return FALSE; 261 } 262 else // pattern? 263 { 264 if (!strlist_add(&s_patterns, argv[iArg])) // append pattern 265 { 266 ConResPuts(StdErr, IDS_OUTOFMEMORY); 267 return FALSE; 268 } 269 } 270 } 271 272 return TRUE; // success 273 } 274 275 static BOOL WhereGetPathExt(strlist_t *ext_list) 276 { 277 BOOL ret = TRUE; 278 LPWSTR pszPathExt, ext; 279 DWORD cchPathExt = GetEnvironmentVariableW(L"PATHEXT", NULL, 0); 280 281 pszPathExt = (cchPathExt ? malloc(cchPathExt * sizeof(WCHAR)) : str_clone(DEFAULT_PATHEXT)); 282 if (!pszPathExt) 283 return FALSE; // out of memory 284 285 if (cchPathExt) 286 GetEnvironmentVariableW(L"PATHEXT", pszPathExt, cchPathExt); 287 288 if (!strlist_add(ext_list, L"")) // add empty extension for normal search 289 { 290 strlist_destroy(ext_list); 291 free(pszPathExt); 292 return FALSE; 293 } 294 295 for (ext = wcstok(pszPathExt, L";"); ext; ext = wcstok(NULL, L";")) // for all extensions 296 { 297 if (!strlist_add(ext_list, ext)) // add extension to ext_list 298 { 299 strlist_destroy(ext_list); 300 ret = FALSE; 301 break; 302 } 303 } 304 305 free(pszPathExt); 306 return ret; 307 } 308 309 static BOOL WhereFindByDirs(LPCWSTR pattern, LPWSTR dirs) 310 { 311 BOOL ret; 312 size_t cch; 313 WCHAR szPath[MAX_PATH]; 314 LPWSTR dir, pch; 315 strlist_t dirlist = strlist_default; 316 317 GetCurrentDirectoryW(_countof(szPath), szPath); 318 if (!strlist_add(&dirlist, szPath)) 319 return FALSE; // out of memory 320 321 for (dir = wcstok(dirs, L";"); dir; dir = wcstok(NULL, L";")) 322 { 323 if (*dir == L'"') // began from '"' 324 { 325 pch = wcschr(++dir, L'"'); // find '"' 326 if (*pch) 327 *pch = 0; // cut off 328 } 329 330 if (*dir != '\\' && dir[1] != L':') 331 continue; // relative path 332 333 cch = wcslen(dir); 334 if (cch > 0 && dir[cch - 1] == L'\\') 335 dir[cch - 1] = 0; // remove trailing backslash 336 337 if (!strlist_add(&dirlist, dir)) 338 { 339 strlist_destroy(&dirlist); 340 return FALSE; // out of memory 341 } 342 } 343 344 ret = WhereSearch(pattern, &dirlist); 345 strlist_destroy(&dirlist); 346 return ret; 347 } 348 349 static BOOL WhereFindByVar(LPCWSTR pattern, LPCWSTR name) 350 { 351 LPWSTR value; 352 BOOL ret = WhereGetVariable(name, &value); 353 if (ret && value) 354 ret = WhereFindByDirs(pattern, value); 355 free(value); 356 return ret; 357 } 358 359 static BOOL WhereIsRecursiveDirOK(LPCWSTR name) 360 { 361 if (wcschr(name, L';') != NULL) 362 { 363 WhereError(IDS_BAD_NAME); 364 return FALSE; 365 } 366 else 367 { 368 DWORD attrs = GetFileAttributesW(name); 369 if (attrs == INVALID_FILE_ATTRIBUTES) // file not found 370 { 371 WhereError(IDS_CANT_FOUND); 372 return FALSE; 373 } 374 if (!(attrs & FILE_ATTRIBUTE_DIRECTORY)) 375 { 376 WhereError(IDS_BAD_DIR); 377 return FALSE; 378 } 379 return TRUE; 380 } 381 } 382 383 static BOOL WhereDoPattern(LPWSTR pattern) 384 { 385 BOOL ret; 386 LPWSTR pch = wcsrchr(pattern, L':'); 387 if (pch) 388 { 389 *pch++ = 0; 390 if (pattern[0] == L'$') // $env:pattern 391 { 392 if (s_dwFlags & FLAG_R) // recursive? 393 { 394 WhereError(IDS_ENVPAT_WITH_R); 395 return FALSE; 396 } 397 ret = WhereFindByVar(pch, pattern + 1); 398 } 399 else // path:pattern 400 { 401 if (s_dwFlags & FLAG_R) // recursive? 402 { 403 WhereError(IDS_PATHPAT_WITH_R); 404 return FALSE; 405 } 406 if (wcschr(pch, L'\\') != NULL) // found '\\'? 407 { 408 WhereError(IDS_BAD_PATHPAT); 409 return FALSE; 410 } 411 ret = WhereFindByDirs(pch, pattern); 412 } 413 } 414 else if (s_pszRecursiveDir) // recursive 415 { 416 WCHAR szPath[MAX_PATH]; 417 418 if (!WhereIsRecursiveDirOK(s_pszRecursiveDir)) 419 return FALSE; 420 421 GetFullPathNameW(s_pszRecursiveDir, _countof(szPath), szPath, NULL); 422 423 ret = WhereSearchRecursive(pattern, szPath); 424 } 425 else // otherwise 426 { 427 ret = WhereFindByVar(pattern, L"PATH"); 428 } 429 430 if (!ret) 431 WhereError(IDS_OUTOFMEMORY); 432 return ret; 433 } 434 435 INT wmain(INT argc, WCHAR **argv) 436 { 437 typedef BOOL (WINAPI *FN_DISABLE_WOW)(PVOID *); 438 HANDLE hKernel32 = GetModuleHandleA("kernel32"); 439 FN_DISABLE_WOW DisableWOW = 440 (FN_DISABLE_WOW)GetProcAddress(hKernel32, "Wow64DisableWow64FsRedirection"); 441 DWORD iPattern; 442 WRET ret = WRET_ERROR; 443 PVOID dummy; 444 445 ConInitStdStreams(); // Initialize the Console Standard Streams 446 447 if (!WhereParseCommandLine(argc, argv)) 448 goto quit; 449 450 if ((s_dwFlags & FLAG_HELP) || !s_patterns.count) 451 { 452 ConResPuts(StdOut, IDS_USAGE); 453 goto quit; 454 } 455 456 if (DisableWOW) 457 DisableWOW(&dummy); 458 459 if (!WhereGetPathExt(&s_pathext)) 460 { 461 WhereError(IDS_OUTOFMEMORY); 462 goto quit; 463 } 464 465 ret = WRET_SUCCESS; 466 for (iPattern = 0; iPattern < s_patterns.count; ++iPattern) 467 { 468 if (!WhereDoPattern(strlist_get_at(&s_patterns, iPattern))) 469 { 470 ret = WRET_ERROR; 471 goto quit; 472 } 473 } 474 475 if (!s_results.count) 476 { 477 WhereError(IDS_NOT_FOUND); 478 ret = WRET_NOT_FOUND; 479 } 480 481 quit: 482 strlist_destroy(&s_results); 483 strlist_destroy(&s_patterns); 484 strlist_destroy(&s_pathext); 485 return ret; 486 } 487