wine/programs/tasklist/tasklist.c
Zhiyi Zhang db0ccc440a tasklist: Partially support '/fi' option.
Some filters such as STATUS and CPUTIME are not implemented.

Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=48596
2023-05-02 13:33:07 +02:00

448 lines
14 KiB
C

/*
* Copyright 2013 Austin English
* Copyright 2023 Zhiyi Zhang for CodeWeavers
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include <windows.h>
#include <psapi.h>
#include <tlhelp32.h>
#include "tasklist.h"
#include "wine/debug.h"
WINE_DEFAULT_DEBUG_CHANNEL(tasklist);
static int tasklist_message(int msg)
{
WCHAR msg_buffer[MAXSTRING];
LoadStringW(GetModuleHandleW(NULL), msg, msg_buffer, ARRAY_SIZE(msg_buffer));
return wprintf(msg_buffer);
}
static int tasklist_error(int msg)
{
WCHAR msg_buffer[MAXSTRING];
LoadStringW(GetModuleHandleW(NULL), msg, msg_buffer, ARRAY_SIZE(msg_buffer));
return fwprintf(stderr, msg_buffer);
}
static PROCESSENTRY32W *enumerate_processes(DWORD *process_count)
{
unsigned int alloc_count = 128;
PROCESSENTRY32W *process_list;
void *realloc_list;
HANDLE snapshot;
*process_count = 0;
snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (snapshot == INVALID_HANDLE_VALUE)
return NULL;
process_list = malloc(alloc_count * sizeof(*process_list));
if (!process_list)
{
CloseHandle(snapshot);
return NULL;
}
process_list[0].dwSize = sizeof(*process_list);
if (!Process32FirstW(snapshot, &process_list[0]))
{
CloseHandle(snapshot);
free(process_list);
return NULL;
}
do
{
(*process_count)++;
if (*process_count == alloc_count)
{
alloc_count *= 2;
realloc_list = realloc(process_list, alloc_count * sizeof(*process_list));
if (!realloc_list)
{
CloseHandle(snapshot);
free(process_list);
return NULL;
}
process_list = realloc_list;
}
process_list[*process_count].dwSize = sizeof(*process_list);
} while (Process32NextW(snapshot, &process_list[*process_count]));
CloseHandle(snapshot);
return process_list;
}
static NUMBERFMTW *tasklist_get_memory_format(void)
{
static NUMBERFMTW format = {0, 0, 0, NULL, NULL, 1};
static WCHAR grouping[3], decimal[2], thousand[2];
static BOOL initialized;
if (initialized)
return &format;
if (!GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_ILZERO | LOCALE_RETURN_NUMBER,
(WCHAR *)&format.LeadingZero, 2))
format.LeadingZero = 0;
if (!GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SGROUPING, grouping, ARRAY_SIZE(grouping)))
format.Grouping = 3;
else
format.Grouping = (grouping[2] == '2' ? 32 : grouping[0] - '0');
if (!GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SDECIMAL, decimal, ARRAY_SIZE(decimal)))
wcscpy(decimal, L".");
if (!GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_STHOUSAND, thousand, ARRAY_SIZE(thousand)))
wcscpy(thousand, L",");
format.lpDecimalSep = decimal;
format.lpThousandSep = thousand;
initialized = TRUE;
return &format;
}
static void tasklist_get_header(const struct tasklist_options *options,
struct tasklist_process_info *header)
{
HMODULE module;
module = GetModuleHandleW(NULL);
LoadStringW(module, STRING_IMAGE_NAME, header->image_name, ARRAY_SIZE(header->image_name));
LoadStringW(module, STRING_PID, header->pid, ARRAY_SIZE(header->pid));
LoadStringW(module, STRING_SESSION_NAME, header->session_name, ARRAY_SIZE(header->session_name));
LoadStringW(module, STRING_SESSION_NUMBER, header->session_number, ARRAY_SIZE(header->session_number));
LoadStringW(module, STRING_MEM_USAGE, header->memory_usage, ARRAY_SIZE(header->memory_usage));
if (options->format == LIST)
{
wcscat(header->image_name, L":");
wcscat(header->pid, L":");
wcscat(header->session_name, L":");
wcscat(header->session_number, L":");
wcscat(header->memory_usage, L":");
}
}
static BOOL tasklist_get_process_info(const PROCESSENTRY32W *process_entry, struct tasklist_process_info *info)
{
PROCESS_MEMORY_COUNTERS memory_counters;
DWORD session_id;
WCHAR buffer[16];
HANDLE process;
memset(info, 0, sizeof(*info));
if (!ProcessIdToSessionId(process_entry->th32ProcessID, &session_id))
{
WINE_FIXME("Failed to get process session id, %lu.\n", GetLastError());
return FALSE;
}
process = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, process_entry->th32ProcessID);
if (process && GetProcessMemoryInfo(process, &memory_counters, sizeof(memory_counters)))
{
swprintf(buffer, ARRAY_SIZE(buffer), L"%u", memory_counters.WorkingSetSize / 1024);
if (GetNumberFormatW(LOCALE_USER_DEFAULT, 0, buffer, tasklist_get_memory_format(),
info->memory_usage, ARRAY_SIZE(info->memory_usage)))
{
LoadStringW(GetModuleHandleW(NULL), STRING_K, buffer, ARRAY_SIZE(buffer));
wcscat(info->memory_usage, L" ");
wcscat(info->memory_usage, buffer);
}
}
if (process)
CloseHandle(process);
if (info->memory_usage[0] == '\0')
wcscpy(info->memory_usage, L"N/A");
info->pid_value = process_entry->th32ProcessID;
info->memory_usage_value = memory_counters.WorkingSetSize / 1024;
info->session_id_value = session_id;
wcscpy(info->image_name, process_entry->szExeFile);
swprintf(info->pid, ARRAY_SIZE(info->pid), L"%u", process_entry->th32ProcessID);
wcscpy(info->session_name, session_id == 0 ? L"Services" : L"Console");
swprintf(info->session_number, ARRAY_SIZE(info->session_number), L"%u", session_id);
return TRUE;
}
static BOOL tasklist_check_filters(const struct tasklist_filter *filter,
const struct tasklist_process_info *info)
{
DWORD left_dword_operand, right_dword_operand;
const WCHAR *left_string_operand = NULL;
BOOL eval;
while (filter)
{
left_string_operand = NULL;
left_dword_operand = 0;
eval = FALSE;
if (filter->name == IMAGENAME)
left_string_operand = info->image_name;
else if (filter->name == SESSIONNAME)
left_string_operand = info->session_name;
else if (filter->name == PID)
left_dword_operand = info->pid_value;
else if (filter->name == SESSION)
left_dword_operand = info->session_id_value;
else if (filter->name == MEMUSAGE)
left_dword_operand = info->memory_usage_value;
if (left_string_operand)
{
eval = wcsicmp(left_string_operand, filter->value);
if (filter->op == EQ)
eval = !eval;
}
else
{
if (swscanf(filter->value, L"%lu", &right_dword_operand) != 1)
{
WINE_ERR("Invalid filter operand %s.\n", wine_dbgstr_w(filter->value));
return FALSE;
}
if (filter->op == EQ)
eval = left_dword_operand == right_dword_operand;
else if (filter->op == NE)
eval = left_dword_operand != right_dword_operand;
else if (filter->op == GT)
eval = left_dword_operand > right_dword_operand;
else if (filter->op == LT)
eval = left_dword_operand < right_dword_operand;
else if (filter->op == GE)
eval = left_dword_operand >= right_dword_operand;
else if (filter->op == LE)
eval = left_dword_operand <= right_dword_operand;
}
if (!eval)
return FALSE;
filter = filter->next;
}
return TRUE;
}
static void tasklist_print(const struct tasklist_options *options)
{
struct tasklist_process_info header, info;
PROCESSENTRY32W *process_list;
DWORD process_count, i;
if (options->format == TABLE)
wprintf(L"\n");
tasklist_get_header(options, &header);
if (!options->no_header)
{
if (options->format == TABLE)
wprintf(L"%-25.25s %8.8s %-16.16s %11.11s %12.12s\n"
L"========================= ======== ================ =========== ============\n",
header.image_name, header.pid, header.session_name, header.session_number, header.memory_usage);
else if (options->format == CSV)
wprintf(L"\"%s\",\"%s\",\"%s\",\"%s\",\"%s\"\n",
header.image_name, header.pid, header.session_name, header.session_number, header.memory_usage);
}
process_list = enumerate_processes(&process_count);
for (i = 0; i < process_count; ++i)
{
if (!tasklist_get_process_info(&process_list[i], &info))
continue;
if (!tasklist_check_filters(options->filters, &info))
continue;
if (options->format == TABLE)
wprintf(L"%-25.25s %8.8s %-16.16s %11.11s %12s\n",
info.image_name, info.pid, info.session_name, info.session_number, info.memory_usage);
else if (options->format == CSV)
wprintf(L"\"%s\",\"%s\",\"%s\",\"%s\",\"%s\"\n",
info.image_name, info.pid, info.session_name, info.session_number, info.memory_usage);
else if (options->format == LIST)
wprintf(L"\n"
L"%-13.13s %s\n"
L"%-13.13s %s\n"
L"%-13.13s %s\n"
L"%-13.13s %s\n"
L"%-13.13s %s\n",
header.image_name, info.image_name,
header.pid, info.pid,
header.session_name, info.session_name,
header.session_number, info.session_number,
header.memory_usage, info.memory_usage);
}
free(process_list);
}
int __cdecl wmain(int argc, WCHAR *argv[])
{
struct tasklist_options options = {0};
struct tasklist_filter *filter, *next, **filter_ptr = &options.filters;
WCHAR *filter_name, *filter_op, *buffer;
int i, ret = 0;
for (i = 0; i < argc; i++)
WINE_TRACE("%s ", wine_dbgstr_w(argv[i]));
WINE_TRACE("\n");
for (i = 1; i < argc; i++)
{
if (!wcscmp(argv[i], L"/?"))
{
tasklist_message(STRING_USAGE);
goto done;
}
else if (!wcsicmp(argv[i], L"/nh"))
{
options.no_header = TRUE;
}
else if (!wcsicmp(argv[i], L"/fo"))
{
if (i + 1 >= argc)
{
tasklist_error(STRING_INVALID_SYNTAX);
ret = 1;
goto done;
}
else if (!wcsicmp(argv[i + 1], L"TABLE"))
{
options.format = TABLE;
}
else if (!wcsicmp(argv[i + 1], L"CSV"))
{
options.format = CSV;
}
else if (!wcsicmp(argv[i + 1], L"LIST"))
{
options.format = LIST;
}
else
{
tasklist_error(STRING_INVALID_SYNTAX);
ret = 1;
goto done;
}
}
else if (!wcsicmp(argv[i], L"/fi"))
{
if (i + 1 >= argc || !(filter_name = wcstok(argv[i + 1], L" ", &buffer)))
{
tasklist_error(STRING_INVALID_SYNTAX);
ret = 1;
goto done;
}
filter = calloc(1, sizeof(*filter));
if (!filter)
{
WINE_ERR("Out of memory.\n");
ret = 1;
goto done;
}
if (!wcsicmp(filter_name, L"IMAGENAME"))
filter->name = IMAGENAME;
else if (!wcsicmp(filter_name, L"PID"))
filter->name = PID;
else if (!wcsicmp(filter_name, L"SESSION"))
filter->name = SESSION;
else if (!wcsicmp(filter_name, L"SESSIONNAME"))
filter->name = SESSIONNAME;
else if (!wcsicmp(filter_name, L"MEMUSAGE"))
filter->name = MEMUSAGE;
else
{
WINE_WARN("Ignoring filter %s.\n", wine_dbgstr_w(filter_name));
free(filter);
continue;
}
filter_op = wcstok(NULL, L" ", &buffer);
if (!filter_op)
{
tasklist_error(STRING_FILTER_NOT_RECOGNIZED);
free(filter);
ret = 1;
goto done;
}
if (!wcsicmp(filter_op, L"EQ"))
filter->op = EQ;
else if (!wcsicmp(filter_op, L"NE"))
filter->op = NE;
else if (!wcsicmp(filter_op, L"GT"))
filter->op = GT;
else if (!wcsicmp(filter_op, L"LT"))
filter->op = LT;
else if (!wcsicmp(filter_op, L"GE"))
filter->op = GE;
else if (!wcsicmp(filter_op, L"LE"))
filter->op = LE;
else
{
tasklist_error(STRING_FILTER_NOT_RECOGNIZED);
free(filter);
ret = 1;
goto done;
}
if (filter->op >= GT && filter->name != PID && filter->name != SESSION && filter->name != MEMUSAGE)
{
tasklist_error(STRING_FILTER_NOT_RECOGNIZED);
free(filter);
ret = 1;
goto done;
}
filter->value = wcstok(NULL, L" ", &buffer);
if (!filter->value)
{
tasklist_error(STRING_FILTER_NOT_RECOGNIZED);
free(filter);
ret = 1;
goto done;
}
*filter_ptr = filter;
filter_ptr = &filter->next;
}
else
{
WINE_WARN("Ignoring option %s\n", wine_dbgstr_w(argv[i]));
}
}
tasklist_print(&options);
done:
next = options.filters;
while (next)
{
filter = next->next;
free(next);
next = filter;
}
return ret;
}