blob: 910443e9978adc0532a0ead5160f26c79c8b1032 [file] [log] [blame]
Hisham Muhammadd6231ba2006-03-04 18:16:49 +00001/*
2htop - Process.c
Hisham Muhammadb1b3f572015-03-21 16:52:54 -03003(C) 2004-2015 Hisham H. Muhammad
Nathan Scott500fb282020-08-20 09:35:24 +10004(C) 2020 Red Hat, Inc. All Rights Reserved.
Daniel Lange94ad1112021-09-22 11:33:00 +02005Released under the GNU GPLv2+, see the COPYING file
Hisham Muhammadd6231ba2006-03-04 18:16:49 +00006in the source distribution for its full text.
7*/
8
Benny Baumann0f526292020-09-19 13:55:23 +02009#include "config.h" // IWYU pragma: keep
Hisham Muhammad272e2d92015-03-16 23:01:48 -030010
Benny Baumann0f526292020-09-19 13:55:23 +020011#include "Process.h"
12
13#include <assert.h>
Benny Baumann0f526292020-09-19 13:55:23 +020014#include <math.h>
15#include <signal.h>
16#include <stdbool.h>
17#include <stdio.h>
18#include <stdlib.h>
mayurdahibhate1b74dfe2021-04-29 20:42:43 +053019#include <string.h>
Benny Baumann0f526292020-09-19 13:55:23 +020020#include <time.h>
Benny Baumann0f526292020-09-19 13:55:23 +020021#include <sys/resource.h>
Explorer0935129712018-12-30 12:18:27 +080022
Hisham Muhammadd6231ba2006-03-04 18:16:49 +000023#include "CRT.h"
Benny Baumanne56089e2023-11-28 15:15:03 +010024#include "Hashtable.h"
25#include "Machine.h"
Christian Göttsche7cf52772020-11-18 14:26:30 +010026#include "Macros.h"
Nathan Scottb74673f2023-08-31 11:56:43 +100027#include "ProcessTable.h"
Sohaib Mohamed6f2021f2021-07-11 03:11:29 +020028#include "DynamicColumn.h"
Benny Baumann0f526292020-09-19 13:55:23 +020029#include "RichString.h"
Christian Göttscheda494892023-01-10 19:40:04 +010030#include "Scheduling.h"
Benny Baumann0f526292020-09-19 13:55:23 +020031#include "Settings.h"
Benny Baumanne56089e2023-11-28 15:15:03 +010032#include "Table.h"
Benny Baumann872e5422020-10-14 20:21:09 +020033#include "XUtils.h"
Hisham Muhammadd6231ba2006-03-04 18:16:49 +000034
Benny Baumann0f526292020-09-19 13:55:23 +020035#if defined(MAJOR_IN_MKDEV)
Kang-Che Sung (宋岡哲)c01f40e2018-02-26 21:15:05 +080036#include <sys/mkdev.h>
Kang-Che Sung (宋岡哲)c01f40e2018-02-26 21:15:05 +080037#endif
Hisham Muhammadd6231ba2006-03-04 18:16:49 +000038
Benny Baumann0f526292020-09-19 13:55:23 +020039
Benny Baumannaa8552b2021-04-18 19:25:56 +020040/* Used to identify kernel threads in Comm and Exe columns */
Benny Baumann976c6122021-07-14 19:24:18 +020041static const char* const kthreadID = "KTHREAD";
Benny Baumannaa8552b2021-04-18 19:25:56 +020042
Christian Göttschea63cfc82020-10-13 14:26:40 +020043void Process_fillStarttimeBuffer(Process* this) {
44 struct tm date;
Nathan Scott0f751e92023-08-22 16:11:05 +100045 time_t now = this->super.host->realtime.tv_sec;
Christian Göttschea63cfc82020-10-13 14:26:40 +020046 (void) localtime_r(&this->starttime_ctime, &date);
arza78c7b1c2022-10-20 17:20:51 +030047
48 strftime(this->starttime_show,
49 sizeof(this->starttime_show) - 1,
Benny Baumann6aa9ef22023-11-23 12:22:02 +010050 (this->starttime_ctime > now - 86400) ? "%R " : (this->starttime_ctime > now - 364 * 86400) ? "%b%d " : " %Y ",
arza78c7b1c2022-10-20 17:20:51 +030051 &date);
Christian Göttschea63cfc82020-10-13 14:26:40 +020052}
53
Benny Baumannbcb18ef2021-04-10 13:31:39 +020054/*
55 * TASK_COMM_LEN is defined to be 16 for /proc/[pid]/comm in man proc(5), but it is
56 * not available in an userspace header - so define it.
57 *
58 * Note: This is taken from LINUX headers, but implicitly taken for other platforms
59 * for sake of brevity.
60 *
61 * Note: when colorizing a basename with the comm prefix, the entire basename
62 * (not just the comm prefix) is colorized for better readability, and it is
Benny Baumannea23eee2023-06-16 11:12:47 +020063 * implicit that only up to (TASK_COMM_LEN - 1) could be comm.
Benny Baumannbcb18ef2021-04-10 13:31:39 +020064 */
65#define TASK_COMM_LEN 16
Tobias Geerinckx-Rice293eec42015-07-29 21:14:29 +020066
Benny Baumann976c6122021-07-14 19:24:18 +020067static bool findCommInCmdline(const char* comm, const char* cmdline, int cmdlineBasenameStart, int* pCommStart, int* pCommEnd) {
Benny Baumannbcb18ef2021-04-10 13:31:39 +020068 /* Try to find procComm in tokenized cmdline - this might in rare cases
69 * mis-identify a string or fail, if comm or cmdline had been unsuitably
70 * modified by the process */
Benny Baumann976c6122021-07-14 19:24:18 +020071 const char* tokenBase;
Benny Baumannbcb18ef2021-04-10 13:31:39 +020072 size_t tokenLen;
73 const size_t commLen = strlen(comm);
74
75 if (cmdlineBasenameStart < 0)
76 return false;
77
Benny Baumann976c6122021-07-14 19:24:18 +020078 for (const char* token = cmdline + cmdlineBasenameStart; *token;) {
Benny Baumannbcb18ef2021-04-10 13:31:39 +020079 for (tokenBase = token; *token && *token != '\n'; ++token) {
80 if (*token == '/') {
81 tokenBase = token + 1;
Hisham Muhammadf2a190b2014-02-27 17:11:23 -030082 }
Hisham Muhammadd6231ba2006-03-04 18:16:49 +000083 }
Benny Baumannbcb18ef2021-04-10 13:31:39 +020084 tokenLen = token - tokenBase;
85
86 if ((tokenLen == commLen || (tokenLen > commLen && commLen == (TASK_COMM_LEN - 1))) &&
87 strncmp(tokenBase, comm, commLen) == 0) {
88 *pCommStart = tokenBase - cmdline;
89 *pCommEnd = token - cmdline;
90 return true;
91 }
92
93 if (*token) {
94 do {
95 ++token;
96 } while (*token && '\n' == *token);
Tobias Geerinckx-Rice293eec42015-07-29 21:14:29 +020097 }
Hisham Muhammadd6231ba2006-03-04 18:16:49 +000098 }
Benny Baumannbcb18ef2021-04-10 13:31:39 +020099 return false;
100}
Tobias Geerinckx-Rice293eec42015-07-29 21:14:29 +0200101
Benny Baumann976c6122021-07-14 19:24:18 +0200102static int matchCmdlinePrefixWithExeSuffix(const char* cmdline, int cmdlineBaseOffset, const char* exe, int exeBaseOffset, int exeBaseLen) {
Benny Baumannbcb18ef2021-04-10 13:31:39 +0200103 int matchLen; /* matching length to be returned */
104 char delim; /* delimiter following basename */
Tobias Geerinckx-Rice293eec42015-07-29 21:14:29 +0200105
Benny Baumannbcb18ef2021-04-10 13:31:39 +0200106 /* cmdline prefix is an absolute path: it must match whole exe. */
107 if (cmdline[0] == '/') {
108 matchLen = exeBaseLen + exeBaseOffset;
109 if (strncmp(cmdline, exe, matchLen) == 0) {
110 delim = cmdline[matchLen];
111 if (delim == 0 || delim == '\n' || delim == ' ') {
112 return matchLen;
113 }
114 }
115 return 0;
116 }
117
118 /* cmdline prefix is a relative path: We need to first match the basename at
119 * cmdlineBaseOffset and then reverse match the cmdline prefix with the exe
120 * suffix. But there is a catch: Some processes modify their cmdline in ways
121 * that make htop's identification of the basename in cmdline unreliable.
122 * For e.g. /usr/libexec/gdm-session-worker modifies its cmdline to
123 * "gdm-session-worker [pam/gdm-autologin]" and htop ends up with
124 * proccmdlineBasenameEnd at "gdm-autologin]". This issue could arise with
125 * chrome as well as it stores in cmdline its concatenated argument vector,
126 * without NUL delimiter between the arguments (which may contain a '/')
127 *
128 * So if needed, we adjust cmdlineBaseOffset to the previous (if any)
129 * component of the cmdline relative path, and retry the procedure. */
130 bool delimFound; /* if valid basename delimiter found */
131 do {
132 /* match basename */
133 matchLen = exeBaseLen + cmdlineBaseOffset;
134 if (cmdlineBaseOffset < exeBaseOffset &&
135 strncmp(cmdline + cmdlineBaseOffset, exe + exeBaseOffset, exeBaseLen) == 0) {
136 delim = cmdline[matchLen];
137 if (delim == 0 || delim == '\n' || delim == ' ') {
138 int i, j;
139 /* reverse match the cmdline prefix and exe suffix */
140 for (i = cmdlineBaseOffset - 1, j = exeBaseOffset - 1;
141 i >= 0 && j >= 0 && cmdline[i] == exe[j]; --i, --j)
142 ;
143
144 /* full match, with exe suffix being a valid relative path */
145 if (i < 0 && j >= 0 && exe[j] == '/')
146 return matchLen;
147 }
148 }
149
150 /* Try to find the previous potential cmdlineBaseOffset - it would be
151 * preceded by '/' or nothing, and delimited by ' ' or '\n' */
152 for (delimFound = false, cmdlineBaseOffset -= 2; cmdlineBaseOffset > 0; --cmdlineBaseOffset) {
153 if (delimFound) {
154 if (cmdline[cmdlineBaseOffset - 1] == '/') {
155 break;
156 }
157 } else if (cmdline[cmdlineBaseOffset] == ' ' || cmdline[cmdlineBaseOffset] == '\n') {
158 delimFound = true;
159 }
160 }
161 } while (delimFound);
162
163 return 0;
164}
165
166/* stpcpy, but also converts newlines to spaces */
Benny Baumann976c6122021-07-14 19:24:18 +0200167static inline char* stpcpyWithNewlineConversion(char* dstStr, const char* srcStr) {
Benny Baumannbcb18ef2021-04-10 13:31:39 +0200168 for (; *srcStr; ++srcStr) {
169 *dstStr++ = (*srcStr == '\n') ? ' ' : *srcStr;
170 }
171 *dstStr = 0;
172 return dstStr;
173}
174
175/*
176 * This function makes the merged Command string. It also stores the offsets of the
177 * basename, comm w.r.t the merged Command string - these offsets will be used by
178 * Process_writeCommand() for coloring. The merged Command string is also
Christian Göttsche1ef8c0e2021-12-16 15:57:37 +0100179 * returned by Process_getCommand() for searching, sorting and filtering.
Benny Baumannbcb18ef2021-04-10 13:31:39 +0200180 */
Nathan Scott0f751e92023-08-22 16:11:05 +1000181void Process_makeCommandStr(Process* this, const Settings* settings) {
Benny Baumann976c6122021-07-14 19:24:18 +0200182 ProcessMergedCommand* mc = &this->mergedCommand;
Benny Baumannbcb18ef2021-04-10 13:31:39 +0200183
184 bool showMergedCommand = settings->showMergedCommand;
185 bool showProgramPath = settings->showProgramPath;
186 bool searchCommInCmdline = settings->findCommInCmdline;
187 bool stripExeFromCmdline = settings->stripExeFromCmdline;
Benny Baumann7bfd62b2021-07-17 20:59:50 +0200188 bool showThreadNames = settings->showThreadNames;
Christian Göttsche623ee312021-05-23 16:04:43 +0200189 bool shadowDistPathPrefix = settings->shadowDistPathPrefix;
Benny Baumannbcb18ef2021-04-10 13:31:39 +0200190
Benny Baumann2999fff2022-05-29 23:03:56 +0200191 uint64_t settingsStamp = settings->lastUpdate;
192
Benny Baumanna61a2e62021-04-18 18:10:04 +0200193 /* Nothing to do to (Re)Generate the Command string, if the process is:
194 * - a kernel thread, or
195 * - a zombie from before being under htop's watch, or
196 * - a user thread and showThreadNames is not set */
197 if (Process_isKernelThread(this))
198 return;
marcluqued8dfbbd2021-10-12 00:45:09 +0200199 if (this->state == ZOMBIE && !this->mergedCommand.str)
Benny Baumanna61a2e62021-04-18 18:10:04 +0200200 return;
Benny Baumanna61a2e62021-04-18 18:10:04 +0200201
Benny Baumannbcb18ef2021-04-10 13:31:39 +0200202 /* this->mergedCommand.str needs updating only if its state or contents changed.
203 * Its content is based on the fields cmdline, comm, and exe. */
Benny Baumann2999fff2022-05-29 23:03:56 +0200204 if (mc->lastUpdate >= settingsStamp)
Benny Baumannbcb18ef2021-04-10 13:31:39 +0200205 return;
Benny Baumann2999fff2022-05-29 23:03:56 +0200206
207 mc->lastUpdate = settingsStamp;
Benny Baumannbcb18ef2021-04-10 13:31:39 +0200208
Stefanf66f04e2023-03-30 11:25:46 +0200209 /* The field separator "│" has been chosen such that it will not match any
Benny Baumannbcb18ef2021-04-10 13:31:39 +0200210 * valid string used for searching or filtering */
Benny Baumann976c6122021-07-14 19:24:18 +0200211 const char* SEPARATOR = CRT_treeStr[TREE_STR_VERT];
Benny Baumannbcb18ef2021-04-10 13:31:39 +0200212 const int SEPARATOR_LEN = strlen(SEPARATOR);
213
Benny Baumann2999fff2022-05-29 23:03:56 +0200214 /* Accommodate the column text, two field separators and terminating NUL */
215 size_t maxLen = 2 * SEPARATOR_LEN + 1;
216 maxLen += this->cmdline ? strlen(this->cmdline) : strlen("(zombie)");
217 maxLen += this->procComm ? strlen(this->procComm) : 0;
218 maxLen += this->procExe ? strlen(this->procExe) : 0;
Benny Baumann7ef58f22021-05-17 23:15:24 +0200219
Benny Baumann2999fff2022-05-29 23:03:56 +0200220 free(mc->str);
221 mc->str = xCalloc(1, maxLen);
Benny Baumannbcb18ef2021-04-10 13:31:39 +0200222
223 /* Reset all locations that need extra handling when actually displaying */
224 mc->highlightCount = 0;
225 memset(mc->highlights, 0, sizeof(mc->highlights));
226
227 size_t mbMismatch = 0;
Benny Baumann9a781552021-05-15 21:54:46 +0200228 #define WRITE_HIGHLIGHT(_offset, _length, _attr, _flags) \
229 do { \
230 /* Check if we still have capacity */ \
231 assert(mc->highlightCount < ARRAYSIZE(mc->highlights)); \
232 if (mc->highlightCount >= ARRAYSIZE(mc->highlights)) \
Christian Göttsche5dec9472021-08-22 17:14:36 +0200233 break; \
Benny Baumann9a781552021-05-15 21:54:46 +0200234 \
235 mc->highlights[mc->highlightCount].offset = str - strStart + (_offset) - mbMismatch; \
236 mc->highlights[mc->highlightCount].length = _length; \
237 mc->highlights[mc->highlightCount].attr = _attr; \
238 mc->highlights[mc->highlightCount].flags = _flags; \
239 mc->highlightCount++; \
Benny Baumannbcb18ef2021-04-10 13:31:39 +0200240 } while (0)
241
Benny Baumann9a781552021-05-15 21:54:46 +0200242 #define WRITE_SEPARATOR \
243 do { \
244 WRITE_HIGHLIGHT(0, 1, CRT_colors[FAILED_READ], CMDLINE_HIGHLIGHT_FLAG_SEPARATOR); \
245 mbMismatch += SEPARATOR_LEN - 1; \
246 str = stpcpy(str, SEPARATOR); \
Benny Baumannbcb18ef2021-04-10 13:31:39 +0200247 } while (0)
248
Christian Göttsche623ee312021-05-23 16:04:43 +0200249 #define CHECK_AND_MARK(str_, prefix_) \
250 if (String_startsWith(str_, prefix_)) { \
251 WRITE_HIGHLIGHT(0, strlen(prefix_), CRT_colors[PROCESS_SHADOW], CMDLINE_HIGHLIGHT_FLAG_PREFIXDIR); \
252 break; \
253 } else (void)0
254
255 #define CHECK_AND_MARK_DIST_PATH_PREFIXES(str_) \
256 do { \
257 if ((str_)[0] != '/') { \
258 break; \
259 } \
260 switch ((str_)[1]) { \
261 case 'b': \
262 CHECK_AND_MARK(str_, "/bin/"); \
263 break; \
264 case 'l': \
265 CHECK_AND_MARK(str_, "/lib/"); \
266 CHECK_AND_MARK(str_, "/lib32/"); \
267 CHECK_AND_MARK(str_, "/lib64/"); \
268 CHECK_AND_MARK(str_, "/libx32/"); \
269 break; \
270 case 's': \
271 CHECK_AND_MARK(str_, "/sbin/"); \
272 break; \
273 case 'u': \
274 if (String_startsWith(str_, "/usr/")) { \
275 switch ((str_)[5]) { \
276 case 'b': \
277 CHECK_AND_MARK(str_, "/usr/bin/"); \
278 break; \
279 case 'l': \
280 CHECK_AND_MARK(str_, "/usr/libexec/"); \
281 CHECK_AND_MARK(str_, "/usr/lib/"); \
282 CHECK_AND_MARK(str_, "/usr/lib32/"); \
283 CHECK_AND_MARK(str_, "/usr/lib64/"); \
284 CHECK_AND_MARK(str_, "/usr/libx32/"); \
285 \
286 CHECK_AND_MARK(str_, "/usr/local/bin/"); \
287 CHECK_AND_MARK(str_, "/usr/local/lib/"); \
288 CHECK_AND_MARK(str_, "/usr/local/sbin/"); \
289 break; \
290 case 's': \
291 CHECK_AND_MARK(str_, "/usr/sbin/"); \
292 break; \
293 } \
294 } \
295 break; \
296 } \
297 } while (0)
298
Benny Baumannbcb18ef2021-04-10 13:31:39 +0200299 const int baseAttr = Process_isThread(this) ? CRT_colors[PROCESS_THREAD_BASENAME] : CRT_colors[PROCESS_BASENAME];
300 const int commAttr = Process_isThread(this) ? CRT_colors[PROCESS_THREAD_COMM] : CRT_colors[PROCESS_COMM];
Christian Göttsche81541252021-04-04 18:07:26 +0200301 const int delExeAttr = CRT_colors[FAILED_READ];
302 const int delLibAttr = CRT_colors[PROCESS_TAG];
Benny Baumannbcb18ef2021-04-10 13:31:39 +0200303
304 /* Establish some shortcuts to data we need */
Benny Baumann976c6122021-07-14 19:24:18 +0200305 const char* cmdline = this->cmdline;
306 const char* procComm = this->procComm;
307 const char* procExe = this->procExe;
Benny Baumannbcb18ef2021-04-10 13:31:39 +0200308
Benny Baumann976c6122021-07-14 19:24:18 +0200309 char* strStart = mc->str;
310 char* str = strStart;
Benny Baumannbcb18ef2021-04-10 13:31:39 +0200311
312 int cmdlineBasenameStart = this->cmdlineBasenameStart;
313 int cmdlineBasenameEnd = this->cmdlineBasenameEnd;
314
315 if (!cmdline) {
316 cmdlineBasenameStart = 0;
317 cmdlineBasenameEnd = 0;
318 cmdline = "(zombie)";
319 }
320
321 assert(cmdlineBasenameStart >= 0);
322 assert(cmdlineBasenameStart <= (int)strlen(cmdline));
323
324 if (!showMergedCommand || !procExe || !procComm) { /* fall back to cmdline */
Jan Kończak8c996832022-01-05 14:27:51 +0100325 if ((showMergedCommand || (Process_isUserlandThread(this) && showThreadNames)) && procComm && strlen(procComm)) { /* set column to or prefix it with comm */
Benny Baumannbcb18ef2021-04-10 13:31:39 +0200326 if (strncmp(cmdline + cmdlineBasenameStart, procComm, MINIMUM(TASK_COMM_LEN - 1, strlen(procComm))) != 0) {
327 WRITE_HIGHLIGHT(0, strlen(procComm), commAttr, CMDLINE_HIGHLIGHT_FLAG_COMM);
328 str = stpcpy(str, procComm);
329
Benny Baumann40104582022-10-22 19:19:39 +0200330 if (!showMergedCommand)
Jan Kończak8c996832022-01-05 14:27:51 +0100331 return;
332
Benny Baumannbcb18ef2021-04-10 13:31:39 +0200333 WRITE_SEPARATOR;
334 }
335 }
336
Christian Göttsche623ee312021-05-23 16:04:43 +0200337 if (shadowDistPathPrefix && showProgramPath)
338 CHECK_AND_MARK_DIST_PATH_PREFIXES(cmdline);
339
Benny Baumann2824e292021-05-15 21:55:14 +0200340 if (cmdlineBasenameEnd > cmdlineBasenameStart)
341 WRITE_HIGHLIGHT(showProgramPath ? cmdlineBasenameStart : 0, cmdlineBasenameEnd - cmdlineBasenameStart, baseAttr, CMDLINE_HIGHLIGHT_FLAG_BASENAME);
Benny Baumannb7248f62021-10-06 16:38:45 +0200342
343 if (this->procExeDeleted)
344 WRITE_HIGHLIGHT(showProgramPath ? cmdlineBasenameStart : 0, cmdlineBasenameEnd - cmdlineBasenameStart, delExeAttr, CMDLINE_HIGHLIGHT_FLAG_DELETED);
345 else if (this->usesDeletedLib)
346 WRITE_HIGHLIGHT(showProgramPath ? cmdlineBasenameStart : 0, cmdlineBasenameEnd - cmdlineBasenameStart, delLibAttr, CMDLINE_HIGHLIGHT_FLAG_DELETED);
347
Benny Baumann2824e292021-05-15 21:55:14 +0200348 (void)stpcpyWithNewlineConversion(str, cmdline + (showProgramPath ? 0 : cmdlineBasenameStart));
Benny Baumannbcb18ef2021-04-10 13:31:39 +0200349
350 return;
351 }
352
353 int exeLen = strlen(this->procExe);
354 int exeBasenameOffset = this->procExeBasenameOffset;
355 int exeBasenameLen = exeLen - exeBasenameOffset;
356
357 assert(exeBasenameOffset >= 0);
358 assert(exeBasenameOffset <= (int)strlen(procExe));
359
360 bool haveCommInExe = false;
Christian Göttschece27f832021-08-08 16:04:26 +0200361 if (procExe && procComm && (!Process_isUserlandThread(this) || showThreadNames)) {
Benny Baumannbcb18ef2021-04-10 13:31:39 +0200362 haveCommInExe = strncmp(procExe + exeBasenameOffset, procComm, TASK_COMM_LEN - 1) == 0;
363 }
364
365 /* Start with copying exe */
366 if (showProgramPath) {
Christian Göttsche623ee312021-05-23 16:04:43 +0200367 if (shadowDistPathPrefix)
368 CHECK_AND_MARK_DIST_PATH_PREFIXES(procExe);
Benny Baumannbcb18ef2021-04-10 13:31:39 +0200369 if (haveCommInExe)
370 WRITE_HIGHLIGHT(exeBasenameOffset, exeBasenameLen, commAttr, CMDLINE_HIGHLIGHT_FLAG_COMM);
371 WRITE_HIGHLIGHT(exeBasenameOffset, exeBasenameLen, baseAttr, CMDLINE_HIGHLIGHT_FLAG_BASENAME);
372 if (this->procExeDeleted)
Christian Göttsche81541252021-04-04 18:07:26 +0200373 WRITE_HIGHLIGHT(exeBasenameOffset, exeBasenameLen, delExeAttr, CMDLINE_HIGHLIGHT_FLAG_DELETED);
374 else if (this->usesDeletedLib)
375 WRITE_HIGHLIGHT(exeBasenameOffset, exeBasenameLen, delLibAttr, CMDLINE_HIGHLIGHT_FLAG_DELETED);
Benny Baumannbcb18ef2021-04-10 13:31:39 +0200376 str = stpcpy(str, procExe);
377 } else {
378 if (haveCommInExe)
379 WRITE_HIGHLIGHT(0, exeBasenameLen, commAttr, CMDLINE_HIGHLIGHT_FLAG_COMM);
380 WRITE_HIGHLIGHT(0, exeBasenameLen, baseAttr, CMDLINE_HIGHLIGHT_FLAG_BASENAME);
381 if (this->procExeDeleted)
Christian Göttsche81541252021-04-04 18:07:26 +0200382 WRITE_HIGHLIGHT(0, exeBasenameLen, delExeAttr, CMDLINE_HIGHLIGHT_FLAG_DELETED);
383 else if (this->usesDeletedLib)
384 WRITE_HIGHLIGHT(0, exeBasenameLen, delLibAttr, CMDLINE_HIGHLIGHT_FLAG_DELETED);
Benny Baumannbcb18ef2021-04-10 13:31:39 +0200385 str = stpcpy(str, procExe + exeBasenameOffset);
386 }
387
388 bool haveCommInCmdline = false;
389 int commStart = 0;
390 int commEnd = 0;
391
392 /* Try to match procComm with procExe's basename: This is reliable (predictable) */
393 if (searchCommInCmdline) {
394 /* commStart/commEnd will be adjusted later along with cmdline */
Christian Göttschece27f832021-08-08 16:04:26 +0200395 haveCommInCmdline = (!Process_isUserlandThread(this) || showThreadNames) && findCommInCmdline(procComm, cmdline, cmdlineBasenameStart, &commStart, &commEnd);
Benny Baumannbcb18ef2021-04-10 13:31:39 +0200396 }
397
398 int matchLen = matchCmdlinePrefixWithExeSuffix(cmdline, cmdlineBasenameStart, procExe, exeBasenameOffset, exeBasenameLen);
399
400 bool haveCommField = false;
401
Christian Göttschece27f832021-08-08 16:04:26 +0200402 if (!haveCommInExe && !haveCommInCmdline && procComm && (!Process_isUserlandThread(this) || showThreadNames)) {
Benny Baumannbcb18ef2021-04-10 13:31:39 +0200403 WRITE_SEPARATOR;
404 WRITE_HIGHLIGHT(0, strlen(procComm), commAttr, CMDLINE_HIGHLIGHT_FLAG_COMM);
405 str = stpcpy(str, procComm);
406 haveCommField = true;
407 }
408
409 if (matchLen) {
Benny Baumann9eed3092022-05-30 22:38:59 +0200410 if (stripExeFromCmdline) {
411 /* strip the matched exe prefix */
412 cmdline += matchLen;
Benny Baumannbcb18ef2021-04-10 13:31:39 +0200413
Benny Baumann9eed3092022-05-30 22:38:59 +0200414 commStart -= matchLen;
415 commEnd -= matchLen;
416 } else {
417 matchLen = 0;
418 }
Benny Baumannbcb18ef2021-04-10 13:31:39 +0200419 }
420
421 if (!matchLen || (haveCommField && *cmdline)) {
422 /* cmdline will be a separate field */
423 WRITE_SEPARATOR;
424 }
425
Christian Göttsche623ee312021-05-23 16:04:43 +0200426 if (shadowDistPathPrefix)
427 CHECK_AND_MARK_DIST_PATH_PREFIXES(cmdline);
428
Christian Göttschece27f832021-08-08 16:04:26 +0200429 if (!haveCommInExe && haveCommInCmdline && !haveCommField && (!Process_isUserlandThread(this) || showThreadNames))
Benny Baumannbcb18ef2021-04-10 13:31:39 +0200430 WRITE_HIGHLIGHT(commStart, commEnd - commStart, commAttr, CMDLINE_HIGHLIGHT_FLAG_COMM);
431
432 /* Display cmdline if it hasn't been consumed by procExe */
433 if (*cmdline)
434 (void)stpcpyWithNewlineConversion(str, cmdline);
435
Christian Göttsche623ee312021-05-23 16:04:43 +0200436 #undef CHECK_AND_MARK_DIST_PATH_PREFIXES
437 #undef CHECK_AND_MARK
Benny Baumannbcb18ef2021-04-10 13:31:39 +0200438 #undef WRITE_SEPARATOR
439 #undef WRITE_HIGHLIGHT
440}
441
442void Process_writeCommand(const Process* this, int attr, int baseAttr, RichString* str) {
443 (void)baseAttr;
444
Benny Baumann976c6122021-07-14 19:24:18 +0200445 const ProcessMergedCommand* mc = &this->mergedCommand;
Christian Göttsche623ee312021-05-23 16:04:43 +0200446 const char* mergedCommand = mc->str;
Benny Baumannbcb18ef2021-04-10 13:31:39 +0200447
448 int strStart = RichString_size(str);
449
Nathan Scott0f751e92023-08-22 16:11:05 +1000450 const Settings* settings = this->super.host->settings;
Nathan Scott0bdade12023-05-02 09:02:22 +1000451 const bool highlightBaseName = settings->highlightBaseName;
Benny Baumannbcb18ef2021-04-10 13:31:39 +0200452 const bool highlightSeparator = true;
Nathan Scott0bdade12023-05-02 09:02:22 +1000453 const bool highlightDeleted = settings->highlightDeletedExe;
Benny Baumannbcb18ef2021-04-10 13:31:39 +0200454
Christian Göttsche623ee312021-05-23 16:04:43 +0200455 if (!mergedCommand) {
Benny Baumannbcb18ef2021-04-10 13:31:39 +0200456 int len = 0;
457 const char* cmdline = this->cmdline;
458
Nathan Scott0bdade12023-05-02 09:02:22 +1000459 if (highlightBaseName || !settings->showProgramPath) {
Benny Baumannbcb18ef2021-04-10 13:31:39 +0200460 int basename = 0;
461 for (int i = 0; i < this->cmdlineBasenameEnd; i++) {
462 if (cmdline[i] == '/') {
463 basename = i + 1;
464 } else if (cmdline[i] == ':') {
465 len = i + 1;
466 break;
467 }
468 }
469 if (len == 0) {
Nathan Scott0bdade12023-05-02 09:02:22 +1000470 if (settings->showProgramPath) {
Benny Baumannbcb18ef2021-04-10 13:31:39 +0200471 strStart += basename;
472 } else {
473 cmdline += basename;
474 }
475 len = this->cmdlineBasenameEnd - basename;
476 }
477 }
478
479 RichString_appendWide(str, attr, cmdline);
480
Nathan Scott0bdade12023-05-02 09:02:22 +1000481 if (settings->highlightBaseName) {
Benny Baumannbcb18ef2021-04-10 13:31:39 +0200482 RichString_setAttrn(str, baseAttr, strStart, len);
483 }
484
485 return;
486 }
487
Christian Göttsche623ee312021-05-23 16:04:43 +0200488 RichString_appendWide(str, attr, mergedCommand);
Benny Baumannbcb18ef2021-04-10 13:31:39 +0200489
490 for (size_t i = 0, hlCount = CLAMP(mc->highlightCount, 0, ARRAYSIZE(mc->highlights)); i < hlCount; i++) {
Benny Baumann976c6122021-07-14 19:24:18 +0200491 const ProcessCmdlineHighlight* hl = &mc->highlights[i];
Benny Baumannbcb18ef2021-04-10 13:31:39 +0200492
493 if (!hl->length)
494 continue;
495
496 if (hl->flags & CMDLINE_HIGHLIGHT_FLAG_SEPARATOR)
497 if (!highlightSeparator)
498 continue;
499
500 if (hl->flags & CMDLINE_HIGHLIGHT_FLAG_BASENAME)
501 if (!highlightBaseName)
502 continue;
503
504 if (hl->flags & CMDLINE_HIGHLIGHT_FLAG_DELETED)
505 if (!highlightDeleted)
506 continue;
507
Christian Göttsche623ee312021-05-23 16:04:43 +0200508 if (hl->flags & CMDLINE_HIGHLIGHT_FLAG_PREFIXDIR)
509 if (!highlightDeleted)
510 continue;
511
Benny Baumannbcb18ef2021-04-10 13:31:39 +0200512 RichString_setAttrn(str, hl->attr, strStart + hl->offset, hl->length);
Benny Baumann45869512020-11-01 01:09:51 +0100513 }
Hisham Muhammadd6231ba2006-03-04 18:16:49 +0000514}
515
marcluqued8dfbbd2021-10-12 00:45:09 +0200516static inline char processStateChar(ProcessState state) {
517 switch (state) {
518 case UNKNOWN: return '?';
519 case RUNNABLE: return 'U';
520 case RUNNING: return 'R';
521 case QUEUED: return 'Q';
522 case WAITING: return 'W';
523 case UNINTERRUPTIBLE_WAIT: return 'D';
524 case BLOCKED: return 'B';
525 case PAGING: return 'P';
526 case STOPPED: return 'T';
527 case TRACED: return 't';
528 case ZOMBIE: return 'Z';
529 case DEFUNCT: return 'X';
530 case IDLE: return 'I';
531 case SLEEPING: return 'S';
532 default:
533 assert(0);
534 return '!';
535 }
536}
537
Nathan Scott0f751e92023-08-22 16:11:05 +1000538static void Process_rowWriteField(const Row* super, RichString* str, RowField field) {
539 const Process* this = (const Process*) super;
540 assert(Object_isA((const Object*) this, (const ObjectClass*) &Process_class));
541 Process_writeField(this, str, field);
542}
543
544void Process_writeField(const Process* this, RichString* str, RowField field) {
Nathan Scott0f751e92023-08-22 16:11:05 +1000545 const Row* super = (const Row*) &this->super;
546 const Machine* host = super->host;
547 const Settings* settings = host->settings;
Benny Baumann2aacbf82023-11-29 21:27:07 +0100548
Nathan Scott0bdade12023-05-02 09:02:22 +1000549 bool coloring = settings->highlightMegabytes;
Benny Baumann2aacbf82023-11-29 21:27:07 +0100550 char buffer[256]; buffer[255] = '\0';
551 int attr = CRT_colors[DEFAULT_COLOR];
552 size_t n = sizeof(buffer) - 1;
Hisham Muhammadd6231ba2006-03-04 18:16:49 +0000553
554 switch (field) {
Hisham Muhammadd6231ba2006-03-04 18:16:49 +0000555 case COMM: {
Christian Göttschefee744a2021-01-27 15:11:46 +0100556 int baseattr = CRT_colors[PROCESS_BASENAME];
Nathan Scott0bdade12023-05-02 09:02:22 +1000557 if (settings->highlightThreads && Process_isThread(this)) {
Hisham Muhammad93f091c2008-03-08 23:39:48 +0000558 attr = CRT_colors[PROCESS_THREAD];
559 baseattr = CRT_colors[PROCESS_THREAD_BASENAME];
560 }
Nathan Scott0bdade12023-05-02 09:02:22 +1000561 const ScreenSettings* ss = settings->ss;
Nathan Scott0f751e92023-08-22 16:11:05 +1000562 if (!ss->treeView || super->indent == 0) {
Hisham Muhammad93f091c2008-03-08 23:39:48 +0000563 Process_writeCommand(this, attr, baseattr, str);
Hisham Muhammadd6231ba2006-03-04 18:16:49 +0000564 return;
Hisham Muhammadd6231ba2006-03-04 18:16:49 +0000565 }
Christian Göttschefee744a2021-01-27 15:11:46 +0100566
567 char* buf = buffer;
Nathan Scott0f751e92023-08-22 16:11:05 +1000568 const bool lastItem = (super->indent < 0);
Christian Göttschefee744a2021-01-27 15:11:46 +0100569
Nathan Scott0f751e92023-08-22 16:11:05 +1000570 for (uint32_t indent = (super->indent < 0 ? -super->indent : super->indent); indent > 1; indent >>= 1) {
Christian Göttschefee744a2021-01-27 15:11:46 +0100571 int written, ret;
Christian Göttsche9631bc92022-06-21 21:10:47 +0200572 if (indent & 1U) {
Christian Göttschefee744a2021-01-27 15:11:46 +0100573 ret = xSnprintf(buf, n, "%s ", CRT_treeStr[TREE_STR_VERT]);
574 } else {
575 ret = xSnprintf(buf, n, " ");
576 }
577 if (ret < 0 || (size_t)ret >= n) {
578 written = n;
579 } else {
580 written = ret;
581 }
582 buf += written;
583 n -= written;
584 }
585
586 const char* draw = CRT_treeStr[lastItem ? TREE_STR_BEND : TREE_STR_RTEE];
Nathan Scott0f751e92023-08-22 16:11:05 +1000587 xSnprintf(buf, n, "%s%s ", draw, super->showChildren ? CRT_treeStr[TREE_STR_SHUT] : CRT_treeStr[TREE_STR_OPEN] );
Christian Göttschefee744a2021-01-27 15:11:46 +0100588 RichString_appendWide(str, CRT_colors[PROCESS_TREE], buffer);
589 Process_writeCommand(this, attr, baseattr, str);
590 return;
Hisham Muhammadd6231ba2006-03-04 18:16:49 +0000591 }
Benny Baumannaa8552b2021-04-18 19:25:56 +0200592 case PROC_COMM: {
593 const char* procComm;
594 if (this->procComm) {
595 attr = CRT_colors[Process_isUserlandThread(this) ? PROCESS_THREAD_COMM : PROCESS_COMM];
596 procComm = this->procComm;
597 } else {
598 attr = CRT_colors[PROCESS_SHADOW];
599 procComm = Process_isKernelThread(this) ? kthreadID : "N/A";
600 }
601
Nathan Scott0f751e92023-08-22 16:11:05 +1000602 Row_printLeftAlignedField(str, attr, procComm, TASK_COMM_LEN - 1);
Benny Baumannaa8552b2021-04-18 19:25:56 +0200603 return;
604 }
605 case PROC_EXE: {
606 const char* procExe;
607 if (this->procExe) {
608 attr = CRT_colors[Process_isUserlandThread(this) ? PROCESS_THREAD_BASENAME : PROCESS_BASENAME];
Nathan Scott0bdade12023-05-02 09:02:22 +1000609 if (settings->highlightDeletedExe) {
Benny Baumannde1d0632021-06-10 23:23:03 +0200610 if (this->procExeDeleted)
611 attr = CRT_colors[FAILED_READ];
612 else if (this->usesDeletedLib)
613 attr = CRT_colors[PROCESS_TAG];
614 }
Benny Baumannaa8552b2021-04-18 19:25:56 +0200615 procExe = this->procExe + this->procExeBasenameOffset;
616 } else {
617 attr = CRT_colors[PROCESS_SHADOW];
618 procExe = Process_isKernelThread(this) ? kthreadID : "N/A";
619 }
620
Nathan Scott0f751e92023-08-22 16:11:05 +1000621 Row_printLeftAlignedField(str, attr, procExe, TASK_COMM_LEN - 1);
Benny Baumannaa8552b2021-04-18 19:25:56 +0200622 return;
623 }
Benny Baumannb6ff5c82021-05-25 19:02:12 +0200624 case CWD: {
625 const char* cwd;
626 if (!this->procCwd) {
627 attr = CRT_colors[PROCESS_SHADOW];
628 cwd = "N/A";
629 } else if (String_startsWith(this->procCwd, "/proc/") && strstr(this->procCwd, " (deleted)") != NULL) {
630 attr = CRT_colors[PROCESS_SHADOW];
631 cwd = "main thread terminated";
632 } else {
633 cwd = this->procCwd;
634 }
Nathan Scott0f751e92023-08-22 16:11:05 +1000635 Row_printLeftAlignedField(str, attr, cwd, 25);
Benny Baumannb6ff5c82021-05-25 19:02:12 +0200636 return;
637 }
Benny Baumannb83ce852022-04-03 12:57:23 +0200638 case ELAPSED: {
Nathan Scott0f751e92023-08-22 16:11:05 +1000639 const uint64_t rt = host->realtimeMs;
Benny Baumannb83ce852022-04-03 12:57:23 +0200640 const uint64_t st = this->starttime_ctime * 1000;
641 const uint64_t dt =
642 rt < st ? 0 :
643 rt - st;
Nathan Scott0f751e92023-08-22 16:11:05 +1000644 Row_printTime(str, /* convert to hundreds of a second */ dt / 10, coloring);
Benny Baumannb83ce852022-04-03 12:57:23 +0200645 return;
646 }
Nathan Scott0f751e92023-08-22 16:11:05 +1000647 case MAJFLT: Row_printCount(str, this->majflt, coloring); return;
648 case MINFLT: Row_printCount(str, this->minflt, coloring); return;
649 case M_RESIDENT: Row_printKBytes(str, this->m_resident, coloring); return;
650 case M_VIRT: Row_printKBytes(str, this->m_virt, coloring); return;
Christian Göttschefee744a2021-01-27 15:11:46 +0100651 case NICE:
Nico Sonack11bf6a72024-04-20 21:30:40 +0200652 if (this->nice == PROCESS_NICE_UNKNOWN) {
653 xSnprintf(buffer, n, "N/A ");
654 attr = CRT_colors[PROCESS_SHADOW];
655 } else {
656 xSnprintf(buffer, n, "%3ld ", this->nice);
657 attr = this->nice < 0 ? CRT_colors[PROCESS_HIGH_PRIORITY]
658 : this->nice > 0 ? CRT_colors[PROCESS_LOW_PRIORITY]
659 : CRT_colors[PROCESS_SHADOW];
660 }
Christian Göttschefee744a2021-01-27 15:11:46 +0100661 break;
662 case NLWP:
663 if (this->nlwp == 1)
664 attr = CRT_colors[PROCESS_SHADOW];
665
666 xSnprintf(buffer, n, "%4ld ", this->nlwp);
667 break;
Nathan Scott0f751e92023-08-22 16:11:05 +1000668 case PERCENT_CPU: Row_printPercentage(this->percent_cpu, buffer, n, Row_fieldWidths[PERCENT_CPU], &attr); break;
Christian Göttschefee744a2021-01-27 15:11:46 +0100669 case PERCENT_NORM_CPU: {
Nathan Scott0f751e92023-08-22 16:11:05 +1000670 float cpuPercentage = this->percent_cpu / host->activeCPUs;
671 Row_printPercentage(cpuPercentage, buffer, n, Row_fieldWidths[PERCENT_CPU], &attr);
Hisham Muhammadbe1700c2015-03-16 01:43:04 -0300672 break;
673 }
Nathan Scott0f751e92023-08-22 16:11:05 +1000674 case PERCENT_MEM: Row_printPercentage(this->percent_mem, buffer, n, 4, &attr); break;
Christian Göttsche9f68c8d2020-12-15 19:44:52 +0100675 case PGRP: xSnprintf(buffer, n, "%*d ", Process_pidDigits, this->pgrp); break;
Nathan Scott0f751e92023-08-22 16:11:05 +1000676 case PID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, Process_getPid(this)); break;
677 case PPID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, Process_getParent(this)); break;
Christian Göttschefee744a2021-01-27 15:11:46 +0100678 case PRIORITY:
679 if (this->priority <= -100)
Hisham Muhammad09e241f2017-07-27 16:07:50 -0300680 xSnprintf(buffer, n, " RT ");
Hisham Muhammadbe1700c2015-03-16 01:43:04 -0300681 else
Hisham Muhammad09e241f2017-07-27 16:07:50 -0300682 xSnprintf(buffer, n, "%3ld ", this->priority);
Hisham Muhammadbe1700c2015-03-16 01:43:04 -0300683 break;
Nathan Scott0bdade12023-05-02 09:02:22 +1000684 case PROCESSOR: xSnprintf(buffer, n, "%3d ", Settings_cpuId(settings, this->processor)); break;
Christian Göttscheda494892023-01-10 19:40:04 +0100685 case SCHEDULERPOLICY: {
686 const char* schedPolStr = "N/A";
687#ifdef SCHEDULER_SUPPORT
688 if (this->scheduling_policy >= 0)
689 schedPolStr = Scheduling_formatPolicy(this->scheduling_policy);
690#endif
691 xSnprintf(buffer, n, "%-5s ", schedPolStr);
692 break;
693 }
Christian Göttsche9f68c8d2020-12-15 19:44:52 +0100694 case SESSION: xSnprintf(buffer, n, "%*d ", Process_pidDigits, this->session); break;
Hisham Muhammad09e241f2017-07-27 16:07:50 -0300695 case STARTTIME: xSnprintf(buffer, n, "%s", this->starttime_show); break;
Christian Göttschefee744a2021-01-27 15:11:46 +0100696 case STATE:
marcluqued8dfbbd2021-10-12 00:45:09 +0200697 xSnprintf(buffer, n, "%c ", processStateChar(this->state));
Christian Göttschefee744a2021-01-27 15:11:46 +0100698 switch (this->state) {
Benny Baumann6aa9ef22023-11-23 12:22:02 +0100699 case RUNNABLE:
700 case RUNNING:
701 case TRACED:
702 attr = CRT_colors[PROCESS_RUN_STATE];
703 break;
marcluqued8dfbbd2021-10-12 00:45:09 +0200704
Benny Baumann6aa9ef22023-11-23 12:22:02 +0100705 case BLOCKED:
706 case DEFUNCT:
707 case STOPPED:
708 case UNINTERRUPTIBLE_WAIT:
709 case ZOMBIE:
710 attr = CRT_colors[PROCESS_D_STATE];
711 break;
marcluqued8dfbbd2021-10-12 00:45:09 +0200712
Benny Baumann6aa9ef22023-11-23 12:22:02 +0100713 case QUEUED:
714 case WAITING:
715 case IDLE:
716 case SLEEPING:
717 attr = CRT_colors[PROCESS_SHADOW];
718 break;
marcluqued8dfbbd2021-10-12 00:45:09 +0200719
Benny Baumann6aa9ef22023-11-23 12:22:02 +0100720 case UNKNOWN:
721 case PAGING:
722 break;
Valmiky Arquissandas64e0d942014-10-14 02:30:17 +0100723 }
Hisham Muhammadd6231ba2006-03-04 18:16:49 +0000724 break;
Silke Hofstra696f79f2021-08-16 22:50:36 +0200725 case ST_UID: xSnprintf(buffer, n, "%*d ", Process_uidDigits, this->st_uid); break;
Nathan Scott0f751e92023-08-22 16:11:05 +1000726 case TIME: Row_printTime(str, this->time, coloring); return;
Christian Göttschefee744a2021-01-27 15:11:46 +0100727 case TGID:
Nathan Scott0f751e92023-08-22 16:11:05 +1000728 if (Process_getThreadGroup(this) == Process_getPid(this))
Christian Göttschefee744a2021-01-27 15:11:46 +0100729 attr = CRT_colors[PROCESS_SHADOW];
730
Nathan Scott0f751e92023-08-22 16:11:05 +1000731 xSnprintf(buffer, n, "%*d ", Process_pidDigits, Process_getThreadGroup(this));
Christian Göttschefee744a2021-01-27 15:11:46 +0100732 break;
Christian Göttsche9f68c8d2020-12-15 19:44:52 +0100733 case TPGID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, this->tpgid); break;
Christian Göttsche9a822152021-03-21 19:40:56 +0100734 case TTY:
735 if (!this->tty_name) {
Christian Göttschea3c82852021-01-27 15:11:42 +0100736 attr = CRT_colors[PROCESS_SHADOW];
Christian Göttsche9a822152021-03-21 19:40:56 +0100737 xSnprintf(buffer, n, "(no tty) ");
Christian Göttschea3c82852021-01-27 15:11:42 +0100738 } else {
Christian Göttsche9a822152021-03-21 19:40:56 +0100739 const char* name = String_startsWith(this->tty_name, "/dev/") ? (this->tty_name + strlen("/dev/")) : this->tty_name;
740 xSnprintf(buffer, n, "%-8s ", name);
Christian Göttschea3c82852021-01-27 15:11:42 +0100741 }
742 break;
Christian Göttschefee744a2021-01-27 15:11:46 +0100743 case USER:
Christian Göttscheb4d5b5c2023-05-05 20:23:15 +0200744 if (this->elevated_priv == TRI_ON)
Christian Göttschee3481a92022-10-29 19:21:12 +0200745 attr = CRT_colors[PROCESS_PRIV];
Nathan Scott0f751e92023-08-22 16:11:05 +1000746 else if (host->htopUserId != this->st_uid)
Hisham Muhammadd6231ba2006-03-04 18:16:49 +0000747 attr = CRT_colors[PROCESS_SHADOW];
Christian Göttsche5359eae2020-12-23 12:21:29 +0100748
Hisham Muhammadeb2803c2006-07-12 01:35:59 +0000749 if (this->user) {
Nathan Scott0f751e92023-08-22 16:11:05 +1000750 Row_printLeftAlignedField(str, attr, this->user, 10);
Christian Göttsche5359eae2020-12-23 12:21:29 +0100751 return;
Hisham Muhammadeb2803c2006-07-12 01:35:59 +0000752 }
Christian Göttsche5359eae2020-12-23 12:21:29 +0100753
Silke Hofstra696f79f2021-08-16 22:50:36 +0200754 xSnprintf(buffer, n, "%-10d ", this->st_uid);
Hisham Muhammadd6231ba2006-03-04 18:16:49 +0000755 break;
Hisham Muhammadd6231ba2006-03-04 18:16:49 +0000756 default:
Sohaib Mohamed6f2021f2021-07-11 03:11:29 +0200757 if (DynamicColumn_writeField(this, str, field))
758 return;
Christian Göttsche615fc932021-04-18 15:52:28 +0200759 assert(0 && "Process_writeField: default key reached"); /* should never be reached */
Hisham Muhammad09e241f2017-07-27 16:07:50 -0300760 xSnprintf(buffer, n, "- ");
Sohaib Mohamed6f2021f2021-07-11 03:11:29 +0200761 break;
Hisham Muhammadd6231ba2006-03-04 18:16:49 +0000762 }
Benny Baumann2aacbf82023-11-29 21:27:07 +0100763
Christian Göttschea2be57d2021-04-14 20:54:38 +0200764 RichString_appendAscii(str, attr, buffer);
Hisham Muhammadd6231ba2006-03-04 18:16:49 +0000765}
766
Hisham Muhammad6f868b02015-02-20 14:52:10 -0200767void Process_done(Process* this) {
Sohaib Mohamed53bdcab2023-08-22 16:46:59 +1000768 assert(this != NULL);
Benny Baumann02431c42020-12-19 16:21:08 +0100769 free(this->cmdline);
Benny Baumannd74e8b72021-01-30 15:31:59 +0100770 free(this->procComm);
771 free(this->procExe);
Benny Baumannb6ff5c82021-05-25 19:02:12 +0200772 free(this->procCwd);
Benny Baumanncdb660a2021-04-10 11:10:50 +0200773 free(this->mergedCommand.str);
Christian Göttsche9a822152021-03-21 19:40:56 +0100774 free(this->tty_name);
Hisham Muhammadda23c8c2008-03-09 08:58:38 +0000775}
776
Benny Baumannc0d02022021-04-24 12:06:49 +0200777/* This function returns the string displayed in Command column, so that sorting
778 * happens on what is displayed - whether comm, full path, basename, etc.. So
779 * this follows Process_writeField(COMM) and Process_writeCommand */
Christian Göttsche1ef8c0e2021-12-16 15:57:37 +0100780const char* Process_getCommand(const Process* this) {
Nathan Scott0f751e92023-08-22 16:11:05 +1000781 const Settings* settings = this->super.host->settings;
782
Nathan Scott0bdade12023-05-02 09:02:22 +1000783 if ((Process_isUserlandThread(this) && settings->showThreadNames) || !this->mergedCommand.str) {
Benny Baumannc0d02022021-04-24 12:06:49 +0200784 return this->cmdline;
785 }
786
787 return this->mergedCommand.str;
Narendran Gopalakrishnan09fe94d2020-10-17 16:24:45 +0530788}
789
Nathan Scott0f751e92023-08-22 16:11:05 +1000790static const char* Process_getSortKey(const Process* this) {
791 return Process_getCommand(this);
Hisham Muhammadda23c8c2008-03-09 08:58:38 +0000792}
793
Sohaib Mohamed53bdcab2023-08-22 16:46:59 +1000794const char* Process_rowGetSortKey(Row* super) {
Nathan Scott0f751e92023-08-22 16:11:05 +1000795 const Process* this = (const Process*) super;
796 assert(Object_isA((const Object*) this, (const ObjectClass*) &Process_class));
797 return Process_getSortKey(this);
Hisham Muhammadda23c8c2008-03-09 08:58:38 +0000798}
799
Nathan Scott0f751e92023-08-22 16:11:05 +1000800/* Test whether display must highlight this row (if the htop UID matches) */
801static bool Process_isHighlighted(const Process* this) {
802 const Machine* host = this->super.host;
803 const Settings* settings = host->settings;
804 return settings->shadowOtherUsers && this->st_uid != host->htopUserId;
805}
806
807bool Process_rowIsHighlighted(const Row* super) {
808 const Process* this = (const Process*) super;
809 assert(Object_isA((const Object*) this, (const ObjectClass*) &Process_class));
810 return Process_isHighlighted(this);
811}
812
813/* Test whether display must follow parent process (if this thread is hidden) */
814static bool Process_isVisible(const Process* p, const Settings* settings) {
815 if (settings->hideUserlandThreads)
816 return !Process_isThread(p);
817 return true;
818}
819
820bool Process_rowIsVisible(const Row* super, const Table* table) {
821 const Process* this = (const Process*) super;
822 assert(Object_isA((const Object*) this, (const ObjectClass*) &Process_class));
823 return Process_isVisible(this, table->host->settings);
824}
825
826/* Test whether display must filter out this process (various mechanisms) */
827static bool Process_matchesFilter(const Process* this, const Table* table) {
828 const Machine* host = table->host;
829 if (host->userId != (uid_t) -1 && this->st_uid != host->userId)
830 return true;
831
832 const char* incFilter = table->incFilter;
833 if (incFilter && !String_contains_i(Process_getCommand(this), incFilter, true))
834 return true;
835
Nathan Scottb74673f2023-08-31 11:56:43 +1000836 const ProcessTable* pt = (const ProcessTable*) host->activeTable;
837 assert(Object_isA((const Object*) pt, (const ObjectClass*) &ProcessTable_class));
838 if (pt->pidMatchList && !Hashtable_get(pt->pidMatchList, Process_getThreadGroup(this)))
Nathan Scott0f751e92023-08-22 16:11:05 +1000839 return true;
840
Adam Saponaradde71c62020-10-30 21:56:16 -0400841 return false;
842}
843
Nathan Scott0f751e92023-08-22 16:11:05 +1000844bool Process_rowMatchesFilter(const Row* super, const Table* table) {
845 const Process* this = (const Process*) super;
846 assert(Object_isA((const Object*) this, (const ObjectClass*) &Process_class));
847 return Process_matchesFilter(this, table);
Adam Saponaradde71c62020-10-30 21:56:16 -0400848}
849
Nathan Scott0f751e92023-08-22 16:11:05 +1000850void Process_init(Process* this, const Machine* host) {
851 Row_init(&this->super, host);
852
853 this->cmdlineBasenameEnd = -1;
Christian Göttsche3716fc72023-09-24 14:43:30 +0200854 this->st_uid = (uid_t)-1;
Nathan Scott0f751e92023-08-22 16:11:05 +1000855}
856
857static bool Process_setPriority(Process* this, int priority) {
Christian Göttsche36880cd2021-01-21 20:27:37 +0100858 if (Settings_isReadonly())
859 return false;
860
Nathan Scott0f751e92023-08-22 16:11:05 +1000861 int old_prio = getpriority(PRIO_PROCESS, Process_getPid(this));
862 int err = setpriority(PRIO_PROCESS, Process_getPid(this), priority);
Benny Baumann82157f52021-02-16 19:44:59 +0100863
Nathan Scott0f751e92023-08-22 16:11:05 +1000864 if (err == 0 && old_prio != getpriority(PRIO_PROCESS, Process_getPid(this))) {
Michael Kleinab3a7c22015-12-07 20:10:09 +0100865 this->nice = priority;
Hisham Muhammadda23c8c2008-03-09 08:58:38 +0000866 }
Michael Kleinab3a7c22015-12-07 20:10:09 +0100867 return (err == 0);
Hisham Muhammadda23c8c2008-03-09 08:58:38 +0000868}
869
Nathan Scott0f751e92023-08-22 16:11:05 +1000870bool Process_rowChangePriorityBy(Row* super, Arg delta) {
871 Process* this = (Process*) super;
872 assert(Object_isA((const Object*) this, (const ObjectClass*) &Process_class));
Nathan Scott500fb282020-08-20 09:35:24 +1000873 return Process_setPriority(this, this->nice + delta.i);
Hisham Muhammad47e881f2012-10-04 23:59:45 +0000874}
875
Nathan Scott0f751e92023-08-22 16:11:05 +1000876static bool Process_sendSignal(Process* this, Arg sgn) {
877 return kill(Process_getPid(this), sgn.i) == 0;
878}
879
880bool Process_rowSendSignal(Row* super, Arg sgn) {
881 Process* this = (Process*) super;
882 assert(Object_isA((const Object*) this, (const ObjectClass*) &Process_class));
883 return Process_sendSignal(this, sgn);
Hisham Muhammadda23c8c2008-03-09 08:58:38 +0000884}
885
Christian Göttsche90ea3ac2020-12-23 13:02:32 +0100886int Process_compare(const void* v1, const void* v2) {
Benny Baumann976c6122021-07-14 19:24:18 +0200887 const Process* p1 = (const Process*)v1;
888 const Process* p2 = (const Process*)v2;
Christian Göttsche397b5c42020-11-04 17:46:24 +0100889
Nathan Scott0f751e92023-08-22 16:11:05 +1000890 const ScreenSettings* ss = p1->super.host->settings->ss;
Christian Göttsche397b5c42020-11-04 17:46:24 +0100891
Hisham Muhammad72ba20f2021-08-31 15:38:52 +1000892 ProcessField key = ScreenSettings_getActiveSortKey(ss);
Hisham Muhammade8c69942020-12-17 19:08:56 -0300893
Christian Göttsche90ea3ac2020-12-23 13:02:32 +0100894 int result = Process_compareByKey(p1, p2, key);
Benny Baumann77db2402020-12-18 22:12:26 +0100895
896 // Implement tie-breaker (needed to make tree mode more stable)
897 if (!result)
Nathan Scott0f751e92023-08-22 16:11:05 +1000898 return SPACESHIP_NUMBER(Process_getPid(p1), Process_getPid(p2));
Benny Baumann77db2402020-12-18 22:12:26 +0100899
Hisham Muhammad72ba20f2021-08-31 15:38:52 +1000900 return (ScreenSettings_getActiveDirection(ss) == 1) ? result : -result;
Benny Baumann77db2402020-12-18 22:12:26 +0100901}
902
Nathan Scott0f751e92023-08-22 16:11:05 +1000903int Process_compareByParent(const Row* r1, const Row* r2) {
Nathan Scott5d778ea2024-01-17 14:04:24 +1100904 int result = SPACESHIP_NUMBER(
905 r1->isRoot ? 0 : Row_getGroupOrParent(r1),
906 r2->isRoot ? 0 : Row_getGroupOrParent(r2)
907 );
Nathan Scott0f751e92023-08-22 16:11:05 +1000908
909 if (result != 0)
910 return result;
911
912 return Process_compare(r1, r2);
913}
914
Christian Göttsche90ea3ac2020-12-23 13:02:32 +0100915int Process_compareByKey_Base(const Process* p1, const Process* p2, ProcessField key) {
Benny Baumann77db2402020-12-18 22:12:26 +0100916 int r;
917
Christian Göttsche89473cc2020-12-15 19:44:48 +0100918 switch (key) {
Hisham Muhammad5d48ab82006-07-11 06:13:32 +0000919 case PERCENT_CPU:
Christian Göttsche15eab202020-10-30 17:02:20 +0100920 case PERCENT_NORM_CPU:
Explorer09076b9132023-08-17 04:21:05 +0800921 return compareRealNumbers(p1->percent_cpu, p2->percent_cpu);
Hisham Muhammad5d48ab82006-07-11 06:13:32 +0000922 case PERCENT_MEM:
Daniel Lange4531b312021-01-21 14:27:23 +0100923 return SPACESHIP_NUMBER(p1->m_resident, p2->m_resident);
Hisham Muhammad5d48ab82006-07-11 06:13:32 +0000924 case COMM:
Narendran Gopalakrishnan09fe94d2020-10-17 16:24:45 +0530925 return SPACESHIP_NULLSTR(Process_getCommand(p1), Process_getCommand(p2));
Benny Baumannaa8552b2021-04-18 19:25:56 +0200926 case PROC_COMM: {
Benny Baumann976c6122021-07-14 19:24:18 +0200927 const char* comm1 = p1->procComm ? p1->procComm : (Process_isKernelThread(p1) ? kthreadID : "");
928 const char* comm2 = p2->procComm ? p2->procComm : (Process_isKernelThread(p2) ? kthreadID : "");
Benny Baumannaa8552b2021-04-18 19:25:56 +0200929 return SPACESHIP_NULLSTR(comm1, comm2);
930 }
931 case PROC_EXE: {
Benny Baumann976c6122021-07-14 19:24:18 +0200932 const char* exe1 = p1->procExe ? (p1->procExe + p1->procExeBasenameOffset) : (Process_isKernelThread(p1) ? kthreadID : "");
933 const char* exe2 = p2->procExe ? (p2->procExe + p2->procExeBasenameOffset) : (Process_isKernelThread(p2) ? kthreadID : "");
Benny Baumannaa8552b2021-04-18 19:25:56 +0200934 return SPACESHIP_NULLSTR(exe1, exe2);
935 }
Benny Baumannb6ff5c82021-05-25 19:02:12 +0200936 case CWD:
937 return SPACESHIP_NULLSTR(p1->procCwd, p2->procCwd);
Christian Göttsche550a1412021-05-02 13:29:39 +0200938 case ELAPSED:
939 r = -SPACESHIP_NUMBER(p1->starttime_ctime, p2->starttime_ctime);
Nathan Scott0f751e92023-08-22 16:11:05 +1000940 return r != 0 ? r : SPACESHIP_NUMBER(Process_getPid(p1), Process_getPid(p2));
Hisham Muhammadbe1700c2015-03-16 01:43:04 -0300941 case MAJFLT:
Daniel Lange4531b312021-01-21 14:27:23 +0100942 return SPACESHIP_NUMBER(p1->majflt, p2->majflt);
Hisham Muhammadbe1700c2015-03-16 01:43:04 -0300943 case MINFLT:
Daniel Lange4531b312021-01-21 14:27:23 +0100944 return SPACESHIP_NUMBER(p1->minflt, p2->minflt);
Hisham Muhammadbe1700c2015-03-16 01:43:04 -0300945 case M_RESIDENT:
Daniel Lange4531b312021-01-21 14:27:23 +0100946 return SPACESHIP_NUMBER(p1->m_resident, p2->m_resident);
Christian Göttschefa002c02020-11-20 17:09:34 +0100947 case M_VIRT:
Daniel Lange4531b312021-01-21 14:27:23 +0100948 return SPACESHIP_NUMBER(p1->m_virt, p2->m_virt);
Hisham Muhammadbe1700c2015-03-16 01:43:04 -0300949 case NICE:
Christian Göttsche397b5c42020-11-04 17:46:24 +0100950 return SPACESHIP_NUMBER(p1->nice, p2->nice);
Hisham Muhammadd357c672007-05-21 19:10:53 +0000951 case NLWP:
Christian Göttsche397b5c42020-11-04 17:46:24 +0100952 return SPACESHIP_NUMBER(p1->nlwp, p2->nlwp);
Hisham Muhammadbe1700c2015-03-16 01:43:04 -0300953 case PGRP:
Christian Göttsche397b5c42020-11-04 17:46:24 +0100954 return SPACESHIP_NUMBER(p1->pgrp, p2->pgrp);
Hisham Muhammadbe1700c2015-03-16 01:43:04 -0300955 case PID:
Nathan Scott0f751e92023-08-22 16:11:05 +1000956 return SPACESHIP_NUMBER(Process_getPid(p1), Process_getPid(p2));
Hisham Muhammadbe1700c2015-03-16 01:43:04 -0300957 case PPID:
Nathan Scott0f751e92023-08-22 16:11:05 +1000958 return SPACESHIP_NUMBER(Process_getParent(p1), Process_getParent(p2));
Hisham Muhammadbe1700c2015-03-16 01:43:04 -0300959 case PRIORITY:
Christian Göttsche397b5c42020-11-04 17:46:24 +0100960 return SPACESHIP_NUMBER(p1->priority, p2->priority);
Hisham Muhammad272e2d92015-03-16 23:01:48 -0300961 case PROCESSOR:
Christian Göttsche397b5c42020-11-04 17:46:24 +0100962 return SPACESHIP_NUMBER(p1->processor, p2->processor);
Christian Göttscheda494892023-01-10 19:40:04 +0100963 case SCHEDULERPOLICY:
964 return SPACESHIP_NUMBER(p1->scheduling_policy, p2->scheduling_policy);
Hisham Muhammadbe1700c2015-03-16 01:43:04 -0300965 case SESSION:
Christian Göttsche397b5c42020-11-04 17:46:24 +0100966 return SPACESHIP_NUMBER(p1->session, p2->session);
967 case STARTTIME:
968 r = SPACESHIP_NUMBER(p1->starttime_ctime, p2->starttime_ctime);
Nathan Scott0f751e92023-08-22 16:11:05 +1000969 return r != 0 ? r : SPACESHIP_NUMBER(Process_getPid(p1), Process_getPid(p2));
Hisham Muhammadbe1700c2015-03-16 01:43:04 -0300970 case STATE:
marcluqued8dfbbd2021-10-12 00:45:09 +0200971 return SPACESHIP_NUMBER(p1->state, p2->state);
Hisham Muhammadbe1700c2015-03-16 01:43:04 -0300972 case ST_UID:
Christian Göttsche397b5c42020-11-04 17:46:24 +0100973 return SPACESHIP_NUMBER(p1->st_uid, p2->st_uid);
Hisham Muhammadbe1700c2015-03-16 01:43:04 -0300974 case TIME:
Daniel Lange4531b312021-01-21 14:27:23 +0100975 return SPACESHIP_NUMBER(p1->time, p2->time);
Hisham Muhammadbe1700c2015-03-16 01:43:04 -0300976 case TGID:
Nathan Scott0f751e92023-08-22 16:11:05 +1000977 return SPACESHIP_NUMBER(Process_getThreadGroup(p1), Process_getThreadGroup(p2));
Hisham Muhammadbe1700c2015-03-16 01:43:04 -0300978 case TPGID:
Christian Göttsche397b5c42020-11-04 17:46:24 +0100979 return SPACESHIP_NUMBER(p1->tpgid, p2->tpgid);
Christian Göttsche9a822152021-03-21 19:40:56 +0100980 case TTY:
981 /* Order no tty last */
982 return SPACESHIP_DEFAULTSTR(p1->tty_name, p2->tty_name, "\x7F");
Hisham Muhammadbe1700c2015-03-16 01:43:04 -0300983 case USER:
Christian Göttsche397b5c42020-11-04 17:46:24 +0100984 return SPACESHIP_NULLSTR(p1->user, p2->user);
Hisham Muhammad5d48ab82006-07-11 06:13:32 +0000985 default:
Christian Göttschefa9f2602021-12-08 13:02:18 +0100986 CRT_debug("Process_compareByKey_Base() called with key %d", key);
Christian Göttsche615fc932021-04-18 15:52:28 +0200987 assert(0 && "Process_compareByKey_Base: default key reached"); /* should never be reached */
Nathan Scott0f751e92023-08-22 16:11:05 +1000988 return SPACESHIP_NUMBER(Process_getPid(p1), Process_getPid(p2));
Hisham Muhammad5d48ab82006-07-11 06:13:32 +0000989 }
Hisham Muhammadd6231ba2006-03-04 18:16:49 +0000990}
Christian Göttsche05fb6812021-05-18 22:29:25 +0200991
992void Process_updateComm(Process* this, const char* comm) {
993 if (!this->procComm && !comm)
994 return;
995
996 if (this->procComm && comm && String_eq(this->procComm, comm))
997 return;
998
999 free(this->procComm);
1000 this->procComm = comm ? xStrdup(comm) : NULL;
Benny Baumann2999fff2022-05-29 23:03:56 +02001001
1002 this->mergedCommand.lastUpdate = 0;
Christian Göttsche05fb6812021-05-18 22:29:25 +02001003}
1004
1005static int skipPotentialPath(const char* cmdline, int end) {
1006 if (cmdline[0] != '/')
1007 return 0;
1008
1009 int slash = 0;
1010 for (int i = 1; i < end; i++) {
Benny Baumann0d85af22021-07-14 19:18:27 +02001011 if (cmdline[i] == '/' && cmdline[i + 1] != '\0') {
Christian Göttsche05fb6812021-05-18 22:29:25 +02001012 slash = i + 1;
1013 continue;
1014 }
1015
Benny Baumann0d85af22021-07-14 19:18:27 +02001016 if (cmdline[i] == ' ' && cmdline[i - 1] != '\\')
Christian Göttsche05fb6812021-05-18 22:29:25 +02001017 return slash;
1018
Benny Baumann0d85af22021-07-14 19:18:27 +02001019 if (cmdline[i] == ':' && cmdline[i + 1] == ' ')
Christian Göttsche05fb6812021-05-18 22:29:25 +02001020 return slash;
1021 }
1022
1023 return slash;
1024}
1025
1026void Process_updateCmdline(Process* this, const char* cmdline, int basenameStart, int basenameEnd) {
1027 assert(basenameStart >= 0);
1028 assert((cmdline && basenameStart < (int)strlen(cmdline)) || (!cmdline && basenameStart == 0));
Christian Göttschec408add2021-05-23 15:53:23 +02001029 assert((basenameEnd > basenameStart) || (basenameEnd == 0 && basenameStart == 0));
Christian Göttsche05fb6812021-05-18 22:29:25 +02001030 assert((cmdline && basenameEnd <= (int)strlen(cmdline)) || (!cmdline && basenameEnd == 0));
1031
1032 if (!this->cmdline && !cmdline)
1033 return;
1034
1035 if (this->cmdline && cmdline && String_eq(this->cmdline, cmdline))
1036 return;
1037
1038 free(this->cmdline);
1039 this->cmdline = cmdline ? xStrdup(cmdline) : NULL;
Christian Göttsche32cb3022024-01-27 23:22:31 +01001040 if (Process_isKernelThread(this)) {
1041 /* kernel threads have no basename */
1042 this->cmdlineBasenameStart = 0;
1043 this->cmdlineBasenameEnd = 0;
1044 } else {
1045 this->cmdlineBasenameStart = (basenameStart || !cmdline) ? basenameStart : skipPotentialPath(cmdline, basenameEnd);
1046 this->cmdlineBasenameEnd = basenameEnd;
1047 }
Benny Baumann2999fff2022-05-29 23:03:56 +02001048
1049 this->mergedCommand.lastUpdate = 0;
Christian Göttsche05fb6812021-05-18 22:29:25 +02001050}
1051
1052void Process_updateExe(Process* this, const char* exe) {
1053 if (!this->procExe && !exe)
1054 return;
1055
1056 if (this->procExe && exe && String_eq(this->procExe, exe))
1057 return;
1058
1059 free(this->procExe);
1060 if (exe) {
1061 this->procExe = xStrdup(exe);
1062 const char* lastSlash = strrchr(exe, '/');
1063 this->procExeBasenameOffset = (lastSlash && *(lastSlash + 1) != '\0' && lastSlash != exe) ? (lastSlash - exe + 1) : 0;
1064 } else {
1065 this->procExe = NULL;
1066 this->procExeBasenameOffset = 0;
1067 }
Benny Baumann2999fff2022-05-29 23:03:56 +02001068
1069 this->mergedCommand.lastUpdate = 0;
Christian Göttsche05fb6812021-05-18 22:29:25 +02001070}
Christian Göttsche3ba69522021-12-04 19:57:47 +01001071
Benny Baumannedf319e2022-02-27 20:29:40 +01001072void Process_updateCPUFieldWidths(float percentage) {
Explorer0995f77eb2024-03-21 14:59:40 +08001073 if (!isgreaterequal(percentage, 99.9F)) {
Nathan Scott0f751e92023-08-22 16:11:05 +10001074 Row_updateFieldWidth(PERCENT_CPU, 4);
1075 Row_updateFieldWidth(PERCENT_NORM_CPU, 4);
Benny Baumannedf319e2022-02-27 20:29:40 +01001076 return;
1077 }
1078
Kumar0af08bc2022-04-14 16:35:02 +05301079 // Add additional two characters, one for "." and another for precision.
1080 uint8_t width = ceil(log10(percentage + 0.1)) + 2;
Benny Baumannedf319e2022-02-27 20:29:40 +01001081
Nathan Scott0f751e92023-08-22 16:11:05 +10001082 Row_updateFieldWidth(PERCENT_CPU, width);
1083 Row_updateFieldWidth(PERCENT_NORM_CPU, width);
Benny Baumannedf319e2022-02-27 20:29:40 +01001084}
Nathan Scott0f751e92023-08-22 16:11:05 +10001085
1086const ProcessClass Process_class = {
1087 .super = {
1088 .super = {
1089 .extends = Class(Row),
1090 .display = Row_display,
1091 .delete = Process_delete,
1092 .compare = Process_compare
1093 },
1094 .isHighlighted = Process_rowIsHighlighted,
1095 .isVisible = Process_rowIsVisible,
1096 .matchesFilter = Process_rowMatchesFilter,
1097 .sortKeyString = Process_rowGetSortKey,
1098 .compareByParent = Process_compareByParent,
1099 .writeField = Process_rowWriteField
1100 },
1101};