Skip to content

Commit 21aa746

Browse files
committed
libbpf-tools: Add cgroup_helpers to get cgroup info
Some tools need to obtain cgroup information. For example, oomkill currently only supports tracking process information and cannot obtain cgroup information. It would be better if it could obtain memcg information. For ease of maintenance, a separate commit is kept just for adding cgroup_helpers. Added interfaces: cgroup_get_roots() - Get the directories where the cgroup is mounted cgroup_free_roots() - Free directories cgroup_cgroupid_of_path() - Get cgroupid from cgroup absolute path cgroup_cgroupid_of_mnt_path() - Get cgroupid from cgroup mount point and relative path cgroup_cgroup_path() - Get cgroup path from cgroupid cgroup_proc_for_each_cgroup_entry() - Traverse all cgroups of a process Signed-off-by: Rong Tao <[email protected]>
1 parent f55a2f6 commit 21aa746

File tree

3 files changed

+366
-0
lines changed

3 files changed

+366
-0
lines changed

libbpf-tools/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ SIGSNOOP_ALIAS = killsnoop
106106
APP_ALIASES = $(FSDIST_ALIASES) $(FSSLOWER_ALIASES) ${SIGSNOOP_ALIAS}
107107

108108
COMMON_OBJ = \
109+
$(OUTPUT)/cgroup_helpers.o \
109110
$(OUTPUT)/trace_helpers.o \
110111
$(OUTPUT)/syscall_helpers.o \
111112
$(OUTPUT)/errno_helpers.o \

libbpf-tools/cgroup_helpers.c

Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
1+
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
2+
/* Copyright (c) 2025 Rong Tao */
3+
#include <assert.h>
4+
#include <dirent.h>
5+
#include <errno.h>
6+
#include <stdbool.h>
7+
#include <stdio.h>
8+
#include <stdlib.h>
9+
#include <sys/stat.h>
10+
#include <string.h>
11+
#include "cgroup_helpers.h"
12+
13+
14+
int cgroup_get_roots(char ***roots)
15+
{
16+
char line[2048];
17+
char fsname[128];
18+
char mountpoint[PATH_MAX];
19+
char fstype[64];
20+
char mntoptions[256];
21+
int dump_frequency;
22+
int fsck_order;
23+
FILE *fp;
24+
int n = 0;
25+
26+
fp = fopen("/proc/mounts", "r");
27+
if (!fp)
28+
return -errno;
29+
30+
*roots = NULL;
31+
32+
while (fgets(line, sizeof(line), fp)) {
33+
if (sscanf(line, "%s %s %s %s %d %d\n", fsname, mountpoint,
34+
fstype, mntoptions, &dump_frequency,
35+
&fsck_order) != 6)
36+
continue;
37+
38+
/* Only need cgroup or cgroup2 */
39+
if (strcmp(fstype, "cgroup") && strcmp(fstype, "cgroup2"))
40+
continue;
41+
42+
n++;
43+
*roots = (char **)realloc(*roots, n * sizeof(char *));
44+
(*roots)[n - 1] = strdup(mountpoint);
45+
}
46+
47+
fclose(fp);
48+
49+
return n;
50+
}
51+
52+
int cgroup_free_roots(char **roots, int nentries)
53+
{
54+
int i;
55+
56+
if (!roots || nentries <= 0)
57+
return -EINVAL;
58+
59+
for (i = 0; i < nentries; i++)
60+
free(roots[i]);
61+
free(roots);
62+
63+
return 0;
64+
}
65+
66+
long cgroup_cgroupid_of_path(const char *cgroup_path)
67+
{
68+
int err;
69+
struct stat st;
70+
/* The inode of the cgroup folder is the groupid */
71+
err = stat(cgroup_path, &st);
72+
return err ? -errno : st.st_ino;
73+
}
74+
75+
long cgroup_cgroupid_of_mnt_path(const char *mntpoint, const char *cgroup_path)
76+
{
77+
char path[PATH_MAX];
78+
snprintf(path, sizeof(path) - 1, "%s/%s", mntpoint, cgroup_path);
79+
return cgroup_cgroupid_of_path(path);
80+
}
81+
82+
typedef int (*match_fn)(const char *path, void *arg);
83+
84+
/**
85+
* Recursively search for a matching cgroup in a root directory.
86+
*
87+
* When @match returns true, the match succeeds and the function returns
88+
* without further searching.
89+
*
90+
* If the match is successful, 1 is returned. If the match fails, 0 is returned.
91+
* If an error occurs during the search process, -errno is returned.
92+
*/
93+
static int find_cgroup_from_root_recur(const char *root, match_fn match,
94+
void *arg)
95+
{
96+
int err = 0;
97+
DIR *dir;
98+
struct dirent *dirent;
99+
char *path;
100+
struct stat st;
101+
size_t path_len;
102+
103+
if (!root || !match)
104+
return -EINVAL;
105+
106+
err = lstat(root, &st);
107+
if (err)
108+
return -errno;
109+
if (!S_ISDIR(st.st_mode))
110+
return -ENOTDIR;
111+
112+
path = malloc(PATH_MAX);
113+
assert(path && "Malloc failed");
114+
115+
snprintf(path, PATH_MAX - 1, "%s/", root);
116+
117+
err = lstat(path, &st);
118+
if (err)
119+
return -errno;
120+
if (!S_ISDIR(st.st_mode)) {
121+
free(path);
122+
return -ENOENT;
123+
}
124+
125+
dir = opendir(path);
126+
if (!dir) {
127+
err = -errno;
128+
goto done;
129+
}
130+
131+
path_len = strlen(path);
132+
133+
/**
134+
* If the directory path doesn't end with a slash, append a slash,
135+
* convenient for splicing subdirectories.
136+
*/
137+
if (path[path_len - 1] != '/') {
138+
path[path_len] = '/';
139+
path[++path_len] = '\0';
140+
}
141+
142+
/**
143+
* Traverse all folders under the root directory, skipping the current
144+
* directory and the previous directory.
145+
*/
146+
while ((dirent = readdir(dir)) != NULL) {
147+
if (!strcmp(dirent->d_name, ".") || !strcmp(dirent->d_name, ".."))
148+
continue;
149+
strncpy(path + path_len, dirent->d_name, PATH_MAX - path_len);
150+
err = lstat(path, &st);
151+
if (err)
152+
continue;
153+
if (!S_ISDIR(st.st_mode))
154+
continue;
155+
#ifdef DEBUG
156+
fprintf(stderr, "%s\n", path);
157+
#endif
158+
if (match(path, arg)) {
159+
/* Found */
160+
err = 1;
161+
goto done;
162+
}
163+
164+
/**
165+
* Recursive search. Returning 1 means it was found, return
166+
* -errno means an error occurred, and returning 0 means it
167+
* was not found and should continue searching.
168+
*/
169+
err = find_cgroup_from_root_recur(path, match, arg);
170+
if (err)
171+
goto done;
172+
}
173+
174+
/* Not found */
175+
err = 0;
176+
done:
177+
closedir(dir);
178+
free(path);
179+
return err;
180+
}
181+
182+
struct match_cgroupid_arg {
183+
long cgroupid;
184+
char path[PATH_MAX];
185+
};
186+
187+
/**
188+
* As the @match parameter of the find_cgroup_from_root_recur() function,
189+
* the cgroup path is found by cgroupid.
190+
*/
191+
static int match_cgroupid(const char *path, void *arg)
192+
{
193+
long cgroupid;
194+
struct match_cgroupid_arg *a = arg;
195+
196+
cgroupid = cgroup_cgroupid_of_path(path);
197+
#ifdef DEBUG
198+
fprintf(stderr, "%ld:%ld %s\n", cgroupid, a->cgroupid, path);
199+
#endif
200+
if (cgroupid == a->cgroupid) {
201+
snprintf(a->path, PATH_MAX, path);
202+
return 1;
203+
}
204+
return 0;
205+
}
206+
207+
int cgroup_cgroup_path(long cgroupid, char *buf, size_t buf_len)
208+
{
209+
char **roots;
210+
int nroots, i, err;
211+
struct match_cgroupid_arg arg = {};
212+
bool found = false;
213+
214+
arg.cgroupid = cgroupid;
215+
216+
nroots = cgroup_get_roots(&roots);
217+
218+
for (i = 0; i < nroots; i++) {
219+
#ifdef DEBUG
220+
fprintf(stderr, "root --- %s\n", roots[i]);
221+
#endif
222+
err = find_cgroup_from_root_recur(roots[i], match_cgroupid, &arg);
223+
if (err == 1) {
224+
strncpy(buf, arg.path, buf_len);
225+
found = true;
226+
break;
227+
}
228+
}
229+
230+
cgroup_free_roots(roots, nroots);
231+
232+
return found ? 0 : -ENOENT;
233+
}
234+
235+
int cgroup_proc_for_each_cgroup_entry(pid_t pid, cgroup_proc_entry_fn callback,
236+
void *arg)
237+
{
238+
char line[512];
239+
char proc[64];
240+
FILE *f;
241+
int lines = 0;
242+
struct cgroup_proc_entry cgrp;
243+
244+
if (!callback)
245+
return -EINVAL;
246+
247+
snprintf(proc, sizeof(proc) - 1, "/proc/%d/cgroup", pid);
248+
249+
f = fopen(proc, "r");
250+
if (!f)
251+
return -errno;
252+
253+
/**
254+
* Parse each line, format:
255+
*
256+
* <hierarchy_id>:<subsystem_list>:<cgroup_path>
257+
*
258+
* For cgroupv2, subsystem_list is empty.
259+
*/
260+
while (fgets(line, sizeof(line), f)) {
261+
char *s_hid = line;
262+
char *s_subsys = strchr(line, ':');
263+
char *s_path = strrchr(line, ':');
264+
265+
s_subsys[0] = '\0';
266+
s_subsys++;
267+
s_path[0] = '\0';
268+
s_path++;
269+
270+
memset(&cgrp, 0, sizeof(cgrp));
271+
272+
cgrp.hierarchy_id = atoi(s_hid);
273+
274+
if (strlen(s_subsys) == 0) {
275+
cgrp.cgroup_type = 2; /* cgroupv2 */
276+
} else {
277+
cgrp.cgroup_type = 1; /* cgroupv1 */
278+
snprintf(cgrp.subsystem_list, sizeof(cgrp.subsystem_list),
279+
s_subsys);
280+
}
281+
282+
/* Use strlen() to strip '\n' */
283+
snprintf(cgrp.cgroup_path, strlen(s_path), s_path);
284+
285+
lines++;
286+
callback(&cgrp, arg);
287+
}
288+
289+
fclose(f);
290+
return lines;
291+
}

libbpf-tools/cgroup_helpers.h

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
2+
/* Copyright (c) 2025 Rong Tao */
3+
#pragma once
4+
#include <limits.h>
5+
#include <unistd.h>
6+
#include <sys/types.h>
7+
8+
#define CGROUP_DEFAULT_MNTPOINT "/sys/fs/cgroup/"
9+
10+
/**
11+
* Get cgroup mountpoints.
12+
*
13+
* @roots need to be free with cgroup_free_roots(), access with roots[idx].
14+
*
15+
* On success, the number of root path returned, need pass to cgroup_free_roots()
16+
* nentries parameter. On error, -errno returned.
17+
*/
18+
int cgroup_get_roots(char ***roots);
19+
20+
/**
21+
* Used to release roots allocated by cgroup_get_roots().
22+
*
23+
* On success, zero returned. On error, -errno returned.
24+
*/
25+
int cgroup_free_roots(char **roots, int nentries);
26+
27+
/**
28+
* Get cgroup id from cgroup path.
29+
*
30+
* On success, the cgroupid returned. On error, -errno returned.
31+
*/
32+
long cgroup_cgroupid_of_path(const char *cgroup_path);
33+
long cgroup_cgroupid_of_mnt_path(const char *mntpoint, const char *cgroup_path);
34+
35+
/**
36+
* Get cgroup path from cgroupid.
37+
*
38+
* On success, zero returned. On error, -errno returned.
39+
*/
40+
int cgroup_cgroup_path(long cgroupid, char *buf, size_t buf_len);
41+
42+
/**
43+
* This structure is used to describe a line of info in /proc/<pid>/cgroup.
44+
* For example, the following two entries show cgroupv1 and cgroupv2
45+
* respectively.
46+
*
47+
* 1:name=systemd:/user.slice/user-1000.slice/session-1.scope
48+
* 0::/user.slice/user-1000.slice/session-1.scope
49+
*/
50+
struct cgroup_proc_entry {
51+
/**
52+
* 1: cgroup v1
53+
* 2: cgroup v2
54+
*/
55+
int cgroup_type;
56+
/**
57+
* The following variable stores a line of information from a
58+
* /proc/<pid>/cgroup file.
59+
*/
60+
int hierarchy_id;
61+
char subsystem_list[256];
62+
char cgroup_path[PATH_MAX];
63+
};
64+
65+
typedef void (*cgroup_proc_entry_fn)(const struct cgroup_proc_entry *cgrp, void *arg);
66+
67+
/**
68+
* This function traverses all cgroup information of a process, and each cgroup
69+
* entry will call the callback function.
70+
*
71+
* On success, the number of entry returned. On error, -errno returned.
72+
*/
73+
int cgroup_proc_for_each_cgroup_entry(pid_t pid, cgroup_proc_entry_fn callback,
74+
void *arg);

0 commit comments

Comments
 (0)