mirror of
https://github.com/reactos/reactos.git
synced 2024-10-30 19:41:57 +00:00
487 lines
13 KiB
C
487 lines
13 KiB
C
|
/*
|
||
|
* PROJECT: ReactOS WHERE command
|
||
|
* LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
|
||
|
* PURPOSE: Search executable files
|
||
|
* COPYRIGHT: Copyright 2021 Katayama Hirofumi MZ <katayama.hirofumi.mz@gmail.com>
|
||
|
*/
|
||
|
|
||
|
#include <stdlib.h>
|
||
|
#include <windef.h>
|
||
|
#include <winbase.h>
|
||
|
#include <winnls.h>
|
||
|
#include <strsafe.h>
|
||
|
#include <conutils.h>
|
||
|
#include "strlist.h" // strlist_...
|
||
|
#include "resource.h"
|
||
|
|
||
|
#define FLAG_HELP (1 << 0) // "/?"
|
||
|
#define FLAG_R (1 << 1) // recursive directory
|
||
|
#define FLAG_Q (1 << 2) // quiet mode
|
||
|
#define FLAG_F (1 << 3) // double quote
|
||
|
#define FLAG_T (1 << 4) // detailed info
|
||
|
|
||
|
static DWORD s_dwFlags = 0;
|
||
|
static LPWSTR s_pszRecursiveDir = NULL;
|
||
|
static strlist_t s_patterns = strlist_default;
|
||
|
static strlist_t s_results = strlist_default;
|
||
|
static strlist_t s_pathext = strlist_default;
|
||
|
|
||
|
// is it either "." or ".."?
|
||
|
#define IS_DOTS(pch) \
|
||
|
(*(pch) == L'.' && ((pch)[1] == 0 || ((pch)[1] == L'.' && (pch)[2] == 0)))
|
||
|
|
||
|
#define DEFAULT_PATHEXT L".com;.exe;.bat;.cmd"
|
||
|
|
||
|
typedef enum WRET // return code of WHERE command
|
||
|
{
|
||
|
WRET_SUCCESS = 0,
|
||
|
WRET_NOT_FOUND = 1,
|
||
|
WRET_ERROR = 2
|
||
|
} WRET;
|
||
|
|
||
|
static VOID WhereError(UINT nID)
|
||
|
{
|
||
|
if (!(s_dwFlags & FLAG_Q)) // not quiet mode?
|
||
|
ConResPuts(StdErr, nID);
|
||
|
}
|
||
|
|
||
|
typedef BOOL (CALLBACK *WHERE_CALLBACK)(LPCWSTR pattern, LPCWSTR path, PWIN32_FIND_DATAW data);
|
||
|
|
||
|
static BOOL
|
||
|
WhereSearchGeneric(LPCWSTR pattern, LPWSTR path, size_t path_len, BOOL bDir,
|
||
|
WHERE_CALLBACK callback)
|
||
|
{
|
||
|
LPWSTR pch;
|
||
|
size_t cch;
|
||
|
BOOL ret;
|
||
|
WIN32_FIND_DATAW data;
|
||
|
HANDLE hFind = FindFirstFileExW(path, FindExInfoStandard, &data, FindExSearchNameMatch,
|
||
|
NULL, 0);
|
||
|
if (hFind == INVALID_HANDLE_VALUE)
|
||
|
return TRUE; // not found
|
||
|
|
||
|
pch = wcsrchr(path, L'\\') + 1;
|
||
|
cch = path_len - (pch - path);
|
||
|
do
|
||
|
{
|
||
|
if (bDir != !!(data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
|
||
|
continue;
|
||
|
if (bDir && IS_DOTS(data.cFileName))
|
||
|
continue; // ignore "." and ".."
|
||
|
if (data.dwFileAttributes & FILE_ATTRIBUTE_VIRTUAL)
|
||
|
continue; // ignore virtual
|
||
|
|
||
|
StringCchCopyW(pch, cch, data.cFileName); // build full path
|
||
|
|
||
|
ret = callback(pattern, path, &data);
|
||
|
if (!ret) // out of memory
|
||
|
break;
|
||
|
} while (FindNextFileW(hFind, &data));
|
||
|
FindClose(hFind);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static BOOL CALLBACK WherePrintPath(LPCWSTR pattern, LPCWSTR path, PWIN32_FIND_DATAW data)
|
||
|
{
|
||
|
WCHAR szPath[MAX_PATH + 2], szDate[32], szTime[32];
|
||
|
LARGE_INTEGER FileSize;
|
||
|
FILETIME ftLocal;
|
||
|
SYSTEMTIME st;
|
||
|
|
||
|
if (strlist_find_i(&s_results, path) >= 0)
|
||
|
return TRUE; // already exists
|
||
|
if (!strlist_add(&s_results, path))
|
||
|
return FALSE; // out of memory
|
||
|
if (s_dwFlags & FLAG_Q) // quiet mode?
|
||
|
return TRUE;
|
||
|
|
||
|
if (s_dwFlags & FLAG_T) // print detailed info
|
||
|
{
|
||
|
// convert date/time
|
||
|
FileTimeToLocalFileTime(&data->ftLastWriteTime, &ftLocal);
|
||
|
FileTimeToSystemTime(&ftLocal, &st);
|
||
|
// get date/time strings
|
||
|
GetDateFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL, szDate, _countof(szDate));
|
||
|
GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL, szTime, _countof(szTime));
|
||
|
// set size
|
||
|
FileSize.LowPart = data->nFileSizeLow;
|
||
|
FileSize.HighPart = data->nFileSizeHigh;
|
||
|
// print
|
||
|
if (s_dwFlags & FLAG_F) // double quote
|
||
|
StringCchPrintfW(szPath, _countof(szPath), L"\"%s\"", path);
|
||
|
else
|
||
|
StringCchCopyW(szPath, _countof(szPath), path);
|
||
|
ConResPrintf(StdOut, IDS_FILE_INFO, FileSize.QuadPart, szDate, szTime, szPath);
|
||
|
}
|
||
|
else // print path only
|
||
|
{
|
||
|
if (s_dwFlags & FLAG_F) // double quote
|
||
|
ConPrintf(StdOut, L"\"%ls\"\n", path);
|
||
|
else
|
||
|
ConPrintf(StdOut, L"%ls\n", path);
|
||
|
}
|
||
|
return TRUE; // success
|
||
|
}
|
||
|
|
||
|
static BOOL WhereSearchFiles(LPCWSTR pattern, LPCWSTR dir)
|
||
|
{
|
||
|
INT iExt;
|
||
|
size_t cch;
|
||
|
WCHAR szPath[MAX_PATH];
|
||
|
StringCchCopyW(szPath, _countof(szPath), dir);
|
||
|
StringCchCatW(szPath, _countof(szPath), L"\\");
|
||
|
StringCchCatW(szPath, _countof(szPath), pattern);
|
||
|
cch = wcslen(szPath);
|
||
|
|
||
|
for (iExt = 0; iExt < s_pathext.count; ++iExt)
|
||
|
{
|
||
|
szPath[cch] = 0; // cut off extension
|
||
|
// append extension
|
||
|
StringCchCatW(szPath, _countof(szPath), strlist_get_at(&s_pathext, iExt));
|
||
|
|
||
|
if (!WhereSearchGeneric(pattern, szPath, _countof(szPath), FALSE, WherePrintPath))
|
||
|
return FALSE;
|
||
|
}
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
static BOOL WhereSearchRecursive(LPCWSTR pattern, LPCWSTR dir);
|
||
|
|
||
|
static BOOL CALLBACK
|
||
|
WhereSearchRecursiveCallback(LPCWSTR pattern, LPCWSTR path, PWIN32_FIND_DATAW data)
|
||
|
{
|
||
|
return WhereSearchRecursive(pattern, path);
|
||
|
}
|
||
|
|
||
|
// FIXME: Too slow. Optimize for speed.
|
||
|
static BOOL WhereSearchRecursive(LPCWSTR pattern, LPCWSTR dir)
|
||
|
{
|
||
|
WCHAR szPath[MAX_PATH];
|
||
|
if (!WhereSearchFiles(pattern, dir))
|
||
|
return FALSE; // out of memory
|
||
|
|
||
|
// build path with wildcard
|
||
|
StringCchCopyW(szPath, _countof(szPath), dir);
|
||
|
StringCchCatW(szPath, _countof(szPath), L"\\*");
|
||
|
return WhereSearchGeneric(pattern, szPath, _countof(szPath), TRUE,
|
||
|
WhereSearchRecursiveCallback);
|
||
|
}
|
||
|
|
||
|
static BOOL WhereSearch(LPCWSTR pattern, strlist_t *dirlist)
|
||
|
{
|
||
|
UINT iDir;
|
||
|
for (iDir = 0; iDir < dirlist->count; ++iDir)
|
||
|
{
|
||
|
if (!WhereSearchFiles(pattern, strlist_get_at(dirlist, iDir)))
|
||
|
return FALSE;
|
||
|
}
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
static BOOL WhereGetVariable(LPCWSTR name, LPWSTR *value)
|
||
|
{
|
||
|
DWORD cch = GetEnvironmentVariableW(name, NULL, 0);
|
||
|
if (cch == 0) // variable not found
|
||
|
{
|
||
|
*value = NULL;
|
||
|
if (!(s_dwFlags & FLAG_Q)) // not quiet mode?
|
||
|
ConResPrintf(StdErr, IDS_BAD_ENVVAR, name);
|
||
|
return TRUE; // it is error, but continue the task
|
||
|
}
|
||
|
|
||
|
*value = malloc(cch * sizeof(WCHAR));
|
||
|
if (!*value || !GetEnvironmentVariableW(name, *value, cch))
|
||
|
{
|
||
|
free(*value);
|
||
|
*value = NULL;
|
||
|
return FALSE; // error
|
||
|
}
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
static BOOL WhereDoOption(DWORD flag, LPCWSTR option)
|
||
|
{
|
||
|
if (s_dwFlags & flag)
|
||
|
{
|
||
|
ConResPrintf(StdErr, IDS_TOO_MANY, option, 1);
|
||
|
ConResPuts(StdErr, IDS_TYPE_HELP);
|
||
|
return FALSE;
|
||
|
}
|
||
|
s_dwFlags |= flag;
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
static BOOL WhereParseCommandLine(INT argc, WCHAR** argv)
|
||
|
{
|
||
|
INT iArg;
|
||
|
for (iArg = 1; iArg < argc; ++iArg)
|
||
|
{
|
||
|
LPWSTR arg = argv[iArg];
|
||
|
if (arg[0] == L'/' || arg[0] == L'-')
|
||
|
{
|
||
|
if (arg[2] == 0)
|
||
|
{
|
||
|
switch (towupper(arg[1]))
|
||
|
{
|
||
|
case L'?':
|
||
|
if (!WhereDoOption(FLAG_HELP, L"/?"))
|
||
|
return FALSE;
|
||
|
continue;
|
||
|
case L'F':
|
||
|
if (!WhereDoOption(FLAG_F, L"/F"))
|
||
|
return FALSE;
|
||
|
continue;
|
||
|
case L'Q':
|
||
|
if (!WhereDoOption(FLAG_Q, L"/Q"))
|
||
|
return FALSE;
|
||
|
continue;
|
||
|
case L'T':
|
||
|
if (!WhereDoOption(FLAG_T, L"/T"))
|
||
|
return FALSE;
|
||
|
continue;
|
||
|
case L'R':
|
||
|
{
|
||
|
if (!WhereDoOption(FLAG_R, L"/R"))
|
||
|
return FALSE;
|
||
|
if (iArg + 1 < argc)
|
||
|
{
|
||
|
++iArg;
|
||
|
s_pszRecursiveDir = argv[iArg];
|
||
|
continue;
|
||
|
}
|
||
|
ConResPrintf(StdErr, IDS_WANT_VALUE, L"/R");
|
||
|
ConResPuts(StdErr, IDS_TYPE_HELP);
|
||
|
return FALSE;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
ConResPrintf(StdErr, IDS_BAD_ARG, argv[iArg]);
|
||
|
ConResPuts(StdErr, IDS_TYPE_HELP);
|
||
|
return FALSE;
|
||
|
}
|
||
|
else // pattern?
|
||
|
{
|
||
|
if (!strlist_add(&s_patterns, argv[iArg])) // append pattern
|
||
|
{
|
||
|
ConResPuts(StdErr, IDS_OUTOFMEMORY);
|
||
|
return FALSE;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return TRUE; // success
|
||
|
}
|
||
|
|
||
|
static BOOL WhereGetPathExt(strlist_t *ext_list)
|
||
|
{
|
||
|
BOOL ret = TRUE;
|
||
|
LPWSTR pszPathExt, ext;
|
||
|
DWORD cchPathExt = GetEnvironmentVariableW(L"PATHEXT", NULL, 0);
|
||
|
|
||
|
pszPathExt = (cchPathExt ? malloc(cchPathExt * sizeof(WCHAR)) : str_clone(DEFAULT_PATHEXT));
|
||
|
if (!pszPathExt)
|
||
|
return FALSE; // out of memory
|
||
|
|
||
|
if (cchPathExt)
|
||
|
GetEnvironmentVariableW(L"PATHEXT", pszPathExt, cchPathExt);
|
||
|
|
||
|
if (!strlist_add(ext_list, L"")) // add empty extension for normal search
|
||
|
{
|
||
|
strlist_destroy(ext_list);
|
||
|
free(pszPathExt);
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
for (ext = wcstok(pszPathExt, L";"); ext; ext = wcstok(NULL, L";")) // for all extensions
|
||
|
{
|
||
|
if (!strlist_add(ext_list, ext)) // add extension to ext_list
|
||
|
{
|
||
|
strlist_destroy(ext_list);
|
||
|
ret = FALSE;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
free(pszPathExt);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static BOOL WhereFindByDirs(LPCWSTR pattern, LPWSTR dirs)
|
||
|
{
|
||
|
BOOL ret;
|
||
|
size_t cch;
|
||
|
WCHAR szPath[MAX_PATH];
|
||
|
LPWSTR dir, pch;
|
||
|
strlist_t dirlist = strlist_default;
|
||
|
|
||
|
GetCurrentDirectoryW(_countof(szPath), szPath);
|
||
|
if (!strlist_add(&dirlist, szPath))
|
||
|
return FALSE; // out of memory
|
||
|
|
||
|
for (dir = wcstok(dirs, L";"); dir; dir = wcstok(NULL, L";"))
|
||
|
{
|
||
|
if (*dir == L'"') // began from '"'
|
||
|
{
|
||
|
pch = wcschr(++dir, L'"'); // find '"'
|
||
|
if (*pch)
|
||
|
*pch = 0; // cut off
|
||
|
}
|
||
|
|
||
|
if (*dir != '\\' && dir[1] != L':')
|
||
|
continue; // relative path
|
||
|
|
||
|
cch = wcslen(dir);
|
||
|
if (cch > 0 && dir[cch - 1] == L'\\')
|
||
|
dir[cch - 1] = 0; // remove trailing backslash
|
||
|
|
||
|
if (!strlist_add(&dirlist, dir))
|
||
|
{
|
||
|
strlist_destroy(&dirlist);
|
||
|
return FALSE; // out of memory
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ret = WhereSearch(pattern, &dirlist);
|
||
|
strlist_destroy(&dirlist);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static BOOL WhereFindByVar(LPCWSTR pattern, LPCWSTR name)
|
||
|
{
|
||
|
LPWSTR value;
|
||
|
BOOL ret = WhereGetVariable(name, &value);
|
||
|
if (ret && value)
|
||
|
ret = WhereFindByDirs(pattern, value);
|
||
|
free(value);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static BOOL WhereIsRecursiveDirOK(LPCWSTR name)
|
||
|
{
|
||
|
if (wcschr(name, L';') != NULL)
|
||
|
{
|
||
|
WhereError(IDS_BAD_NAME);
|
||
|
return FALSE;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
DWORD attrs = GetFileAttributesW(name);
|
||
|
if (attrs == INVALID_FILE_ATTRIBUTES) // file not found
|
||
|
{
|
||
|
WhereError(IDS_CANT_FOUND);
|
||
|
return FALSE;
|
||
|
}
|
||
|
if (!(attrs & FILE_ATTRIBUTE_DIRECTORY))
|
||
|
{
|
||
|
WhereError(IDS_BAD_DIR);
|
||
|
return FALSE;
|
||
|
}
|
||
|
return TRUE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static BOOL WhereDoPattern(LPWSTR pattern)
|
||
|
{
|
||
|
BOOL ret;
|
||
|
LPWSTR pch = wcsrchr(pattern, L':');
|
||
|
if (pch)
|
||
|
{
|
||
|
*pch++ = 0;
|
||
|
if (pattern[0] == L'$') // $env:pattern
|
||
|
{
|
||
|
if (s_dwFlags & FLAG_R) // recursive?
|
||
|
{
|
||
|
WhereError(IDS_ENVPAT_WITH_R);
|
||
|
return FALSE;
|
||
|
}
|
||
|
ret = WhereFindByVar(pch, pattern + 1);
|
||
|
}
|
||
|
else // path:pattern
|
||
|
{
|
||
|
if (s_dwFlags & FLAG_R) // recursive?
|
||
|
{
|
||
|
WhereError(IDS_PATHPAT_WITH_R);
|
||
|
return FALSE;
|
||
|
}
|
||
|
if (wcschr(pch, L'\\') != NULL) // found '\\'?
|
||
|
{
|
||
|
WhereError(IDS_BAD_PATHPAT);
|
||
|
return FALSE;
|
||
|
}
|
||
|
ret = WhereFindByDirs(pch, pattern);
|
||
|
}
|
||
|
}
|
||
|
else if (s_pszRecursiveDir) // recursive
|
||
|
{
|
||
|
WCHAR szPath[MAX_PATH];
|
||
|
|
||
|
if (!WhereIsRecursiveDirOK(s_pszRecursiveDir))
|
||
|
return FALSE;
|
||
|
|
||
|
GetFullPathNameW(s_pszRecursiveDir, _countof(szPath), szPath, NULL);
|
||
|
|
||
|
ret = WhereSearchRecursive(pattern, szPath);
|
||
|
}
|
||
|
else // otherwise
|
||
|
{
|
||
|
ret = WhereFindByVar(pattern, L"PATH");
|
||
|
}
|
||
|
|
||
|
if (!ret)
|
||
|
WhereError(IDS_OUTOFMEMORY);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
INT wmain(INT argc, WCHAR **argv)
|
||
|
{
|
||
|
typedef BOOL (WINAPI *FN_DISABLE_WOW)(PVOID *);
|
||
|
HANDLE hKernel32 = GetModuleHandleA("kernel32");
|
||
|
FN_DISABLE_WOW DisableWOW =
|
||
|
(FN_DISABLE_WOW)GetProcAddress(hKernel32, "Wow64DisableWow64FsRedirection");
|
||
|
DWORD iPattern;
|
||
|
WRET ret = WRET_ERROR;
|
||
|
PVOID dummy;
|
||
|
|
||
|
ConInitStdStreams(); // Initialize the Console Standard Streams
|
||
|
|
||
|
if (!WhereParseCommandLine(argc, argv))
|
||
|
goto quit;
|
||
|
|
||
|
if ((s_dwFlags & FLAG_HELP) || !s_patterns.count)
|
||
|
{
|
||
|
ConResPuts(StdOut, IDS_USAGE);
|
||
|
goto quit;
|
||
|
}
|
||
|
|
||
|
if (DisableWOW)
|
||
|
DisableWOW(&dummy);
|
||
|
|
||
|
if (!WhereGetPathExt(&s_pathext))
|
||
|
{
|
||
|
WhereError(IDS_OUTOFMEMORY);
|
||
|
goto quit;
|
||
|
}
|
||
|
|
||
|
ret = WRET_SUCCESS;
|
||
|
for (iPattern = 0; iPattern < s_patterns.count; ++iPattern)
|
||
|
{
|
||
|
if (!WhereDoPattern(strlist_get_at(&s_patterns, iPattern)))
|
||
|
{
|
||
|
ret = WRET_ERROR;
|
||
|
goto quit;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!s_results.count)
|
||
|
{
|
||
|
WhereError(IDS_NOT_FOUND);
|
||
|
ret = WRET_NOT_FOUND;
|
||
|
}
|
||
|
|
||
|
quit:
|
||
|
strlist_destroy(&s_results);
|
||
|
strlist_destroy(&s_patterns);
|
||
|
strlist_destroy(&s_pathext);
|
||
|
return ret;
|
||
|
}
|