1 /*
2  * PROJECT:     ReactOS Tasklist Command
3  * LICENSE:     GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
4  * PURPOSE:     Displays a list of currently running processes on the computer.
5  * COPYRIGHT:   Copyright 2021 He Yang <1160386205@qq.com>
6  */
7 
8 #include "tasklist.h"
9 
10 // the strings in OptionList are the command-line options.
11 // should always correspond with the defines below, in sequence (except OPTION_INVALID)
12 static PCWSTR OptionList[] = { L"?", L"nh" };
13 
14 #define OPTION_INVALID    -1
15 #define OPTION_HELP       0
16 #define OPTION_NOHEADER    1
17 
18 // the max string length PrintResString can handle
19 #define RES_STR_MAXLEN 64
20 
21 // Print split line
PrintSplitLine(UINT Length)22 VOID PrintSplitLine(UINT Length)
23 {
24     for (; Length; Length--)
25     {
26         ConPuts(StdOut, L"=");
27     }
28 }
29 
30 // Print spaces
PrintSpace(UINT Length)31 VOID PrintSpace(UINT Length)
32 {
33     ConPrintf(StdOut, L"%*ls", (INT)Length, L"");
34 }
35 
36 // Print a string.
37 // if bAlignLeft == TRUE then aligned to left, otherwise aligned to right
38 // MaxWidth is the width for printing.
PrintString(LPCWSTR String,UINT MaxWidth,BOOL bAlignLeft)39 VOID PrintString(LPCWSTR String, UINT MaxWidth, BOOL bAlignLeft)
40 {
41     ConPrintf(StdOut, bAlignLeft ? L"%-*.*ls" : L"%*.*ls", MaxWidth, MaxWidth, String);
42 }
43 
44 // Print a string from resource
45 // if bAlignLeft == TRUE then aligned to left, otherwise aligned to right
46 // MaxWidth is the width for printing.
47 // The string WILL be truncated if it's longer than RES_STR_MAXLEN
PrintResString(HINSTANCE hInstance,UINT uID,UINT MaxWidth,BOOL bAlignLeft)48 VOID PrintResString(HINSTANCE hInstance, UINT uID, UINT MaxWidth, BOOL bAlignLeft)
49 {
50     if (!hInstance)
51         return;
52 
53     WCHAR StringBuffer[RES_STR_MAXLEN];
54     LoadStringW(hInstance, uID, StringBuffer, _countof(StringBuffer));
55     PrintString(StringBuffer, MaxWidth, bAlignLeft);
56 }
57 
58 // Print a number, aligned to right.
59 // MaxWidth is the width for printing.
60 // the number WILL NOT be truncated if it's longer than MaxWidth
PrintNum(LONGLONG Number,UINT MaxWidth)61 VOID PrintNum(LONGLONG Number, UINT MaxWidth)
62 {
63     ConPrintf(StdOut, L"%*lld", MaxWidth, Number);
64 }
65 
66 // Print memory size using KB as unit, with comma-separated number, aligned to right.
67 // MaxWidth is the width for printing.
68 // the number WILL be truncated if it's longer than MaxWidth
PrintMemory(SIZE_T MemorySizeByte,UINT MaxWidth,HINSTANCE hInstance)69 BOOL PrintMemory(SIZE_T MemorySizeByte, UINT MaxWidth, HINSTANCE hInstance)
70 {
71     if (!hInstance)
72         return FALSE;
73 
74     SIZE_T MemorySize = MemorySizeByte >> 10;
75 
76     WCHAR NumberString[27] = { 0 }; // length 26 is enough to display ULLONG_MAX in decimal with comma, one more for zero-terminated.
77     C_ASSERT(sizeof(SIZE_T) <= 8);
78 
79     PWCHAR pNumberStr = NumberString;
80 
81     // calculate the length
82     UINT PrintLength = 0;
83     SIZE_T Tmp = MemorySize;
84     UINT Mod = 1;
85 
86     do
87     {
88         Tmp /= 10;
89         PrintLength++;
90         Mod *= 10;
91     } while (Tmp);
92 
93     for (UINT i = PrintLength; i; i--)
94     {
95         Mod /= 10;
96         *pNumberStr = L'0' + (MemorySize / Mod);
97         MemorySize %= Mod;
98         pNumberStr++;
99 
100         if (i != 1 && i % 3 == 1)
101         {
102             *pNumberStr = L',';
103             pNumberStr++;
104         }
105     }
106 
107     WCHAR FormatStr[RES_STR_MAXLEN];
108     LoadStringW(hInstance, IDS_MEMORY_STR, FormatStr, _countof(FormatStr));
109 
110     WCHAR String[RES_STR_MAXLEN + _countof(NumberString)] = { 0 };
111 
112     StringCchPrintfW(String, _countof(String), FormatStr, NumberString);
113     PrintString(String, MaxWidth, FALSE);
114 
115     return TRUE;
116 }
117 
PrintHeader(HINSTANCE hInstance)118 VOID PrintHeader(HINSTANCE hInstance)
119 {
120     if (!hInstance)
121         return;
122 
123     PrintResString(hInstance, IDS_HEADER_IMAGENAME, COLUMNWIDTH_IMAGENAME, TRUE);
124     PrintSpace(1);
125     PrintResString(hInstance, IDS_HEADER_PID,       COLUMNWIDTH_PID,       FALSE);
126     PrintSpace(1);
127     PrintResString(hInstance, IDS_HEADER_SESSION,   COLUMNWIDTH_SESSION,   FALSE);
128     PrintSpace(1);
129     PrintResString(hInstance, IDS_HEADER_MEMUSAGE,  COLUMNWIDTH_MEMUSAGE,  FALSE);
130 
131     ConPuts(StdOut, L"\n");
132 
133     PrintSplitLine(COLUMNWIDTH_IMAGENAME);
134     PrintSpace(1);
135     PrintSplitLine(COLUMNWIDTH_PID);
136     PrintSpace(1);
137     PrintSplitLine(COLUMNWIDTH_SESSION);
138     PrintSpace(1);
139     PrintSplitLine(COLUMNWIDTH_MEMUSAGE);
140 
141     ConPuts(StdOut, L"\n");
142 }
143 
EnumProcessAndPrint(BOOL bNoHeader)144 BOOL EnumProcessAndPrint(BOOL bNoHeader)
145 {
146     // Call NtQuerySystemInformation for the process information
147     ULONG ProcessInfoBufferLength = 0;
148     ULONG ResultLength = 0;
149     PBYTE ProcessInfoBuffer = NULL;
150 
151     // Get the buffer size we need
152     NTSTATUS Status = NtQuerySystemInformation(SystemProcessInformation, NULL, 0, &ResultLength);
153 
154     // New process/thread might appear before we call for the actual data.
155     // Try to avoid this by retrying several times.
156     for (UINT Retry = 0; Retry < NT_SYSTEM_QUERY_MAX_RETRY; Retry++)
157     {
158         // (Re)allocate buffer
159         ProcessInfoBufferLength = ResultLength;
160         ResultLength = 0;
161         if (ProcessInfoBuffer)
162         {
163             PBYTE NewProcessInfoBuffer = HeapReAlloc(GetProcessHeap(), 0,
164                                                      ProcessInfoBuffer,
165                                                      ProcessInfoBufferLength);
166             if (NewProcessInfoBuffer)
167             {
168                 ProcessInfoBuffer = NewProcessInfoBuffer;
169             }
170             else
171             {
172                 // out of memory
173                 ConResMsgPrintf(StdErr, 0, IDS_OUT_OF_MEMORY);
174                 HeapFree(GetProcessHeap(), 0, ProcessInfoBuffer);
175                 return FALSE;
176             }
177         }
178         else
179         {
180             ProcessInfoBuffer = HeapAlloc(GetProcessHeap(), 0, ProcessInfoBufferLength);
181             if (!ProcessInfoBuffer)
182             {
183                 // out of memory
184                 ConResMsgPrintf(StdErr, 0, IDS_OUT_OF_MEMORY);
185                 return FALSE;
186             }
187         }
188 
189         // Query information
190         Status = NtQuerySystemInformation(SystemProcessInformation,
191                                           ProcessInfoBuffer,
192                                           ProcessInfoBufferLength,
193                                           &ResultLength);
194         if (Status != STATUS_INFO_LENGTH_MISMATCH)
195         {
196             break;
197         }
198     }
199 
200     if (!NT_SUCCESS(Status))
201     {
202         // tried NT_SYSTEM_QUERY_MAX_RETRY times, or failed with some other reason
203         ConResMsgPrintf(StdErr, 0, IDS_ENUM_FAILED);
204         HeapFree(GetProcessHeap(), 0, ProcessInfoBuffer);
205         return FALSE;
206     }
207 
208     HINSTANCE hInstance = GetModuleHandleW(NULL);
209     assert(hInstance);
210 
211     ConPuts(StdOut, L"\n");
212 
213     if (!bNoHeader)
214     {
215         PrintHeader(hInstance);
216     }
217 
218     PSYSTEM_PROCESS_INFORMATION pSPI;
219     pSPI = (PSYSTEM_PROCESS_INFORMATION)ProcessInfoBuffer;
220     while (pSPI)
221     {
222         PrintString(pSPI->UniqueProcessId ? pSPI->ImageName.Buffer : L"System Idle Process", COLUMNWIDTH_IMAGENAME, TRUE);
223         PrintSpace(1);
224         PrintNum((LONGLONG)(INT_PTR)pSPI->UniqueProcessId, COLUMNWIDTH_PID);
225         PrintSpace(1);
226         PrintNum((ULONGLONG)pSPI->SessionId, COLUMNWIDTH_SESSION);
227         PrintSpace(1);
228         PrintMemory(pSPI->WorkingSetSize, COLUMNWIDTH_MEMUSAGE, hInstance);
229 
230         ConPuts(StdOut, L"\n");
231 
232         if (pSPI->NextEntryOffset == 0)
233             break;
234         pSPI = (PSYSTEM_PROCESS_INFORMATION)((LPBYTE)pSPI + pSPI->NextEntryOffset);
235     }
236 
237     HeapFree(GetProcessHeap(), 0, ProcessInfoBuffer);
238     return TRUE;
239 }
240 
GetOptionType(LPCWSTR szOption)241 INT GetOptionType(LPCWSTR szOption)
242 {
243     if (szOption[0] != L'/' && szOption[0] != L'-')
244     {
245         return OPTION_INVALID;
246     }
247     szOption++;
248 
249     for (UINT i = 0; i < _countof(OptionList); i++)
250     {
251         if (!_wcsicmp(OptionList[i], szOption))
252         {
253             return i;
254         }
255     }
256     return OPTION_INVALID;
257 }
258 
ProcessArguments(INT argc,WCHAR ** argv)259 BOOL ProcessArguments(INT argc, WCHAR **argv)
260 {
261     BOOL bHasHelp = FALSE, bHasNoHeader = FALSE;
262 
263     for (INT i = 1; i < argc; i++)
264     {
265         INT Option = GetOptionType(argv[i]);
266 
267         switch (Option)
268         {
269             case OPTION_HELP:
270             {
271                 if (bHasHelp)
272                 {
273                     // -? already specified
274                     ConResMsgPrintf(StdErr, 0, IDS_OPTION_TOO_MUCH, argv[i], 1);
275                     ConResMsgPrintf(StdErr, 0, IDS_USAGE);
276                     return FALSE;
277                 }
278                 bHasHelp = TRUE;
279                 break;
280             }
281             case OPTION_NOHEADER:
282             {
283                 if (bHasNoHeader)
284                 {
285                     // -nh already specified
286                     ConResMsgPrintf(StdErr, 0, IDS_OPTION_TOO_MUCH, argv[i], 1);
287                     ConResMsgPrintf(StdErr, 0, IDS_USAGE);
288                     return FALSE;
289                 }
290                 bHasNoHeader = TRUE;
291                 break;
292             }
293             case OPTION_INVALID:
294             default:
295             {
296                 ConResMsgPrintf(StdErr, 0, IDS_INVALID_OPTION);
297                 ConResMsgPrintf(StdErr, 0, IDS_USAGE);
298                 return FALSE;
299             }
300         }
301     }
302 
303     if (bHasHelp)
304     {
305         if (argc > 2) // any arguments other than -? exists
306         {
307             ConResMsgPrintf(StdErr, 0, IDS_INVALID_SYNTAX);
308             ConResMsgPrintf(StdErr, 0, IDS_USAGE);
309             return FALSE;
310         }
311         else
312         {
313             ConResMsgPrintf(StdOut, 0, IDS_USAGE);
314             ConResMsgPrintf(StdOut, 0, IDS_DESCRIPTION);
315             return FALSE;
316         }
317     }
318     else
319     {
320         EnumProcessAndPrint(bHasNoHeader);
321     }
322     return TRUE;
323 }
324 
wmain(int argc,WCHAR ** argv)325 int wmain(int argc, WCHAR **argv)
326 {
327     /* Initialize the Console Standard Streams */
328     ConInitStdStreams();
329 
330     if (!ProcessArguments(argc, argv))
331     {
332         return 1;
333     }
334     return 0;
335 }
336