diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index c11be4eafa..95bf1db39d 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -3100,6 +3100,7 @@ virProcessGetMaxMemLock; virProcessGetNamespaces; virProcessGetPids; virProcessGetStartTime; +virProcessGetStat; virProcessGroupGet; virProcessGroupKill; virProcessKill; diff --git a/src/util/virprocess.c b/src/util/virprocess.c index 81de90200e..1d5ef048b2 100644 --- a/src/util/virprocess.c +++ b/src/util/virprocess.c @@ -1721,3 +1721,80 @@ virProcessSetScheduler(pid_t pid G_GNUC_UNUSED, } #endif /* !WITH_SCHED_SETSCHEDULER */ + +/* + * Get all stat fields for a process based on pid and tid: + * - pid == 0 && tid == 0 => /proc/self/stat + * - pid != 0 && tid == 0 => /proc//stat + * - pid == 0 && tid != 0 => /proc/self/task//stat + * - pid != 0 && tid != 0 => /proc//task//stat + * and return them as array of strings. + */ +GStrv +virProcessGetStat(pid_t pid, + pid_t tid) +{ + int len = 10 * 1024; /* 10kB ought to be enough for everyone */ + g_autofree char *buf = NULL; + g_autofree char *path = NULL; + GStrv rest = NULL; + GStrv ret = NULL; + char *comm = NULL; + char *rparen = NULL; + size_t nrest = 0; + + if (pid) { + if (tid) + path = g_strdup_printf("/proc/%d/task/%d/stat", (int)pid, (int)tid); + else + path = g_strdup_printf("/proc/%d/stat", (int)pid); + } else { + if (tid) + path = g_strdup_printf("/proc/self/task/%d/stat", (int)tid); + else + path = g_strdup("/proc/self/stat"); + } + + len = virFileReadAllQuiet(path, len, &buf); + if (len < 0) + return NULL; + + /* eliminate trailing spaces */ + while (len > 0 && g_ascii_isspace(buf[--len])) + buf[len] = '\0'; + + /* Find end of the first field */ + if (!(comm = strchr(buf, ' '))) + return NULL; + *comm = '\0'; + + /* Check start of the second field (filename of the executable, in + * parentheses) */ + comm++; + if (*comm != '(') + return NULL; + comm++; + + /* Check end of the second field (last closing parenthesis) */ + rparen = strrchr(comm, ')'); + if (!rparen) + return NULL; + *rparen = '\0'; + + /* We need to check that the next char is not '\0', but why not just opt in + * for the safer way of checking whether it is ' ' (space) instead */ + if (rparen[1] != ' ') + return NULL; + + rest = g_strsplit(rparen + 2, " ", 0); + nrest = g_strv_length(rest); + ret = g_new0(char *, nrest + 3); + ret[0] = g_strdup(buf); + ret[1] = g_strdup(comm); + memcpy(ret + 2, rest, nrest * sizeof(char *)); + + /* Do not use g_strfreev() as individual elements they were moved to @ret. */ + VIR_FREE(rest); + + return ret; +} diff --git a/src/util/virprocess.h b/src/util/virprocess.h index 9910331a0c..82b7403964 100644 --- a/src/util/virprocess.h +++ b/src/util/virprocess.h @@ -117,6 +117,72 @@ int virProcessSetupPrivateMountNS(void); int virProcessSetScheduler(pid_t pid, virProcessSchedPolicy policy, int priority); + +GStrv virProcessGetStat(pid_t pid, pid_t tid); + +/* These constants are modelled after proc(5) */ +enum { + VIR_PROCESS_STAT_PID, + VIR_PROCESS_STAT_COMM, + VIR_PROCESS_STAT_STATE, + VIR_PROCESS_STAT_PPID, + VIR_PROCESS_STAT_PGRP, + VIR_PROCESS_STAT_SESSION, + VIR_PROCESS_STAT_TTY_NR, + VIR_PROCESS_STAT_TPGID, + VIR_PROCESS_STAT_FLAGS, + VIR_PROCESS_STAT_MINFLT, + VIR_PROCESS_STAT_CMINFLT, + VIR_PROCESS_STAT_MAJFLT, + VIR_PROCESS_STAT_CMAJFLT, + VIR_PROCESS_STAT_UTIME, + VIR_PROCESS_STAT_STIME, + VIR_PROCESS_STAT_CUTIME, + VIR_PROCESS_STAT_CSTIME, + VIR_PROCESS_STAT_PRIORITY, + VIR_PROCESS_STAT_NICE, + VIR_PROCESS_STAT_NUM_THREADS, + VIR_PROCESS_STAT_ITREALVALUE, + VIR_PROCESS_STAT_STARTTIME, + VIR_PROCESS_STAT_VSIZE, + VIR_PROCESS_STAT_RSS, + VIR_PROCESS_STAT_RSSLIM, + VIR_PROCESS_STAT_STARTCODE, + VIR_PROCESS_STAT_ENDCODE, + VIR_PROCESS_STAT_STARTSTACK, + VIR_PROCESS_STAT_KSTKESP, + VIR_PROCESS_STAT_KSTKEIP, + VIR_PROCESS_STAT_SIGNAL, + VIR_PROCESS_STAT_BLOCKED, + VIR_PROCESS_STAT_SIGIGNORE, + VIR_PROCESS_STAT_SIGCATCH, + VIR_PROCESS_STAT_WCHAN, + VIR_PROCESS_STAT_NSWAP, + VIR_PROCESS_STAT_CNSWAP, + VIR_PROCESS_STAT_EXIT_SIGNAL, + VIR_PROCESS_STAT_PROCESSOR, + VIR_PROCESS_STAT_RT_PRIORITY, + VIR_PROCESS_STAT_POLICY, + VIR_PROCESS_STAT_DELAYACCT_BLKIO_TICKS, + VIR_PROCESS_STAT_GUEST_TIME, + VIR_PROCESS_STAT_CGUEST_TIME, + VIR_PROCESS_STAT_START_DATA, + VIR_PROCESS_STAT_END_DATA, + VIR_PROCESS_STAT_START_BRK, + VIR_PROCESS_STAT_ARG_START, + VIR_PROCESS_STAT_ARG_END, + VIR_PROCESS_STAT_ENV_START, + VIR_PROCESS_STAT_ENV_END, + VIR_PROCESS_STAT_EXIT_CODE, +}; + +/* + * At the time of writing there are 52 values reported in /proc/.../stat, the + * line below checks that the last one has the right value, increase accordingly + * based on proc(5) whenever adding new fields. +*/ +G_STATIC_ASSERT(VIR_PROCESS_STAT_EXIT_CODE == 51); + typedef enum { VIR_PROCESS_NAMESPACE_MNT = (1 << 1), VIR_PROCESS_NAMESPACE_IPC = (1 << 2), diff --git a/tests/meson.build b/tests/meson.build index 1948c07ae3..f75c248720 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -347,6 +347,7 @@ if host_machine.system() == 'linux' { 'name': 'scsihosttest' }, { 'name': 'vircaps2xmltest', 'link_whole': [ test_file_wrapper_lib ] }, { 'name': 'virnetdevbandwidthtest' }, + { 'name': 'virprocessstattest', 'link_whole': [ test_file_wrapper_lib ] }, { 'name': 'virresctrltest', 'link_whole': [ test_file_wrapper_lib ] }, { 'name': 'virscsitest' }, { 'name': 'virusbtest' }, diff --git a/tests/virprocessstatdata/complex/stat b/tests/virprocessstatdata/complex/stat new file mode 100644 index 0000000000..df0ecc149c --- /dev/null +++ b/tests/virprocessstatdata/complex/stat @@ -0,0 +1,2 @@ +1 (this) is ( a weird ) +)( (command ( ) 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 diff --git a/tests/virprocessstatdata/simple/stat b/tests/virprocessstatdata/simple/stat new file mode 100644 index 0000000000..97d6873537 --- /dev/null +++ b/tests/virprocessstatdata/simple/stat @@ -0,0 +1 @@ +1 (command) 3 4 5 diff --git a/tests/virprocessstattest.c b/tests/virprocessstattest.c new file mode 100644 index 0000000000..8fb82c48ef --- /dev/null +++ b/tests/virprocessstattest.c @@ -0,0 +1,88 @@ +#include + +#include "testutils.h" +#include "virfilewrapper.h" +#include "virprocess.h" + + +struct testData { + const char *filename; + const char *command; + size_t count; + bool self; +}; + + +static int +test_virProcessGetStat(const void *opaque) +{ + struct testData *data = (struct testData *) opaque; + g_autofree char *data_dir = NULL; + g_auto(GStrv) proc_stat = NULL; + size_t len = 0; + id_t id = data->self ? 0 : -1; + const char *command = NULL; + + data_dir = g_strdup_printf("%s/virprocessstatdata/%s/", + abs_srcdir, data->filename); + + /* We are using predictable id of -1 because this case we will clearly see + * that the test failed in case of virFileWrapper failure */ + if (id) + virFileWrapperAddPrefix("/proc/-1/task/-1/", data_dir); + else + virFileWrapperAddPrefix("/proc/self/", data_dir); + + proc_stat = virProcessGetStat(id, id); + + virFileWrapperClearPrefixes(); + + if (!proc_stat) { + fprintf(stderr, "Could not get process stats\n"); + return -1; + } + + len = g_strv_length(proc_stat); + if (data->count != len) { + fprintf(stderr, "Count incorrect, expected %zu, got %zu\n", + data->count, len); + return -1; + } + + command = proc_stat[VIR_PROCESS_STAT_COMM]; + if (!STREQ_NULLABLE(data->command, command)) { + fprintf(stderr, "Command incorrect, expected %s, got %s\n", + data->command, command); + return -1; + } + + return 0; +} + + +static int +mymain(void) +{ + struct testData data = {0}; + int ret = 0; + +#define DO_TEST(_filename, _command, _count, _self) \ + do { \ + data = (struct testData){ \ + .filename = _filename, \ + .command = _command, \ + .count = _count, \ + .self = _self, \ + }; \ + if (virTestRun("Reading process stat: " _filename, \ + test_virProcessGetStat, &data) < 0) \ + ret = -1; \ + } while (0) + + DO_TEST("simple", "command", 5, true); + DO_TEST("complex", "this) is ( a \t weird )\n)( (command ( ", 100, false); + + return ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE; +} + +VIR_TEST_MAIN(mymain)