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
WhereError(UINT nID)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
WhereSearchGeneric(LPCWSTR pattern,LPWSTR path,size_t path_len,BOOL bDir,WHERE_CALLBACK callback)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
WherePrintPath(LPCWSTR pattern,LPCWSTR path,PWIN32_FIND_DATAW data)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
WhereSearchFiles(LPCWSTR pattern,LPCWSTR dir)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
WhereSearchRecursiveCallback(LPCWSTR pattern,LPCWSTR path,PWIN32_FIND_DATAW data)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.
WhereSearchRecursive(LPCWSTR pattern,LPCWSTR dir)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
WhereSearch(LPCWSTR pattern,strlist_t * dirlist)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
WhereGetVariable(LPCWSTR name,LPWSTR * value)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
WhereDoOption(DWORD flag,LPCWSTR option)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
WhereParseCommandLine(INT argc,WCHAR ** argv)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
WhereGetPathExt(strlist_t * ext_list)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
WhereFindByDirs(LPCWSTR pattern,LPWSTR dirs)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
WhereFindByVar(LPCWSTR pattern,LPCWSTR name)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
WhereIsRecursiveDirOK(LPCWSTR name)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
WhereDoPattern(LPWSTR pattern)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
wmain(INT argc,WCHAR ** argv)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