1// SPDX-License-Identifier: GPL-2.0
2#include <linux/stringify.h>
3#include <sys/types.h>
4#include <sys/stat.h>
5#include <fcntl.h>
6#include <stdio.h>
7#include <stdlib.h>
8#include <string.h>
9#include "fs.h"
10
11struct cgroupfs_cache_entry {
12	char	subsys[32];
13	char	mountpoint[PATH_MAX];
14};
15
16/* just cache last used one */
17static struct cgroupfs_cache_entry *cached;
18
19int cgroupfs_find_mountpoint(char *buf, size_t maxlen, const char *subsys)
20{
21	FILE *fp;
22	char *line = NULL;
23	size_t len = 0;
24	char *p, *path;
25	char mountpoint[PATH_MAX];
26
27	if (cached && !strcmp(cached->subsys, subsys)) {
28		if (strlen(cached->mountpoint) < maxlen) {
29			strcpy(buf, cached->mountpoint);
30			return 0;
31		}
32		return -1;
33	}
34
35	fp = fopen("/proc/mounts", "r");
36	if (!fp)
37		return -1;
38
39	/*
40	 * in order to handle split hierarchy, we need to scan /proc/mounts
41	 * and inspect every cgroupfs mount point to find one that has
42	 * the given subsystem.  If we found v1, just use it.  If not we can
43	 * use v2 path as a fallback.
44	 */
45	mountpoint[0] = '\0';
46
47	/*
48	 * The /proc/mounts has the follow format:
49	 *
50	 *   <devname> <mount point> <fs type> <options> ...
51	 *
52	 */
53	while (getline(&line, &len, fp) != -1) {
54		/* skip devname */
55		p = strchr(line, ' ');
56		if (p == NULL)
57			continue;
58
59		/* save the mount point */
60		path = ++p;
61		p = strchr(p, ' ');
62		if (p == NULL)
63			continue;
64
65		*p++ = '\0';
66
67		/* check filesystem type */
68		if (strncmp(p, "cgroup", 6))
69			continue;
70
71		if (p[6] == '2') {
72			/* save cgroup v2 path */
73			strcpy(mountpoint, path);
74			continue;
75		}
76
77		/* now we have cgroup v1, check the options for subsystem */
78		p += 7;
79
80		p = strstr(p, subsys);
81		if (p == NULL)
82			continue;
83
84		/* sanity check: it should be separated by a space or a comma */
85		if (!strchr(" ,", p[-1]) || !strchr(" ,", p[strlen(subsys)]))
86			continue;
87
88		strcpy(mountpoint, path);
89		break;
90	}
91	free(line);
92	fclose(fp);
93
94	if (!cached)
95		cached = calloc(1, sizeof(*cached));
96
97	if (cached) {
98		strncpy(cached->subsys, subsys, sizeof(cached->subsys) - 1);
99		strcpy(cached->mountpoint, mountpoint);
100	}
101
102	if (mountpoint[0] && strlen(mountpoint) < maxlen) {
103		strcpy(buf, mountpoint);
104		return 0;
105	}
106	return -1;
107}
108