htop/Process.c

1294 lines
47 KiB
C
Raw Normal View History

2006-03-04 18:16:49 +00:00
/*
htop - Process.c
2015-03-21 19:52:54 +00:00
(C) 2004-2015 Hisham H. Muhammad
(C) 2020 Red Hat, Inc. All Rights Reserved.
Released under the GNU GPLv2+, see the COPYING file
2006-03-04 18:16:49 +00:00
in the source distribution for its full text.
*/
#include "config.h" // IWYU pragma: keep
2011-12-26 21:35:57 +00:00
#include "Process.h"
2015-03-17 02:01:48 +00:00
#include <assert.h>
#include <limits.h>
#include <math.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
2021-04-29 15:12:43 +00:00
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <sys/resource.h>
2006-03-04 18:16:49 +00:00
#include "CRT.h"
2020-11-18 13:26:30 +00:00
#include "Macros.h"
#include "Platform.h"
2020-11-18 13:26:30 +00:00
#include "ProcessList.h"
#include "DynamicColumn.h"
#include "RichString.h"
#include "Settings.h"
2020-10-14 18:21:09 +00:00
#include "XUtils.h"
2006-03-04 18:16:49 +00:00
#if defined(MAJOR_IN_MKDEV)
#include <sys/mkdev.h>
#endif
2006-03-04 18:16:49 +00:00
/* Used to identify kernel threads in Comm and Exe columns */
2021-07-14 17:24:18 +00:00
static const char* const kthreadID = "KTHREAD";
2020-11-04 16:46:04 +00:00
static uid_t Process_getuid = (uid_t)-1;
2006-07-12 01:35:59 +00:00
int Process_pidDigits = PROCESS_MIN_PID_DIGITS;
int Process_uidDigits = PROCESS_MIN_UID_DIGITS;
void Process_setupColumnWidths() {
int maxPid = Platform_getMaxPid();
2020-11-01 00:09:51 +00:00
if (maxPid == -1)
return;
if (maxPid < (int)pow(10, PROCESS_MIN_PID_DIGITS)) {
Process_pidDigits = PROCESS_MIN_PID_DIGITS;
return;
}
Process_pidDigits = (int)log10(maxPid) + 1;
assert(Process_pidDigits <= PROCESS_MAX_PID_DIGITS);
}
void Process_setUidColumnWidth(uid_t maxUid) {
if (maxUid < (uid_t)pow(10, PROCESS_MIN_UID_DIGITS)) {
Process_uidDigits = PROCESS_MIN_UID_DIGITS;
return;
}
Process_uidDigits = (int)log10(maxUid) + 1;
assert(Process_uidDigits <= PROCESS_MAX_UID_DIGITS);
}
void Process_printBytes(RichString* str, unsigned long long number, bool coloring) {
char buffer[16];
2006-03-04 18:16:49 +00:00
int len;
2019-10-31 16:39:12 +00:00
int largeNumberColor = coloring ? CRT_colors[LARGE_NUMBER] : CRT_colors[PROCESS];
int processMegabytesColor = coloring ? CRT_colors[PROCESS_MEGABYTES] : CRT_colors[PROCESS];
int processGigabytesColor = coloring ? CRT_colors[PROCESS_GIGABYTES] : CRT_colors[PROCESS];
int shadowColor = coloring ? CRT_colors[PROCESS_SHADOW] : CRT_colors[PROCESS];
int processColor = CRT_colors[PROCESS];
2019-10-31 16:39:12 +00:00
if (number == ULLONG_MAX) {
//Invalid number
RichString_appendAscii(str, shadowColor, " N/A ");
return;
}
number /= ONE_K;
if (number < 1000) {
//Plain number, no markings
len = xSnprintf(buffer, sizeof(buffer), "%5llu ", number);
RichString_appendnAscii(str, processColor, buffer, len);
} else if (number < 100000) {
//2 digit MB, 3 digit KB
2021-07-14 17:18:27 +00:00
len = xSnprintf(buffer, sizeof(buffer), "%2llu", number / 1000);
RichString_appendnAscii(str, processMegabytesColor, buffer, len);
2006-03-04 18:16:49 +00:00
number %= 1000;
len = xSnprintf(buffer, sizeof(buffer), "%03llu ", number);
RichString_appendnAscii(str, processColor, buffer, len);
} else if (number < 1000 * ONE_K) {
//3 digit MB
number /= ONE_K;
len = xSnprintf(buffer, sizeof(buffer), "%4lluM ", number);
RichString_appendnAscii(str, processMegabytesColor, buffer, len);
} else if (number < 10000 * ONE_K) {
//1 digit GB, 3 digit MB
number /= ONE_K;
2021-07-14 17:18:27 +00:00
len = xSnprintf(buffer, sizeof(buffer), "%1llu", number / 1000);
RichString_appendnAscii(str, processGigabytesColor, buffer, len);
number %= 1000;
len = xSnprintf(buffer, sizeof(buffer), "%03lluM ", number);
RichString_appendnAscii(str, processMegabytesColor, buffer, len);
} else if (number < 100000 * ONE_K) {
//2 digit GB, 1 digit MB
number /= 100 * ONE_K;
2021-07-14 17:18:27 +00:00
len = xSnprintf(buffer, sizeof(buffer), "%2llu", number / 10);
RichString_appendnAscii(str, processGigabytesColor, buffer, len);
number %= 10;
len = xSnprintf(buffer, sizeof(buffer), ".%1llu", number);
RichString_appendnAscii(str, processMegabytesColor, buffer, len);
RichString_appendAscii(str, processGigabytesColor, "G ");
} else if (number < 1000 * ONE_M) {
//3 digit GB
number /= ONE_M;
len = xSnprintf(buffer, sizeof(buffer), "%4lluG ", number);
RichString_appendnAscii(str, processGigabytesColor, buffer, len);
Process.{h,c}: Use integer types that are more portable When building on a 32-bit system, the compiler warned that the following line uses a constant whose value is the overflow result of a compile-time computation: Process.c (line 109): } else if (number < 10000 * ONE_M) { Namely, this constant expression: 10000 * ONE_M was intended to produce the following value: 10485760000 However, the result overflowed to produce: 1895825408 The reason for this overflow is as follows: o The macros are expanded: 10000 * (ONE_K * ONE_K) 10000 * (1024L * 1024L) o The untyped constant expression "10000" is typed: 10000U * (1024L * 1024L) o The parenthesized expression is evaluated: 10000U * (1048576L) o The left operand ("10000U") is converted: 10000L * (1048576L) Unbound by integer sizes, that last multiplication would produce the following value: 10485760000 However, on a 32-bit machine, where a long is 32 bits (really 31 bits when talking about positive numbers), the maximum value that can be computed is 2**31-1: 2147483647 Consequently, the computation overflows. o The compiler produces a long int value that is the the result of overflow (10485760000 % 2**31): 1895825408L Actually, I think this overflow is implementation-defined, so it's not even a portable description of what happens. The solution is to use a long long int (or, even better, an unsigned long long int) type for the constant expression; the C standard mandates a sufficiently large maximum value for such types. Hence, the following change is made to the bad line: - } else if (number < 10000 * ONE_M) { + } else if (number < 10000ULL * ONE_M) { However, the whole line is now patently silly, because the variable "number" is typed "unsigned long", and so it will always be less than the constant expression (the compiler will warn about this, too). Hence, "number" must be typed "unsigned long long"; however, this necessitates changing all of the string formats from something like "%lu" to something like "%llu". Et voila! This commit is born. Then, for the sake of completeness, the declared types of the constant-expression macros are updated: o ONE_K is made unsigned (a "UL" instead of "L") o ONE_T is computed by introducing "1ULL *" o Similar changes are made for ONE_DECIMAL_{K,T} Also, a non-portable overflow-conversion to a signed value has been replaced with a portable comparison: - if ((long long) number == -1LL) { + if (number == ULLONG_MAX) { It might be worth reviewing the rest of the code for other cases where overflows are not handled correctly; even at runtime, it's often necessary to check for overflow unless such behavior is expected (especially for signed integer values, for which overflow has implementation-defined behavior).
2020-09-29 14:04:22 +00:00
} else if (number < 10000ULL * ONE_M) {
//1 digit TB, 3 digit GB
number /= ONE_M;
2021-07-14 17:18:27 +00:00
len = xSnprintf(buffer, sizeof(buffer), "%1llu", number / 1000);
RichString_appendnAscii(str, largeNumberColor, buffer, len);
number %= 1000;
len = xSnprintf(buffer, sizeof(buffer), "%03lluG ", number);
RichString_appendnAscii(str, processGigabytesColor, buffer, len);
} else if (number < 100000 * ONE_M) {
//2 digit TB, 1 digit GB
number /= 100 * ONE_M;
2021-07-14 17:18:27 +00:00
len = xSnprintf(buffer, sizeof(buffer), "%2llu", number / 10);
RichString_appendnAscii(str, largeNumberColor, buffer, len);
number %= 10;
len = xSnprintf(buffer, sizeof(buffer), ".%1llu", number);
RichString_appendnAscii(str, processGigabytesColor, buffer, len);
RichString_appendAscii(str, largeNumberColor, "T ");
} else if (number < 10000ULL * ONE_G) {
//3 digit TB or 1 digit PB, 3 digit TB
number /= ONE_G;
len = xSnprintf(buffer, sizeof(buffer), "%4lluT ", number);
RichString_appendnAscii(str, largeNumberColor, buffer, len);
} else {
//2 digit PB and above
2021-07-14 17:18:27 +00:00
len = xSnprintf(buffer, sizeof(buffer), "%4.1lfP ", (double)number / ONE_T);
RichString_appendnAscii(str, largeNumberColor, buffer, len);
2006-03-04 18:16:49 +00:00
}
}
void Process_printKBytes(RichString* str, unsigned long long number, bool coloring) {
if (number == ULLONG_MAX)
Process_printBytes(str, ULLONG_MAX, coloring);
else
Process_printBytes(str, number * ONE_K, coloring);
}
void Process_printCount(RichString* str, unsigned long long number, bool coloring) {
char buffer[13];
int largeNumberColor = coloring ? CRT_colors[LARGE_NUMBER] : CRT_colors[PROCESS];
int processMegabytesColor = coloring ? CRT_colors[PROCESS_MEGABYTES] : CRT_colors[PROCESS];
int processColor = CRT_colors[PROCESS];
int processShadowColor = coloring ? CRT_colors[PROCESS_SHADOW] : CRT_colors[PROCESS];
Process.{h,c}: Use integer types that are more portable When building on a 32-bit system, the compiler warned that the following line uses a constant whose value is the overflow result of a compile-time computation: Process.c (line 109): } else if (number < 10000 * ONE_M) { Namely, this constant expression: 10000 * ONE_M was intended to produce the following value: 10485760000 However, the result overflowed to produce: 1895825408 The reason for this overflow is as follows: o The macros are expanded: 10000 * (ONE_K * ONE_K) 10000 * (1024L * 1024L) o The untyped constant expression "10000" is typed: 10000U * (1024L * 1024L) o The parenthesized expression is evaluated: 10000U * (1048576L) o The left operand ("10000U") is converted: 10000L * (1048576L) Unbound by integer sizes, that last multiplication would produce the following value: 10485760000 However, on a 32-bit machine, where a long is 32 bits (really 31 bits when talking about positive numbers), the maximum value that can be computed is 2**31-1: 2147483647 Consequently, the computation overflows. o The compiler produces a long int value that is the the result of overflow (10485760000 % 2**31): 1895825408L Actually, I think this overflow is implementation-defined, so it's not even a portable description of what happens. The solution is to use a long long int (or, even better, an unsigned long long int) type for the constant expression; the C standard mandates a sufficiently large maximum value for such types. Hence, the following change is made to the bad line: - } else if (number < 10000 * ONE_M) { + } else if (number < 10000ULL * ONE_M) { However, the whole line is now patently silly, because the variable "number" is typed "unsigned long", and so it will always be less than the constant expression (the compiler will warn about this, too). Hence, "number" must be typed "unsigned long long"; however, this necessitates changing all of the string formats from something like "%lu" to something like "%llu". Et voila! This commit is born. Then, for the sake of completeness, the declared types of the constant-expression macros are updated: o ONE_K is made unsigned (a "UL" instead of "L") o ONE_T is computed by introducing "1ULL *" o Similar changes are made for ONE_DECIMAL_{K,T} Also, a non-portable overflow-conversion to a signed value has been replaced with a portable comparison: - if ((long long) number == -1LL) { + if (number == ULLONG_MAX) { It might be worth reviewing the rest of the code for other cases where overflows are not handled correctly; even at runtime, it's often necessary to check for overflow unless such behavior is expected (especially for signed integer values, for which overflow has implementation-defined behavior).
2020-09-29 14:04:22 +00:00
if (number == ULLONG_MAX) {
RichString_appendAscii(str, CRT_colors[PROCESS_SHADOW], " N/A ");
} else if (number >= 100000LL * ONE_DECIMAL_T) {
xSnprintf(buffer, sizeof(buffer), "%11llu ", number / ONE_DECIMAL_G);
RichString_appendnAscii(str, largeNumberColor, buffer, 12);
} else if (number >= 100LL * ONE_DECIMAL_T) {
xSnprintf(buffer, sizeof(buffer), "%11llu ", number / ONE_DECIMAL_M);
RichString_appendnAscii(str, largeNumberColor, buffer, 8);
2021-07-14 17:18:27 +00:00
RichString_appendnAscii(str, processMegabytesColor, buffer + 8, 4);
} else if (number >= 10LL * ONE_DECIMAL_G) {
xSnprintf(buffer, sizeof(buffer), "%11llu ", number / ONE_DECIMAL_K);
RichString_appendnAscii(str, largeNumberColor, buffer, 5);
2021-07-14 17:18:27 +00:00
RichString_appendnAscii(str, processMegabytesColor, buffer + 5, 3);
RichString_appendnAscii(str, processColor, buffer + 8, 4);
2011-05-26 16:31:18 +00:00
} else {
xSnprintf(buffer, sizeof(buffer), "%11llu ", number);
RichString_appendnAscii(str, largeNumberColor, buffer, 2);
2021-07-14 17:18:27 +00:00
RichString_appendnAscii(str, processMegabytesColor, buffer + 2, 3);
RichString_appendnAscii(str, processColor, buffer + 5, 3);
RichString_appendnAscii(str, processShadowColor, buffer + 8, 4);
2011-05-26 16:31:18 +00:00
}
}
void Process_printTime(RichString* str, unsigned long long totalHundredths, bool coloring) {
char buffer[10];
int len;
2006-03-04 18:16:49 +00:00
unsigned long long totalSeconds = totalHundredths / 100;
2015-03-17 02:01:48 +00:00
unsigned long long hours = totalSeconds / 3600;
unsigned long long days = totalSeconds / 86400;
2015-03-17 02:01:48 +00:00
int minutes = (totalSeconds / 60) % 60;
int seconds = totalSeconds % 60;
int hundredths = totalHundredths - (totalSeconds * 100);
int yearColor = coloring ? CRT_colors[LARGE_NUMBER] : CRT_colors[PROCESS];
int dayColor = coloring ? CRT_colors[PROCESS_GIGABYTES] : CRT_colors[PROCESS];
int hourColor = coloring ? CRT_colors[PROCESS_MEGABYTES] : CRT_colors[PROCESS];
int defColor = CRT_colors[PROCESS];
if (days >= /* Ignore leapyears */365) {
int years = days / 365;
int daysLeft = days - 365 * years;
if (years >= 10000000) {
RichString_appendnAscii(str, yearColor, "eternity ", 9);
} else if (years >= 1000) {
len = xSnprintf(buffer, sizeof(buffer), "%7dy ", years);
RichString_appendnAscii(str, yearColor, buffer, len);
} else if (daysLeft >= 100) {
len = xSnprintf(buffer, sizeof(buffer), "%3dy", years);
RichString_appendnAscii(str, yearColor, buffer, len);
len = xSnprintf(buffer, sizeof(buffer), "%3dd ", daysLeft);
RichString_appendnAscii(str, dayColor, buffer, len);
} else if (daysLeft >= 10) {
len = xSnprintf(buffer, sizeof(buffer), "%4dy", years);
RichString_appendnAscii(str, yearColor, buffer, len);
len = xSnprintf(buffer, sizeof(buffer), "%2dd ", daysLeft);
RichString_appendnAscii(str, dayColor, buffer, len);
} else {
len = xSnprintf(buffer, sizeof(buffer), "%5dy", years);
RichString_appendnAscii(str, yearColor, buffer, len);
len = xSnprintf(buffer, sizeof(buffer), "%1dd ", daysLeft);
RichString_appendnAscii(str, dayColor, buffer, len);
}
} else if (days >= 100) {
int hoursLeft = hours - days * 24;
if (hoursLeft >= 10) {
len = xSnprintf(buffer, sizeof(buffer), "%4llud", days);
RichString_appendnAscii(str, dayColor, buffer, len);
len = xSnprintf(buffer, sizeof(buffer), "%2dh ", hoursLeft);
RichString_appendnAscii(str, hourColor, buffer, len);
} else {
len = xSnprintf(buffer, sizeof(buffer), "%5llud", days);
RichString_appendnAscii(str, dayColor, buffer, len);
len = xSnprintf(buffer, sizeof(buffer), "%1dh ", hoursLeft);
RichString_appendnAscii(str, hourColor, buffer, len);
}
} else if (hours >= 100) {
int minutesLeft = totalSeconds / 60 - hours * 60;
if (minutesLeft >= 10) {
len = xSnprintf(buffer, sizeof(buffer), "%4lluh", hours);
RichString_appendnAscii(str, hourColor, buffer, len);
len = xSnprintf(buffer, sizeof(buffer), "%2dm ", minutesLeft);
RichString_appendnAscii(str, defColor, buffer, len);
} else {
len = xSnprintf(buffer, sizeof(buffer), "%5lluh", hours);
RichString_appendnAscii(str, hourColor, buffer, len);
len = xSnprintf(buffer, sizeof(buffer), "%1dm ", minutesLeft);
RichString_appendnAscii(str, defColor, buffer, len);
}
} else if (hours > 0) {
len = xSnprintf(buffer, sizeof(buffer), "%2lluh", hours);
RichString_appendnAscii(str, hourColor, buffer, len);
len = xSnprintf(buffer, sizeof(buffer), "%02d:%02d ", minutes, seconds);
RichString_appendnAscii(str, defColor, buffer, len);
} else {
len = xSnprintf(buffer, sizeof(buffer), "%2d:%02d.%02d ", minutes, seconds, hundredths);
RichString_appendnAscii(str, defColor, buffer, len);
2006-03-04 18:16:49 +00:00
}
}
void Process_fillStarttimeBuffer(Process* this) {
struct tm date;
(void) localtime_r(&this->starttime_ctime, &date);
strftime(this->starttime_show, sizeof(this->starttime_show) - 1, (this->starttime_ctime > (time(NULL) - 86400)) ? "%R " : "%b%d ", &date);
}
/*
* TASK_COMM_LEN is defined to be 16 for /proc/[pid]/comm in man proc(5), but it is
* not available in an userspace header - so define it.
*
* Note: This is taken from LINUX headers, but implicitly taken for other platforms
* for sake of brevity.
*
* Note: when colorizing a basename with the comm prefix, the entire basename
* (not just the comm prefix) is colorized for better readability, and it is
* implicit that only upto (TASK_COMM_LEN - 1) could be comm.
*/
#define TASK_COMM_LEN 16
2021-07-14 17:24:18 +00:00
static bool findCommInCmdline(const char* comm, const char* cmdline, int cmdlineBasenameStart, int* pCommStart, int* pCommEnd) {
/* Try to find procComm in tokenized cmdline - this might in rare cases
* mis-identify a string or fail, if comm or cmdline had been unsuitably
* modified by the process */
2021-07-14 17:24:18 +00:00
const char* tokenBase;
size_t tokenLen;
const size_t commLen = strlen(comm);
if (cmdlineBasenameStart < 0)
return false;
2021-07-14 17:24:18 +00:00
for (const char* token = cmdline + cmdlineBasenameStart; *token;) {
for (tokenBase = token; *token && *token != '\n'; ++token) {
if (*token == '/') {
tokenBase = token + 1;
}
2006-03-04 18:16:49 +00:00
}
tokenLen = token - tokenBase;
if ((tokenLen == commLen || (tokenLen > commLen && commLen == (TASK_COMM_LEN - 1))) &&
strncmp(tokenBase, comm, commLen) == 0) {
*pCommStart = tokenBase - cmdline;
*pCommEnd = token - cmdline;
return true;
}
if (*token) {
do {
++token;
} while (*token && '\n' == *token);
}
}
return false;
}
2021-07-14 17:24:18 +00:00
static int matchCmdlinePrefixWithExeSuffix(const char* cmdline, int cmdlineBaseOffset, const char* exe, int exeBaseOffset, int exeBaseLen) {
int matchLen; /* matching length to be returned */
char delim; /* delimiter following basename */
/* cmdline prefix is an absolute path: it must match whole exe. */
if (cmdline[0] == '/') {
matchLen = exeBaseLen + exeBaseOffset;
if (strncmp(cmdline, exe, matchLen) == 0) {
delim = cmdline[matchLen];
if (delim == 0 || delim == '\n' || delim == ' ') {
return matchLen;
}
}
return 0;
}
/* cmdline prefix is a relative path: We need to first match the basename at
* cmdlineBaseOffset and then reverse match the cmdline prefix with the exe
* suffix. But there is a catch: Some processes modify their cmdline in ways
* that make htop's identification of the basename in cmdline unreliable.
* For e.g. /usr/libexec/gdm-session-worker modifies its cmdline to
* "gdm-session-worker [pam/gdm-autologin]" and htop ends up with
* proccmdlineBasenameEnd at "gdm-autologin]". This issue could arise with
* chrome as well as it stores in cmdline its concatenated argument vector,
* without NUL delimiter between the arguments (which may contain a '/')
*
* So if needed, we adjust cmdlineBaseOffset to the previous (if any)
* component of the cmdline relative path, and retry the procedure. */
bool delimFound; /* if valid basename delimiter found */
do {
/* match basename */
matchLen = exeBaseLen + cmdlineBaseOffset;
if (cmdlineBaseOffset < exeBaseOffset &&
strncmp(cmdline + cmdlineBaseOffset, exe + exeBaseOffset, exeBaseLen) == 0) {
delim = cmdline[matchLen];
if (delim == 0 || delim == '\n' || delim == ' ') {
int i, j;
/* reverse match the cmdline prefix and exe suffix */
for (i = cmdlineBaseOffset - 1, j = exeBaseOffset - 1;
i >= 0 && j >= 0 && cmdline[i] == exe[j]; --i, --j)
;
/* full match, with exe suffix being a valid relative path */
if (i < 0 && j >= 0 && exe[j] == '/')
return matchLen;
}
}
/* Try to find the previous potential cmdlineBaseOffset - it would be
* preceded by '/' or nothing, and delimited by ' ' or '\n' */
for (delimFound = false, cmdlineBaseOffset -= 2; cmdlineBaseOffset > 0; --cmdlineBaseOffset) {
if (delimFound) {
if (cmdline[cmdlineBaseOffset - 1] == '/') {
break;
}
} else if (cmdline[cmdlineBaseOffset] == ' ' || cmdline[cmdlineBaseOffset] == '\n') {
delimFound = true;
}
}
} while (delimFound);
return 0;
}
/* stpcpy, but also converts newlines to spaces */
2021-07-14 17:24:18 +00:00
static inline char* stpcpyWithNewlineConversion(char* dstStr, const char* srcStr) {
for (; *srcStr; ++srcStr) {
*dstStr++ = (*srcStr == '\n') ? ' ' : *srcStr;
}
*dstStr = 0;
return dstStr;
}
/*
* This function makes the merged Command string. It also stores the offsets of the
* basename, comm w.r.t the merged Command string - these offsets will be used by
* Process_writeCommand() for coloring. The merged Command string is also
* returned by Process_getCommand() for searching, sorting and filtering.
*/
2021-07-14 17:24:18 +00:00
void Process_makeCommandStr(Process* this) {
ProcessMergedCommand* mc = &this->mergedCommand;
const Settings* settings = this->settings;
bool showMergedCommand = settings->showMergedCommand;
bool showProgramPath = settings->showProgramPath;
bool searchCommInCmdline = settings->findCommInCmdline;
bool stripExeFromCmdline = settings->stripExeFromCmdline;
bool showThreadNames = settings->showThreadNames;
2021-04-18 16:10:04 +00:00
/* Nothing to do to (Re)Generate the Command string, if the process is:
* - a kernel thread, or
* - a zombie from before being under htop's watch, or
* - a user thread and showThreadNames is not set */
if (Process_isKernelThread(this))
return;
2021-10-11 22:45:09 +00:00
if (this->state == ZOMBIE && !this->mergedCommand.str)
2021-04-18 16:10:04 +00:00
return;
if (Process_isUserlandThread(this) && settings->showThreadNames && (showThreadNames == mc->prevShowThreadNames) && (mc->prevMergeSet == showMergedCommand))
2021-04-18 16:10:04 +00:00
return;
/* this->mergedCommand.str needs updating only if its state or contents changed.
* Its content is based on the fields cmdline, comm, and exe. */
if (
2021-07-14 17:15:09 +00:00
mc->prevMergeSet == showMergedCommand &&
mc->prevPathSet == showProgramPath &&
mc->prevCommSet == searchCommInCmdline &&
mc->prevCmdlineSet == stripExeFromCmdline &&
mc->prevShowThreadNames == showThreadNames &&
2021-07-14 17:15:09 +00:00
!mc->cmdlineChanged &&
!mc->commChanged &&
!mc->exeChanged
) {
return;
}
/* The field separtor "│" has been chosen such that it will not match any
* valid string used for searching or filtering */
2021-07-14 17:24:18 +00:00
const char* SEPARATOR = CRT_treeStr[TREE_STR_VERT];
const int SEPARATOR_LEN = strlen(SEPARATOR);
/* Check for any changed fields since we last built this string */
if (mc->cmdlineChanged || mc->commChanged || mc->exeChanged) {
free(mc->str);
/* Accommodate the column text, two field separators and terminating NUL */
2021-05-17 21:15:24 +00:00
size_t maxLen = 2 * SEPARATOR_LEN + 1;
maxLen += this->cmdline ? strlen(this->cmdline) : strlen("(zombie)");
maxLen += this->procComm ? strlen(this->procComm) : 0;
maxLen += this->procExe ? strlen(this->procExe) : 0;
mc->str = xCalloc(1, maxLen);
}
/* Preserve the settings used in this run */
mc->prevMergeSet = showMergedCommand;
mc->prevPathSet = showProgramPath;
mc->prevCommSet = searchCommInCmdline;
mc->prevCmdlineSet = stripExeFromCmdline;
mc->prevShowThreadNames = showThreadNames;
/* Mark everything as unchanged */
mc->cmdlineChanged = false;
mc->commChanged = false;
mc->exeChanged = false;
/* Reset all locations that need extra handling when actually displaying */
mc->highlightCount = 0;
memset(mc->highlights, 0, sizeof(mc->highlights));
size_t mbMismatch = 0;
2021-05-15 19:54:46 +00:00
#define WRITE_HIGHLIGHT(_offset, _length, _attr, _flags) \
do { \
/* Check if we still have capacity */ \
assert(mc->highlightCount < ARRAYSIZE(mc->highlights)); \
if (mc->highlightCount >= ARRAYSIZE(mc->highlights)) \
break; \
2021-05-15 19:54:46 +00:00
\
mc->highlights[mc->highlightCount].offset = str - strStart + (_offset) - mbMismatch; \
mc->highlights[mc->highlightCount].length = _length; \
mc->highlights[mc->highlightCount].attr = _attr; \
mc->highlights[mc->highlightCount].flags = _flags; \
mc->highlightCount++; \
} while (0)
2021-05-15 19:54:46 +00:00
#define WRITE_SEPARATOR \
do { \
WRITE_HIGHLIGHT(0, 1, CRT_colors[FAILED_READ], CMDLINE_HIGHLIGHT_FLAG_SEPARATOR); \
mbMismatch += SEPARATOR_LEN - 1; \
str = stpcpy(str, SEPARATOR); \
} while (0)
const int baseAttr = Process_isThread(this) ? CRT_colors[PROCESS_THREAD_BASENAME] : CRT_colors[PROCESS_BASENAME];
const int commAttr = Process_isThread(this) ? CRT_colors[PROCESS_THREAD_COMM] : CRT_colors[PROCESS_COMM];
const int delExeAttr = CRT_colors[FAILED_READ];
const int delLibAttr = CRT_colors[PROCESS_TAG];
/* Establish some shortcuts to data we need */
2021-07-14 17:24:18 +00:00
const char* cmdline = this->cmdline;
const char* procComm = this->procComm;
const char* procExe = this->procExe;
2021-07-14 17:24:18 +00:00
char* strStart = mc->str;
char* str = strStart;
int cmdlineBasenameStart = this->cmdlineBasenameStart;
int cmdlineBasenameEnd = this->cmdlineBasenameEnd;
if (!cmdline) {
cmdlineBasenameStart = 0;
cmdlineBasenameEnd = 0;
cmdline = "(zombie)";
}
assert(cmdlineBasenameStart >= 0);
assert(cmdlineBasenameStart <= (int)strlen(cmdline));
if (!showMergedCommand || !procExe || !procComm) { /* fall back to cmdline */
if ((showMergedCommand || (Process_isUserlandThread(this) && showThreadNames)) && procComm && strlen(procComm)) { /* set column to or prefix it with comm */
if (strncmp(cmdline + cmdlineBasenameStart, procComm, MINIMUM(TASK_COMM_LEN - 1, strlen(procComm))) != 0) {
WRITE_HIGHLIGHT(0, strlen(procComm), commAttr, CMDLINE_HIGHLIGHT_FLAG_COMM);
str = stpcpy(str, procComm);
if(!showMergedCommand)
return;
WRITE_SEPARATOR;
}
}
if (cmdlineBasenameEnd > cmdlineBasenameStart)
WRITE_HIGHLIGHT(showProgramPath ? cmdlineBasenameStart : 0, cmdlineBasenameEnd - cmdlineBasenameStart, baseAttr, CMDLINE_HIGHLIGHT_FLAG_BASENAME);
if (this->procExeDeleted)
WRITE_HIGHLIGHT(showProgramPath ? cmdlineBasenameStart : 0, cmdlineBasenameEnd - cmdlineBasenameStart, delExeAttr, CMDLINE_HIGHLIGHT_FLAG_DELETED);
else if (this->usesDeletedLib)
WRITE_HIGHLIGHT(showProgramPath ? cmdlineBasenameStart : 0, cmdlineBasenameEnd - cmdlineBasenameStart, delLibAttr, CMDLINE_HIGHLIGHT_FLAG_DELETED);
(void)stpcpyWithNewlineConversion(str, cmdline + (showProgramPath ? 0 : cmdlineBasenameStart));
return;
}
int exeLen = strlen(this->procExe);
int exeBasenameOffset = this->procExeBasenameOffset;
int exeBasenameLen = exeLen - exeBasenameOffset;
assert(exeBasenameOffset >= 0);
assert(exeBasenameOffset <= (int)strlen(procExe));
bool haveCommInExe = false;
if (procExe && procComm && (!Process_isUserlandThread(this) || showThreadNames)) {
haveCommInExe = strncmp(procExe + exeBasenameOffset, procComm, TASK_COMM_LEN - 1) == 0;
}
/* Start with copying exe */
if (showProgramPath) {
if (haveCommInExe)
WRITE_HIGHLIGHT(exeBasenameOffset, exeBasenameLen, commAttr, CMDLINE_HIGHLIGHT_FLAG_COMM);
WRITE_HIGHLIGHT(exeBasenameOffset, exeBasenameLen, baseAttr, CMDLINE_HIGHLIGHT_FLAG_BASENAME);
if (this->procExeDeleted)
WRITE_HIGHLIGHT(exeBasenameOffset, exeBasenameLen, delExeAttr, CMDLINE_HIGHLIGHT_FLAG_DELETED);
else if (this->usesDeletedLib)
WRITE_HIGHLIGHT(exeBasenameOffset, exeBasenameLen, delLibAttr, CMDLINE_HIGHLIGHT_FLAG_DELETED);
str = stpcpy(str, procExe);
} else {
if (haveCommInExe)
WRITE_HIGHLIGHT(0, exeBasenameLen, commAttr, CMDLINE_HIGHLIGHT_FLAG_COMM);
WRITE_HIGHLIGHT(0, exeBasenameLen, baseAttr, CMDLINE_HIGHLIGHT_FLAG_BASENAME);
if (this->procExeDeleted)
WRITE_HIGHLIGHT(0, exeBasenameLen, delExeAttr, CMDLINE_HIGHLIGHT_FLAG_DELETED);
else if (this->usesDeletedLib)
WRITE_HIGHLIGHT(0, exeBasenameLen, delLibAttr, CMDLINE_HIGHLIGHT_FLAG_DELETED);
str = stpcpy(str, procExe + exeBasenameOffset);
}
bool haveCommInCmdline = false;
int commStart = 0;
int commEnd = 0;
/* Try to match procComm with procExe's basename: This is reliable (predictable) */
if (searchCommInCmdline) {
/* commStart/commEnd will be adjusted later along with cmdline */
haveCommInCmdline = (!Process_isUserlandThread(this) || showThreadNames) && findCommInCmdline(procComm, cmdline, cmdlineBasenameStart, &commStart, &commEnd);
}
int matchLen = matchCmdlinePrefixWithExeSuffix(cmdline, cmdlineBasenameStart, procExe, exeBasenameOffset, exeBasenameLen);
bool haveCommField = false;
if (!haveCommInExe && !haveCommInCmdline && procComm && (!Process_isUserlandThread(this) || showThreadNames)) {
WRITE_SEPARATOR;
WRITE_HIGHLIGHT(0, strlen(procComm), commAttr, CMDLINE_HIGHLIGHT_FLAG_COMM);
str = stpcpy(str, procComm);
haveCommField = true;
}
if (matchLen) {
/* strip the matched exe prefix */
cmdline += matchLen;
commStart -= matchLen;
commEnd -= matchLen;
}
if (!matchLen || (haveCommField && *cmdline)) {
/* cmdline will be a separate field */
WRITE_SEPARATOR;
}
if (!haveCommInExe && haveCommInCmdline && !haveCommField && (!Process_isUserlandThread(this) || showThreadNames))
WRITE_HIGHLIGHT(commStart, commEnd - commStart, commAttr, CMDLINE_HIGHLIGHT_FLAG_COMM);
/* Display cmdline if it hasn't been consumed by procExe */
if (*cmdline)
(void)stpcpyWithNewlineConversion(str, cmdline);
#undef WRITE_SEPARATOR
#undef WRITE_HIGHLIGHT
}
void Process_writeCommand(const Process* this, int attr, int baseAttr, RichString* str) {
(void)baseAttr;
2021-07-14 17:24:18 +00:00
const ProcessMergedCommand* mc = &this->mergedCommand;
int strStart = RichString_size(str);
const bool highlightBaseName = this->settings->highlightBaseName;
const bool highlightSeparator = true;
const bool highlightDeleted = this->settings->highlightDeletedExe;
if (!this->mergedCommand.str) {
int len = 0;
const char* cmdline = this->cmdline;
if (highlightBaseName || !this->settings->showProgramPath) {
int basename = 0;
for (int i = 0; i < this->cmdlineBasenameEnd; i++) {
if (cmdline[i] == '/') {
basename = i + 1;
} else if (cmdline[i] == ':') {
len = i + 1;
break;
}
}
if (len == 0) {
if (this->settings->showProgramPath) {
strStart += basename;
} else {
cmdline += basename;
}
len = this->cmdlineBasenameEnd - basename;
2020-11-01 00:09:51 +00:00
}
}
RichString_appendWide(str, attr, cmdline);
if (this->settings->highlightBaseName) {
RichString_setAttrn(str, baseAttr, strStart, len);
}
return;
2006-03-04 18:16:49 +00:00
}
RichString_appendWide(str, attr, this->mergedCommand.str);
for (size_t i = 0, hlCount = CLAMP(mc->highlightCount, 0, ARRAYSIZE(mc->highlights)); i < hlCount; i++) {
2021-07-14 17:24:18 +00:00
const ProcessCmdlineHighlight* hl = &mc->highlights[i];
if (!hl->length)
continue;
if (hl->flags & CMDLINE_HIGHLIGHT_FLAG_SEPARATOR)
if (!highlightSeparator)
continue;
if (hl->flags & CMDLINE_HIGHLIGHT_FLAG_BASENAME)
if (!highlightBaseName)
continue;
if (hl->flags & CMDLINE_HIGHLIGHT_FLAG_DELETED)
if (!highlightDeleted)
continue;
RichString_setAttrn(str, hl->attr, strStart + hl->offset, hl->length);
2020-11-01 00:09:51 +00:00
}
2006-03-04 18:16:49 +00:00
}
void Process_printRate(RichString* str, double rate, bool coloring) {
char buffer[16];
int largeNumberColor = CRT_colors[LARGE_NUMBER];
int processMegabytesColor = CRT_colors[PROCESS_MEGABYTES];
int processColor = CRT_colors[PROCESS];
int shadowColor = CRT_colors[PROCESS_SHADOW];
if (!coloring) {
largeNumberColor = CRT_colors[PROCESS];
processMegabytesColor = CRT_colors[PROCESS];
}
if (isnan(rate)) {
RichString_appendAscii(str, shadowColor, " N/A ");
} else if (rate < 0.005) {
int len = snprintf(buffer, sizeof(buffer), "%7.2f B/s ", rate);
RichString_appendnAscii(str, shadowColor, buffer, len);
} else if (rate < ONE_K) {
int len = snprintf(buffer, sizeof(buffer), "%7.2f B/s ", rate);
RichString_appendnAscii(str, processColor, buffer, len);
} else if (rate < ONE_M) {
int len = snprintf(buffer, sizeof(buffer), "%7.2f K/s ", rate / ONE_K);
RichString_appendnAscii(str, processColor, buffer, len);
} else if (rate < ONE_G) {
int len = snprintf(buffer, sizeof(buffer), "%7.2f M/s ", rate / ONE_M);
RichString_appendnAscii(str, processMegabytesColor, buffer, len);
} else if (rate < ONE_T) {
int len = snprintf(buffer, sizeof(buffer), "%7.2f G/s ", rate / ONE_G);
RichString_appendnAscii(str, largeNumberColor, buffer, len);
} else if (rate < ONE_P) {
int len = snprintf(buffer, sizeof(buffer), "%7.2f T/s ", rate / ONE_T);
RichString_appendnAscii(str, largeNumberColor, buffer, len);
} else {
int len = snprintf(buffer, sizeof(buffer), "%7.2f P/s ", rate / ONE_P);
RichString_appendnAscii(str, largeNumberColor, buffer, len);
}
}
void Process_printLeftAlignedField(RichString* str, int attr, const char* content, unsigned int width) {
int columns = width;
RichString_appendnWideColumns(str, attr, content, strlen(content), &columns);
RichString_appendChr(str, attr, ' ', width + 1 - columns);
}
void Process_printPercentage(float val, char* buffer, int n, uint8_t width, int* attr) {
if (val >= 0) {
if (val < 99.9F) {
if (val < 0.05F) {
*attr = CRT_colors[PROCESS_SHADOW];
}
xSnprintf(buffer, n, "%*.1f ", width, val);
} else {
*attr = CRT_colors[PROCESS_MEGABYTES];
if (val < 100.0F)
val = 100.0F; // Don't round down and display "val" as "99".
xSnprintf(buffer, n, "%*.0f ", width, val);
}
} else {
*attr = CRT_colors[PROCESS_SHADOW];
xSnprintf(buffer, n, "%*.*s ", width, width, "N/A");
}
}
2021-10-11 22:45:09 +00:00
static inline char processStateChar(ProcessState state) {
switch (state) {
case UNKNOWN: return '?';
case RUNNABLE: return 'U';
case RUNNING: return 'R';
case QUEUED: return 'Q';
case WAITING: return 'W';
case UNINTERRUPTIBLE_WAIT: return 'D';
case BLOCKED: return 'B';
case PAGING: return 'P';
case STOPPED: return 'T';
case TRACED: return 't';
case ZOMBIE: return 'Z';
case DEFUNCT: return 'X';
case IDLE: return 'I';
case SLEEPING: return 'S';
default:
assert(0);
return '!';
}
}
void Process_writeField(const Process* this, RichString* str, ProcessField field) {
char buffer[256];
size_t n = sizeof(buffer);
2006-03-04 18:16:49 +00:00
int attr = CRT_colors[DEFAULT_COLOR];
bool coloring = this->settings->highlightMegabytes;
2006-03-04 18:16:49 +00:00
switch (field) {
case COMM: {
int baseattr = CRT_colors[PROCESS_BASENAME];
if (this->settings->highlightThreads && Process_isThread(this)) {
attr = CRT_colors[PROCESS_THREAD];
baseattr = CRT_colors[PROCESS_THREAD_BASENAME];
}
const ScreenSettings* ss = this->settings->ss;
if (!ss->treeView || this->indent == 0) {
Process_writeCommand(this, attr, baseattr, str);
2006-03-04 18:16:49 +00:00
return;
}
2020-11-01 00:09:51 +00:00
char* buf = buffer;
int maxIndent = 0;
bool lastItem = (this->indent < 0);
int indent = (this->indent < 0 ? -this->indent : this->indent);
for (int i = 0; i < 32; i++) {
if (indent & (1U << i)) {
2021-07-14 17:18:27 +00:00
maxIndent = i + 1;
}
}
for (int i = 0; i < maxIndent - 1; i++) {
int written, ret;
if (indent & (1 << i)) {
ret = xSnprintf(buf, n, "%s ", CRT_treeStr[TREE_STR_VERT]);
} else {
ret = xSnprintf(buf, n, " ");
}
if (ret < 0 || (size_t)ret >= n) {
written = n;
} else {
written = ret;
}
buf += written;
n -= written;
2006-03-04 18:16:49 +00:00
}
const char* draw = CRT_treeStr[lastItem ? TREE_STR_BEND : TREE_STR_RTEE];
xSnprintf(buf, n, "%s%s ", draw, this->showChildren ? CRT_treeStr[TREE_STR_SHUT] : CRT_treeStr[TREE_STR_OPEN] );
RichString_appendWide(str, CRT_colors[PROCESS_TREE], buffer);
Process_writeCommand(this, attr, baseattr, str);
return;
2006-03-04 18:16:49 +00:00
}
case PROC_COMM: {
const char* procComm;
if (this->procComm) {
attr = CRT_colors[Process_isUserlandThread(this) ? PROCESS_THREAD_COMM : PROCESS_COMM];
procComm = this->procComm;
} else {
attr = CRT_colors[PROCESS_SHADOW];
procComm = Process_isKernelThread(this) ? kthreadID : "N/A";
}
Process_printLeftAlignedField(str, attr, procComm, TASK_COMM_LEN - 1);
return;
}
case PROC_EXE: {
const char* procExe;
if (this->procExe) {
attr = CRT_colors[Process_isUserlandThread(this) ? PROCESS_THREAD_BASENAME : PROCESS_BASENAME];
if (this->settings->highlightDeletedExe) {
if (this->procExeDeleted)
attr = CRT_colors[FAILED_READ];
else if (this->usesDeletedLib)
attr = CRT_colors[PROCESS_TAG];
}
procExe = this->procExe + this->procExeBasenameOffset;
} else {
attr = CRT_colors[PROCESS_SHADOW];
procExe = Process_isKernelThread(this) ? kthreadID : "N/A";
}
Process_printLeftAlignedField(str, attr, procExe, TASK_COMM_LEN - 1);
return;
}
case CWD: {
const char* cwd;
if (!this->procCwd) {
attr = CRT_colors[PROCESS_SHADOW];
cwd = "N/A";
} else if (String_startsWith(this->procCwd, "/proc/") && strstr(this->procCwd, " (deleted)") != NULL) {
attr = CRT_colors[PROCESS_SHADOW];
cwd = "main thread terminated";
} else {
cwd = this->procCwd;
}
Process_printLeftAlignedField(str, attr, cwd, 25);
return;
}
case ELAPSED: {
const uint64_t rt = this->processList->realtimeMs;
const uint64_t st = this->starttime_ctime * 1000;
const uint64_t dt =
rt < st ? 0 :
rt - st;
Process_printTime(str, /* convert to hundreds of a second */ dt / 10, coloring);
return;
}
case MAJFLT: Process_printCount(str, this->majflt, coloring); return;
case MINFLT: Process_printCount(str, this->minflt, coloring); return;
case M_RESIDENT: Process_printKBytes(str, this->m_resident, coloring); return;
case M_VIRT: Process_printKBytes(str, this->m_virt, coloring); return;
case NICE:
xSnprintf(buffer, n, "%3ld ", this->nice);
attr = this->nice < 0 ? CRT_colors[PROCESS_HIGH_PRIORITY]
: this->nice > 0 ? CRT_colors[PROCESS_LOW_PRIORITY]
: CRT_colors[PROCESS_SHADOW];
break;
case NLWP:
if (this->nlwp == 1)
attr = CRT_colors[PROCESS_SHADOW];
xSnprintf(buffer, n, "%4ld ", this->nlwp);
break;
case PERCENT_CPU: Process_printPercentage(this->percent_cpu, buffer, n, Process_fieldWidths[PERCENT_CPU], &attr); break;
case PERCENT_NORM_CPU: {
float cpuPercentage = this->percent_cpu / this->processList->activeCPUs;
Process_printPercentage(cpuPercentage, buffer, n, Process_fieldWidths[PERCENT_CPU], &attr);
break;
}
case PERCENT_MEM: Process_printPercentage(this->percent_mem, buffer, n, 4, &attr); break;
case PGRP: xSnprintf(buffer, n, "%*d ", Process_pidDigits, this->pgrp); break;
case PID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, this->pid); break;
case PPID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, this->ppid); break;
case PRIORITY:
if (this->priority <= -100)
xSnprintf(buffer, n, " RT ");
else
xSnprintf(buffer, n, "%3ld ", this->priority);
break;
case PROCESSOR: xSnprintf(buffer, n, "%3d ", Settings_cpuId(this->settings, this->processor)); break;
case SESSION: xSnprintf(buffer, n, "%*d ", Process_pidDigits, this->session); break;
case STARTTIME: xSnprintf(buffer, n, "%s", this->starttime_show); break;
case STATE:
2021-10-11 22:45:09 +00:00
xSnprintf(buffer, n, "%c ", processStateChar(this->state));
switch (this->state) {
2021-10-11 22:45:09 +00:00
case RUNNABLE:
case RUNNING:
case TRACED:
attr = CRT_colors[PROCESS_RUN_STATE];
break;
2021-10-11 22:45:09 +00:00
case BLOCKED:
case DEFUNCT:
case STOPPED:
case UNINTERRUPTIBLE_WAIT:
2021-10-11 22:45:09 +00:00
case ZOMBIE:
attr = CRT_colors[PROCESS_D_STATE];
break;
2021-10-11 22:45:09 +00:00
case QUEUED:
case WAITING:
case IDLE:
case SLEEPING:
attr = CRT_colors[PROCESS_SHADOW];
break;
2021-10-11 22:45:09 +00:00
case UNKNOWN:
case PAGING:
break;
2014-10-14 01:30:17 +00:00
}
2006-03-04 18:16:49 +00:00
break;
case ST_UID: xSnprintf(buffer, n, "%*d ", Process_uidDigits, this->st_uid); break;
case TIME: Process_printTime(str, this->time, coloring); return;
case TGID:
if (this->tgid == this->pid)
attr = CRT_colors[PROCESS_SHADOW];
xSnprintf(buffer, n, "%*d ", Process_pidDigits, this->tgid);
break;
case TPGID: xSnprintf(buffer, n, "%*d ", Process_pidDigits, this->tpgid); break;
case TTY:
if (!this->tty_name) {
attr = CRT_colors[PROCESS_SHADOW];
xSnprintf(buffer, n, "(no tty) ");
} else {
const char* name = String_startsWith(this->tty_name, "/dev/") ? (this->tty_name + strlen("/dev/")) : this->tty_name;
xSnprintf(buffer, n, "%-8s ", name);
}
break;
case USER:
2020-11-04 16:46:04 +00:00
if (Process_getuid != this->st_uid)
2006-03-04 18:16:49 +00:00
attr = CRT_colors[PROCESS_SHADOW];
2006-07-12 01:35:59 +00:00
if (this->user) {
Process_printLeftAlignedField(str, attr, this->user, 10);
return;
2006-03-04 18:16:49 +00:00
}
xSnprintf(buffer, n, "%-10d ", this->st_uid);
2006-03-04 18:16:49 +00:00
break;
default:
if (DynamicColumn_writeField(this, str, field))
return;
assert(0 && "Process_writeField: default key reached"); /* should never be reached */
xSnprintf(buffer, n, "- ");
break;
2006-03-04 18:16:49 +00:00
}
RichString_appendAscii(str, attr, buffer);
2006-03-04 18:16:49 +00:00
}
void Process_display(const Object* cast, RichString* out) {
const Process* this = (const Process*) cast;
const ProcessField* fields = this->settings->ss->fields;
for (int i = 0; fields[i]; i++)
2015-04-01 02:23:10 +00:00
As_Process(this)->writeField(this, out, fields[i]);
2020-11-01 00:09:51 +00:00
2020-11-04 16:46:04 +00:00
if (this->settings->shadowOtherUsers && this->st_uid != Process_getuid) {
RichString_setAttr(out, CRT_colors[PROCESS_SHADOW]);
2020-11-01 00:09:51 +00:00
}
if (this->tag == true) {
RichString_setAttr(out, CRT_colors[PROCESS_TAG]);
2020-11-01 00:09:51 +00:00
}
2020-10-31 01:56:16 +00:00
if (this->settings->highlightChanges) {
if (Process_isTomb(this)) {
2020-10-31 01:56:16 +00:00
out->highlightAttr = CRT_colors[PROCESS_TOMB];
} else if (Process_isNew(this)) {
2020-11-01 00:36:53 +00:00
out->highlightAttr = CRT_colors[PROCESS_NEW];
}
2020-10-31 01:56:16 +00:00
}
assert(RichString_size(out) > 0);
}
void Process_done(Process* this) {
assert (this != NULL);
free(this->cmdline);
free(this->procComm);
free(this->procExe);
free(this->procCwd);
free(this->mergedCommand.str);
free(this->tty_name);
}
/* This function returns the string displayed in Command column, so that sorting
* happens on what is displayed - whether comm, full path, basename, etc.. So
* this follows Process_writeField(COMM) and Process_writeCommand */
const char* Process_getCommand(const Process* this) {
if ((Process_isUserlandThread(this) && this->settings->showThreadNames) || !this->mergedCommand.str) {
return this->cmdline;
}
return this->mergedCommand.str;
2020-10-17 10:54:45 +00:00
}
2020-10-05 11:19:50 +00:00
const ProcessClass Process_class = {
2015-04-01 02:23:10 +00:00
.super = {
.extends = Class(Object),
.display = Process_display,
.delete = Process_delete,
.compare = Process_compare
},
.writeField = Process_writeField,
};
void Process_init(Process* this, const Settings* settings) {
this->settings = settings;
this->tag = false;
2010-06-17 19:02:03 +00:00
this->showChildren = true;
this->show = true;
this->updated = false;
this->cmdlineBasenameEnd = -1;
this->st_uid = (uid_t)-1;
2020-11-01 00:09:51 +00:00
2020-11-04 16:46:04 +00:00
if (Process_getuid == (uid_t)-1) {
2020-11-01 00:09:51 +00:00
Process_getuid = getuid();
}
}
void Process_toggleTag(Process* this) {
2020-12-08 21:37:15 +00:00
this->tag = !this->tag;
}
2020-10-31 01:56:16 +00:00
bool Process_isNew(const Process* this) {
2020-11-01 00:36:53 +00:00
assert(this->processList);
if (this->processList->monotonicMs >= this->seenStampMs) {
return this->processList->monotonicMs - this->seenStampMs <= 1000 * (uint64_t)this->processList->settings->highlightDelaySecs;
}
2020-10-31 01:56:16 +00:00
return false;
}
bool Process_isTomb(const Process* this) {
2021-07-14 17:15:09 +00:00
return this->tombStampMs > 0;
2020-10-31 01:56:16 +00:00
}
bool Process_setPriority(Process* this, int priority) {
if (Settings_isReadonly())
return false;
int old_prio = getpriority(PRIO_PROCESS, this->pid);
int err = setpriority(PRIO_PROCESS, this->pid, priority);
if (err == 0 && old_prio != getpriority(PRIO_PROCESS, this->pid)) {
this->nice = priority;
}
return (err == 0);
}
bool Process_changePriorityBy(Process* this, Arg delta) {
return Process_setPriority(this, this->nice + delta.i);
2012-10-04 23:59:45 +00:00
}
bool Process_sendSignal(Process* this, Arg sgn) {
return kill(this->pid, sgn.i) == 0;
}
int Process_compare(const void* v1, const void* v2) {
2021-07-14 17:24:18 +00:00
const Process* p1 = (const Process*)v1;
const Process* p2 = (const Process*)v2;
2021-07-14 17:24:18 +00:00
const Settings* settings = p1->settings;
const ScreenSettings* ss = settings->ss;
ProcessField key = ScreenSettings_getActiveSortKey(ss);
int result = Process_compareByKey(p1, p2, key);
// Implement tie-breaker (needed to make tree mode more stable)
if (!result)
return SPACESHIP_NUMBER(p1->pid, p2->pid);
return (ScreenSettings_getActiveDirection(ss) == 1) ? result : -result;
}
int Process_compareByKey_Base(const Process* p1, const Process* p2, ProcessField key) {
int r;
switch (key) {
2006-03-04 18:16:49 +00:00
case PERCENT_CPU:
case PERCENT_NORM_CPU:
return SPACESHIP_NUMBER(p1->percent_cpu, p2->percent_cpu);
2006-03-04 18:16:49 +00:00
case PERCENT_MEM:
return SPACESHIP_NUMBER(p1->m_resident, p2->m_resident);
2006-03-04 18:16:49 +00:00
case COMM:
2020-10-17 10:54:45 +00:00
return SPACESHIP_NULLSTR(Process_getCommand(p1), Process_getCommand(p2));
case PROC_COMM: {
2021-07-14 17:24:18 +00:00
const char* comm1 = p1->procComm ? p1->procComm : (Process_isKernelThread(p1) ? kthreadID : "");
const char* comm2 = p2->procComm ? p2->procComm : (Process_isKernelThread(p2) ? kthreadID : "");
return SPACESHIP_NULLSTR(comm1, comm2);
}
case PROC_EXE: {
2021-07-14 17:24:18 +00:00
const char* exe1 = p1->procExe ? (p1->procExe + p1->procExeBasenameOffset) : (Process_isKernelThread(p1) ? kthreadID : "");
const char* exe2 = p2->procExe ? (p2->procExe + p2->procExeBasenameOffset) : (Process_isKernelThread(p2) ? kthreadID : "");
return SPACESHIP_NULLSTR(exe1, exe2);
}
case CWD:
return SPACESHIP_NULLSTR(p1->procCwd, p2->procCwd);
case ELAPSED:
r = -SPACESHIP_NUMBER(p1->starttime_ctime, p2->starttime_ctime);
return r != 0 ? r : SPACESHIP_NUMBER(p1->pid, p2->pid);
case MAJFLT:
return SPACESHIP_NUMBER(p1->majflt, p2->majflt);
case MINFLT:
return SPACESHIP_NUMBER(p1->minflt, p2->minflt);
case M_RESIDENT:
return SPACESHIP_NUMBER(p1->m_resident, p2->m_resident);
case M_VIRT:
return SPACESHIP_NUMBER(p1->m_virt, p2->m_virt);
case NICE:
return SPACESHIP_NUMBER(p1->nice, p2->nice);
case NLWP:
return SPACESHIP_NUMBER(p1->nlwp, p2->nlwp);
case PGRP:
return SPACESHIP_NUMBER(p1->pgrp, p2->pgrp);
case PID:
return SPACESHIP_NUMBER(p1->pid, p2->pid);
case PPID:
return SPACESHIP_NUMBER(p1->ppid, p2->ppid);
case PRIORITY:
return SPACESHIP_NUMBER(p1->priority, p2->priority);
2015-03-17 02:01:48 +00:00
case PROCESSOR:
return SPACESHIP_NUMBER(p1->processor, p2->processor);
case SESSION:
return SPACESHIP_NUMBER(p1->session, p2->session);
case STARTTIME:
r = SPACESHIP_NUMBER(p1->starttime_ctime, p2->starttime_ctime);
return r != 0 ? r : SPACESHIP_NUMBER(p1->pid, p2->pid);
case STATE:
2021-10-11 22:45:09 +00:00
return SPACESHIP_NUMBER(p1->state, p2->state);
case ST_UID:
return SPACESHIP_NUMBER(p1->st_uid, p2->st_uid);
case TIME:
return SPACESHIP_NUMBER(p1->time, p2->time);
case TGID:
return SPACESHIP_NUMBER(p1->tgid, p2->tgid);
case TPGID:
return SPACESHIP_NUMBER(p1->tpgid, p2->tpgid);
case TTY:
/* Order no tty last */
return SPACESHIP_DEFAULTSTR(p1->tty_name, p2->tty_name, "\x7F");
case USER:
return SPACESHIP_NULLSTR(p1->user, p2->user);
2006-03-04 18:16:49 +00:00
default:
2021-12-08 12:02:18 +00:00
CRT_debug("Process_compareByKey_Base() called with key %d", key);
assert(0 && "Process_compareByKey_Base: default key reached"); /* should never be reached */
return SPACESHIP_NUMBER(p1->pid, p2->pid);
2006-03-04 18:16:49 +00:00
}
}
void Process_updateComm(Process* this, const char* comm) {
if (!this->procComm && !comm)
return;
if (this->procComm && comm && String_eq(this->procComm, comm))
return;
free(this->procComm);
this->procComm = comm ? xStrdup(comm) : NULL;
this->mergedCommand.commChanged = true;
}
static int skipPotentialPath(const char* cmdline, int end) {
if (cmdline[0] != '/')
return 0;
int slash = 0;
for (int i = 1; i < end; i++) {
2021-07-14 17:18:27 +00:00
if (cmdline[i] == '/' && cmdline[i + 1] != '\0') {
slash = i + 1;
continue;
}
2021-07-14 17:18:27 +00:00
if (cmdline[i] == ' ' && cmdline[i - 1] != '\\')
return slash;
2021-07-14 17:18:27 +00:00
if (cmdline[i] == ':' && cmdline[i + 1] == ' ')
return slash;
}
return slash;
}
void Process_updateCmdline(Process* this, const char* cmdline, int basenameStart, int basenameEnd) {
assert(basenameStart >= 0);
assert((cmdline && basenameStart < (int)strlen(cmdline)) || (!cmdline && basenameStart == 0));
assert((basenameEnd > basenameStart) || (basenameEnd == 0 && basenameStart == 0));
assert((cmdline && basenameEnd <= (int)strlen(cmdline)) || (!cmdline && basenameEnd == 0));
if (!this->cmdline && !cmdline)
return;
if (this->cmdline && cmdline && String_eq(this->cmdline, cmdline))
return;
free(this->cmdline);
this->cmdline = cmdline ? xStrdup(cmdline) : NULL;
this->cmdlineBasenameStart = (basenameStart || !cmdline) ? basenameStart : skipPotentialPath(cmdline, basenameEnd);
this->cmdlineBasenameEnd = basenameEnd;
this->mergedCommand.cmdlineChanged = true;
}
void Process_updateExe(Process* this, const char* exe) {
if (!this->procExe && !exe)
return;
if (this->procExe && exe && String_eq(this->procExe, exe))
return;
free(this->procExe);
if (exe) {
this->procExe = xStrdup(exe);
const char* lastSlash = strrchr(exe, '/');
this->procExeBasenameOffset = (lastSlash && *(lastSlash + 1) != '\0' && lastSlash != exe) ? (lastSlash - exe + 1) : 0;
} else {
this->procExe = NULL;
this->procExeBasenameOffset = 0;
}
this->mergedCommand.exeChanged = true;
}
uint8_t Process_fieldWidths[LAST_PROCESSFIELD] = { 0 };
void Process_resetFieldWidths() {
for (size_t i = 0; i < LAST_PROCESSFIELD; i++) {
if (!Process_fields[i].autoWidth)
continue;
size_t len = strlen(Process_fields[i].title);
assert(len <= UINT8_MAX);
Process_fieldWidths[i] = (uint8_t)len;
}
}
void Process_updateFieldWidth(ProcessField key, size_t width) {
if (width > UINT8_MAX)
Process_fieldWidths[key] = UINT8_MAX;
else if (width > Process_fieldWidths[key])
Process_fieldWidths[key] = (uint8_t)width;
}
void Process_updateCPUFieldWidths(float percentage) {
if (percentage < 99.9) {
Process_updateFieldWidth(PERCENT_CPU, 4);
Process_updateFieldWidth(PERCENT_NORM_CPU, 4);
return;
}
uint8_t width = ceil(log10(percentage + .2));
Process_updateFieldWidth(PERCENT_CPU, width);
Process_updateFieldWidth(PERCENT_NORM_CPU, width);
}