From 4cfb0fb763d4bf8d9c2fa4564689e527ca636988 Mon Sep 17 00:00:00 2001 From: Craig Small Date: Tue, 26 Oct 2021 20:56:19 +1100 Subject: [PATCH] pgrep: Match on cgroup v2 paths You can match or filter on cgroup paths. Currently the match is only done for version 2 cgroups because these are way simpler as they have a unified name and always start with "0::". cgroup v1 can have: named groups "1:name=myspecialname:" controllers "9:blkio:" multiple controllers! "4:cpu,cpuacct:" So they are very much more complicated from a options parsing and cgroup matching point of view. In addition, both my Debian bookworm and bullseye systems use v2 cgroups. $ ./pgrep --cgroup /system.slice/cron.service 760 Signed-off-by: Craig Small --- NEWS | 1 + pgrep.1 | 9 +++++++-- pgrep.c | 51 ++++++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 56 insertions(+), 5 deletions(-) diff --git a/NEWS b/NEWS index f4d3186b..987c5d10 100644 --- a/NEWS +++ b/NEWS @@ -9,6 +9,7 @@ procps-ng-NEXT * pkill: Check for lt- variants of program name issue #192 * pgrep: Add newline after regex error message merge #91 * pgrep: Fix selection where uid/gid > 2^31 merge !146 + * pgrep: Select on cgroup v2 paths issue #168 * ps: Add OOM and OOMADJ fields issue #198 * ps: Add IO Accounting fields issue #184 * ps: Add PSS and USS fields issue #112 diff --git a/pgrep.1 b/pgrep.1 index 301c895b..0d836844 100644 --- a/pgrep.1 +++ b/pgrep.1 @@ -7,7 +7,7 @@ .\" the Free Software Foundation; either version 2 of the License, or .\" (at your option) any later version. .\" -.TH PGREP "1" "2020-06-04" "procps-ng" "User Commands" +.TH PGREP "1" "2021-10-26" "procps-ng" "User Commands" .SH NAME pgrep, pkill, pidwait \- look up, signal, or wait for processes based on name and other attributes .SH SYNOPSIS @@ -177,6 +177,10 @@ Fail if pidfile (see \fB\-F\fR) not locked. \fB\-r\fR, \fB\-\-runstates\fR \fID,R,S,Z,\fP... Match only processes which match the process state. .TP +\fB\-\-cgroup \fIname\fP,... +Match on provided control group (cgroup) v2 name. See +.BR cgroups (8) +.TP \fB\-\-ns \fIpid\fP Match processes that belong to the same namespaces. Required to run as root to match processes from other users. See \fB\-\-nslist\fR for how to @@ -282,7 +286,8 @@ Defunct processes are reported. .BR killall (1), .BR skill (1), .BR kill (1), -.BR kill (2) +.BR kill (2), +.BR cgroups (8) .SH AUTHOR .UR kjetilho@ifi.uio.no Kjetil Torgrim Homme diff --git a/pgrep.c b/pgrep.c index c5cd6f1d..6457fd40 100644 --- a/pgrep.c +++ b/pgrep.c @@ -72,11 +72,13 @@ enum pids_item Items[] = { PIDS_CMD, PIDS_CMDLINE, PIDS_STATE, - PIDS_TIME_ELAPSED + PIDS_TIME_ELAPSED, + PIDS_CGROUP_V }; enum rel_items { EU_PID, EU_PPID, EU_PGRP, EU_EUID, EU_RUID, EU_RGID, EU_SESSION, - EU_TGID, EU_STARTTIME, EU_TTYNAME, EU_CMD, EU_CMDLINE, EU_STA, EU_ELAPSED + EU_TGID, EU_STARTTIME, EU_TTYNAME, EU_CMD, EU_CMDLINE, EU_STA, EU_ELAPSED, + EU_CGROUP }; #define grow_size(x) do { \ if ((x) < 0 || (size_t)(x) >= INT_MAX / 5 / sizeof(struct el)) \ @@ -127,6 +129,7 @@ static struct el *opt_term = NULL; static struct el *opt_euid = NULL; static struct el *opt_ruid = NULL; static struct el *opt_nslist = NULL; +static struct el *opt_cgroup = NULL; static char *opt_pattern = NULL; static char *opt_pidfile = NULL; static char *opt_runstates = NULL; @@ -177,6 +180,7 @@ static int __attribute__ ((__noreturn__)) usage(int opt) fputs(_(" -F, --pidfile read PIDs from file\n"), fp); fputs(_(" -L, --logpidfile fail if PID file is not locked\n"), fp); fputs(_(" -r, --runstates match runstates [D,S,Z,...]\n"), fp); + fputs(_(" --cgroup match by cgroup v2 names\n"), fp); fputs(_(" --ns match the processes that belong to the same\n" " namespace as \n"), fp); fputs(_(" --nslist list which namespaces will be considered for\n" @@ -455,6 +459,35 @@ static int match_ns (const int pid, return found; } +static int cgroup_cmp(const char *restrict cgroup, + const char *restrict path) +{ + if (cgroup == NULL || path == NULL) + return 1; + // Cgroup v2 have 0:: + if (strncmp("0::", cgroup, 3) == 0) { + return strcmp(cgroup+3, path); + } //might try for cgroup v1 later + return 1; +} + + +static int match_cgroup_list(char **values, + const struct el *restrict list) +{ + if (list != NULL && values != NULL) { + int i, j; + for (i = list[0].num; i > 0; i--) { + for (j=0; values[j] && values[j][0]; j++) { + if (! cgroup_cmp (values[j], list[i].str)) { + return 1; + } + } + } + } + return 0; +} + static void output_numlist (const struct el *restrict list, int num) { int i; @@ -535,6 +568,7 @@ static struct el * select_procs (int *num) #define PIDS_GETULL(e) PIDS_VAL(EU_ ## e, ull_int, stack, info) #define PIDS_GETSTR(e) PIDS_VAL(EU_ ## e, str, stack, info) #define PIDS_GETSCH(e) PIDS_VAL(EU_ ## e, s_ch, stack, info) +#define PIDS_GETSTV(e) PIDS_VAL(EU_ ## e, strv, stack, info) struct pids_info *info=NULL; struct procps_ns nsp; struct pids_stack *stack; @@ -568,7 +602,7 @@ static struct el * select_procs (int *num) _("Error reading reference namespace information\n")); } - if (procps_pids_new(&info, Items, 14) < 0) + if (procps_pids_new(&info, Items, 15) < 0) xerrx(EXIT_FATAL, _("Unable to create pid info structure")); which = PIDS_FETCH_TASKS_ONLY; @@ -607,6 +641,8 @@ static struct el * select_procs (int *num) match = match_strlist(PIDS_GETSTR(TTYNAME), opt_term); else if (opt_runstates && ! strchr(opt_runstates, PIDS_GETSCH(STA))) match = 0; + else if (opt_cgroup && ! match_cgroup_list (PIDS_GETSTV(CGROUP), opt_cgroup)) + match = 0; task_cmdline = PIDS_GETSTR(CMDLINE); @@ -681,6 +717,7 @@ static struct el * select_procs (int *num) #undef PIDS_GETUNT #undef PIDS_GETULL #undef PIDS_GETSTR +#undef PIDS_GETSTV } static int signal_option(int *argc, char **argv) @@ -718,10 +755,12 @@ static void parse_opts (int argc, char **argv) SIGNAL_OPTION = CHAR_MAX + 1, NS_OPTION, NSLIST_OPTION, + CGROUP_OPTION, }; static const struct option longopts[] = { {"signal", required_argument, NULL, SIGNAL_OPTION}, {"count", no_argument, NULL, 'c'}, + {"cgroup", required_argument, NULL, CGROUP_OPTION}, {"delimiter", required_argument, NULL, 'd'}, {"list-name", no_argument, NULL, 'l'}, {"list-full", no_argument, NULL, 'a'}, @@ -920,6 +959,12 @@ static void parse_opts (int argc, char **argv) sigval.sival_int = atoi(optarg); use_sigqueue = true; break; + case CGROUP_OPTION: + opt_cgroup = split_list (optarg, conv_str); + if (opt_cgroup == NULL) + usage ('?'); + ++criteria_count; + break; case 'h': case '?': usage (opt);