18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci#define _GNU_SOURCE
38c2ecf20Sopenharmony_ci#include <sched.h>
48c2ecf20Sopenharmony_ci#include <sys/mount.h>
58c2ecf20Sopenharmony_ci#include <sys/stat.h>
68c2ecf20Sopenharmony_ci#include <sys/types.h>
78c2ecf20Sopenharmony_ci#include <linux/limits.h>
88c2ecf20Sopenharmony_ci#include <stdio.h>
98c2ecf20Sopenharmony_ci#include <stdlib.h>
108c2ecf20Sopenharmony_ci#include <linux/sched.h>
118c2ecf20Sopenharmony_ci#include <fcntl.h>
128c2ecf20Sopenharmony_ci#include <unistd.h>
138c2ecf20Sopenharmony_ci#include <ftw.h>
148c2ecf20Sopenharmony_ci
158c2ecf20Sopenharmony_ci
168c2ecf20Sopenharmony_ci#include "cgroup_helpers.h"
178c2ecf20Sopenharmony_ci
188c2ecf20Sopenharmony_ci/*
198c2ecf20Sopenharmony_ci * To avoid relying on the system setup, when setup_cgroup_env is called
208c2ecf20Sopenharmony_ci * we create a new mount namespace, and cgroup namespace. The cgroup2
218c2ecf20Sopenharmony_ci * root is mounted at CGROUP_MOUNT_PATH
228c2ecf20Sopenharmony_ci *
238c2ecf20Sopenharmony_ci * Unfortunately, most people don't have cgroupv2 enabled at this point in time.
248c2ecf20Sopenharmony_ci * It's easier to create our own mount namespace and manage it ourselves.
258c2ecf20Sopenharmony_ci *
268c2ecf20Sopenharmony_ci * We assume /mnt exists.
278c2ecf20Sopenharmony_ci */
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_ci#define WALK_FD_LIMIT			16
308c2ecf20Sopenharmony_ci#define CGROUP_MOUNT_PATH		"/mnt"
318c2ecf20Sopenharmony_ci#define CGROUP_WORK_DIR			"/cgroup-test-work-dir"
328c2ecf20Sopenharmony_ci#define format_cgroup_path(buf, path) \
338c2ecf20Sopenharmony_ci	snprintf(buf, sizeof(buf), "%s%s%s", CGROUP_MOUNT_PATH, \
348c2ecf20Sopenharmony_ci		 CGROUP_WORK_DIR, path)
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_ci/**
378c2ecf20Sopenharmony_ci * enable_all_controllers() - Enable all available cgroup v2 controllers
388c2ecf20Sopenharmony_ci *
398c2ecf20Sopenharmony_ci * Enable all available cgroup v2 controllers in order to increase
408c2ecf20Sopenharmony_ci * the code coverage.
418c2ecf20Sopenharmony_ci *
428c2ecf20Sopenharmony_ci * If successful, 0 is returned.
438c2ecf20Sopenharmony_ci */
448c2ecf20Sopenharmony_cistatic int enable_all_controllers(char *cgroup_path)
458c2ecf20Sopenharmony_ci{
468c2ecf20Sopenharmony_ci	char path[PATH_MAX + 1];
478c2ecf20Sopenharmony_ci	char buf[PATH_MAX];
488c2ecf20Sopenharmony_ci	char *c, *c2;
498c2ecf20Sopenharmony_ci	int fd, cfd;
508c2ecf20Sopenharmony_ci	ssize_t len;
518c2ecf20Sopenharmony_ci
528c2ecf20Sopenharmony_ci	snprintf(path, sizeof(path), "%s/cgroup.controllers", cgroup_path);
538c2ecf20Sopenharmony_ci	fd = open(path, O_RDONLY);
548c2ecf20Sopenharmony_ci	if (fd < 0) {
558c2ecf20Sopenharmony_ci		log_err("Opening cgroup.controllers: %s", path);
568c2ecf20Sopenharmony_ci		return 1;
578c2ecf20Sopenharmony_ci	}
588c2ecf20Sopenharmony_ci
598c2ecf20Sopenharmony_ci	len = read(fd, buf, sizeof(buf) - 1);
608c2ecf20Sopenharmony_ci	if (len < 0) {
618c2ecf20Sopenharmony_ci		close(fd);
628c2ecf20Sopenharmony_ci		log_err("Reading cgroup.controllers: %s", path);
638c2ecf20Sopenharmony_ci		return 1;
648c2ecf20Sopenharmony_ci	}
658c2ecf20Sopenharmony_ci	buf[len] = 0;
668c2ecf20Sopenharmony_ci	close(fd);
678c2ecf20Sopenharmony_ci
688c2ecf20Sopenharmony_ci	/* No controllers available? We're probably on cgroup v1. */
698c2ecf20Sopenharmony_ci	if (len == 0)
708c2ecf20Sopenharmony_ci		return 0;
718c2ecf20Sopenharmony_ci
728c2ecf20Sopenharmony_ci	snprintf(path, sizeof(path), "%s/cgroup.subtree_control", cgroup_path);
738c2ecf20Sopenharmony_ci	cfd = open(path, O_RDWR);
748c2ecf20Sopenharmony_ci	if (cfd < 0) {
758c2ecf20Sopenharmony_ci		log_err("Opening cgroup.subtree_control: %s", path);
768c2ecf20Sopenharmony_ci		return 1;
778c2ecf20Sopenharmony_ci	}
788c2ecf20Sopenharmony_ci
798c2ecf20Sopenharmony_ci	for (c = strtok_r(buf, " ", &c2); c; c = strtok_r(NULL, " ", &c2)) {
808c2ecf20Sopenharmony_ci		if (dprintf(cfd, "+%s\n", c) <= 0) {
818c2ecf20Sopenharmony_ci			log_err("Enabling controller %s: %s", c, path);
828c2ecf20Sopenharmony_ci			close(cfd);
838c2ecf20Sopenharmony_ci			return 1;
848c2ecf20Sopenharmony_ci		}
858c2ecf20Sopenharmony_ci	}
868c2ecf20Sopenharmony_ci	close(cfd);
878c2ecf20Sopenharmony_ci	return 0;
888c2ecf20Sopenharmony_ci}
898c2ecf20Sopenharmony_ci
908c2ecf20Sopenharmony_ci/**
918c2ecf20Sopenharmony_ci * setup_cgroup_environment() - Setup the cgroup environment
928c2ecf20Sopenharmony_ci *
938c2ecf20Sopenharmony_ci * After calling this function, cleanup_cgroup_environment should be called
948c2ecf20Sopenharmony_ci * once testing is complete.
958c2ecf20Sopenharmony_ci *
968c2ecf20Sopenharmony_ci * This function will print an error to stderr and return 1 if it is unable
978c2ecf20Sopenharmony_ci * to setup the cgroup environment. If setup is successful, 0 is returned.
988c2ecf20Sopenharmony_ci */
998c2ecf20Sopenharmony_ciint setup_cgroup_environment(void)
1008c2ecf20Sopenharmony_ci{
1018c2ecf20Sopenharmony_ci	char cgroup_workdir[PATH_MAX - 24];
1028c2ecf20Sopenharmony_ci
1038c2ecf20Sopenharmony_ci	format_cgroup_path(cgroup_workdir, "");
1048c2ecf20Sopenharmony_ci
1058c2ecf20Sopenharmony_ci	if (unshare(CLONE_NEWNS)) {
1068c2ecf20Sopenharmony_ci		log_err("unshare");
1078c2ecf20Sopenharmony_ci		return 1;
1088c2ecf20Sopenharmony_ci	}
1098c2ecf20Sopenharmony_ci
1108c2ecf20Sopenharmony_ci	if (mount("none", "/", NULL, MS_REC | MS_PRIVATE, NULL)) {
1118c2ecf20Sopenharmony_ci		log_err("mount fakeroot");
1128c2ecf20Sopenharmony_ci		return 1;
1138c2ecf20Sopenharmony_ci	}
1148c2ecf20Sopenharmony_ci
1158c2ecf20Sopenharmony_ci	if (mount("none", CGROUP_MOUNT_PATH, "cgroup2", 0, NULL) && errno != EBUSY) {
1168c2ecf20Sopenharmony_ci		log_err("mount cgroup2");
1178c2ecf20Sopenharmony_ci		return 1;
1188c2ecf20Sopenharmony_ci	}
1198c2ecf20Sopenharmony_ci
1208c2ecf20Sopenharmony_ci	/* Cleanup existing failed runs, now that the environment is setup */
1218c2ecf20Sopenharmony_ci	cleanup_cgroup_environment();
1228c2ecf20Sopenharmony_ci
1238c2ecf20Sopenharmony_ci	if (mkdir(cgroup_workdir, 0777) && errno != EEXIST) {
1248c2ecf20Sopenharmony_ci		log_err("mkdir cgroup work dir");
1258c2ecf20Sopenharmony_ci		return 1;
1268c2ecf20Sopenharmony_ci	}
1278c2ecf20Sopenharmony_ci
1288c2ecf20Sopenharmony_ci	if (enable_all_controllers(cgroup_workdir))
1298c2ecf20Sopenharmony_ci		return 1;
1308c2ecf20Sopenharmony_ci
1318c2ecf20Sopenharmony_ci	return 0;
1328c2ecf20Sopenharmony_ci}
1338c2ecf20Sopenharmony_ci
1348c2ecf20Sopenharmony_cistatic int nftwfunc(const char *filename, const struct stat *statptr,
1358c2ecf20Sopenharmony_ci		    int fileflags, struct FTW *pfwt)
1368c2ecf20Sopenharmony_ci{
1378c2ecf20Sopenharmony_ci	if ((fileflags & FTW_D) && rmdir(filename))
1388c2ecf20Sopenharmony_ci		log_err("Removing cgroup: %s", filename);
1398c2ecf20Sopenharmony_ci	return 0;
1408c2ecf20Sopenharmony_ci}
1418c2ecf20Sopenharmony_ci
1428c2ecf20Sopenharmony_ci
1438c2ecf20Sopenharmony_cistatic int join_cgroup_from_top(char *cgroup_path)
1448c2ecf20Sopenharmony_ci{
1458c2ecf20Sopenharmony_ci	char cgroup_procs_path[PATH_MAX + 1];
1468c2ecf20Sopenharmony_ci	pid_t pid = getpid();
1478c2ecf20Sopenharmony_ci	int fd, rc = 0;
1488c2ecf20Sopenharmony_ci
1498c2ecf20Sopenharmony_ci	snprintf(cgroup_procs_path, sizeof(cgroup_procs_path),
1508c2ecf20Sopenharmony_ci		 "%s/cgroup.procs", cgroup_path);
1518c2ecf20Sopenharmony_ci
1528c2ecf20Sopenharmony_ci	fd = open(cgroup_procs_path, O_WRONLY);
1538c2ecf20Sopenharmony_ci	if (fd < 0) {
1548c2ecf20Sopenharmony_ci		log_err("Opening Cgroup Procs: %s", cgroup_procs_path);
1558c2ecf20Sopenharmony_ci		return 1;
1568c2ecf20Sopenharmony_ci	}
1578c2ecf20Sopenharmony_ci
1588c2ecf20Sopenharmony_ci	if (dprintf(fd, "%d\n", pid) < 0) {
1598c2ecf20Sopenharmony_ci		log_err("Joining Cgroup");
1608c2ecf20Sopenharmony_ci		rc = 1;
1618c2ecf20Sopenharmony_ci	}
1628c2ecf20Sopenharmony_ci
1638c2ecf20Sopenharmony_ci	close(fd);
1648c2ecf20Sopenharmony_ci	return rc;
1658c2ecf20Sopenharmony_ci}
1668c2ecf20Sopenharmony_ci
1678c2ecf20Sopenharmony_ci/**
1688c2ecf20Sopenharmony_ci * join_cgroup() - Join a cgroup
1698c2ecf20Sopenharmony_ci * @path: The cgroup path, relative to the workdir, to join
1708c2ecf20Sopenharmony_ci *
1718c2ecf20Sopenharmony_ci * This function expects a cgroup to already be created, relative to the cgroup
1728c2ecf20Sopenharmony_ci * work dir, and it joins it. For example, passing "/my-cgroup" as the path
1738c2ecf20Sopenharmony_ci * would actually put the calling process into the cgroup
1748c2ecf20Sopenharmony_ci * "/cgroup-test-work-dir/my-cgroup"
1758c2ecf20Sopenharmony_ci *
1768c2ecf20Sopenharmony_ci * On success, it returns 0, otherwise on failure it returns 1.
1778c2ecf20Sopenharmony_ci */
1788c2ecf20Sopenharmony_ciint join_cgroup(const char *path)
1798c2ecf20Sopenharmony_ci{
1808c2ecf20Sopenharmony_ci	char cgroup_path[PATH_MAX + 1];
1818c2ecf20Sopenharmony_ci
1828c2ecf20Sopenharmony_ci	format_cgroup_path(cgroup_path, path);
1838c2ecf20Sopenharmony_ci	return join_cgroup_from_top(cgroup_path);
1848c2ecf20Sopenharmony_ci}
1858c2ecf20Sopenharmony_ci
1868c2ecf20Sopenharmony_ci/**
1878c2ecf20Sopenharmony_ci * cleanup_cgroup_environment() - Cleanup Cgroup Testing Environment
1888c2ecf20Sopenharmony_ci *
1898c2ecf20Sopenharmony_ci * This is an idempotent function to delete all temporary cgroups that
1908c2ecf20Sopenharmony_ci * have been created during the test, including the cgroup testing work
1918c2ecf20Sopenharmony_ci * directory.
1928c2ecf20Sopenharmony_ci *
1938c2ecf20Sopenharmony_ci * At call time, it moves the calling process to the root cgroup, and then
1948c2ecf20Sopenharmony_ci * runs the deletion process. It is idempotent, and should not fail, unless
1958c2ecf20Sopenharmony_ci * a process is lingering.
1968c2ecf20Sopenharmony_ci *
1978c2ecf20Sopenharmony_ci * On failure, it will print an error to stderr, and try to continue.
1988c2ecf20Sopenharmony_ci */
1998c2ecf20Sopenharmony_civoid cleanup_cgroup_environment(void)
2008c2ecf20Sopenharmony_ci{
2018c2ecf20Sopenharmony_ci	char cgroup_workdir[PATH_MAX + 1];
2028c2ecf20Sopenharmony_ci
2038c2ecf20Sopenharmony_ci	format_cgroup_path(cgroup_workdir, "");
2048c2ecf20Sopenharmony_ci	join_cgroup_from_top(CGROUP_MOUNT_PATH);
2058c2ecf20Sopenharmony_ci	nftw(cgroup_workdir, nftwfunc, WALK_FD_LIMIT, FTW_DEPTH | FTW_MOUNT);
2068c2ecf20Sopenharmony_ci}
2078c2ecf20Sopenharmony_ci
2088c2ecf20Sopenharmony_ci/**
2098c2ecf20Sopenharmony_ci * create_and_get_cgroup() - Create a cgroup, relative to workdir, and get the FD
2108c2ecf20Sopenharmony_ci * @path: The cgroup path, relative to the workdir, to join
2118c2ecf20Sopenharmony_ci *
2128c2ecf20Sopenharmony_ci * This function creates a cgroup under the top level workdir and returns the
2138c2ecf20Sopenharmony_ci * file descriptor. It is idempotent.
2148c2ecf20Sopenharmony_ci *
2158c2ecf20Sopenharmony_ci * On success, it returns the file descriptor. On failure it returns -1.
2168c2ecf20Sopenharmony_ci * If there is a failure, it prints the error to stderr.
2178c2ecf20Sopenharmony_ci */
2188c2ecf20Sopenharmony_ciint create_and_get_cgroup(const char *path)
2198c2ecf20Sopenharmony_ci{
2208c2ecf20Sopenharmony_ci	char cgroup_path[PATH_MAX + 1];
2218c2ecf20Sopenharmony_ci	int fd;
2228c2ecf20Sopenharmony_ci
2238c2ecf20Sopenharmony_ci	format_cgroup_path(cgroup_path, path);
2248c2ecf20Sopenharmony_ci	if (mkdir(cgroup_path, 0777) && errno != EEXIST) {
2258c2ecf20Sopenharmony_ci		log_err("mkdiring cgroup %s .. %s", path, cgroup_path);
2268c2ecf20Sopenharmony_ci		return -1;
2278c2ecf20Sopenharmony_ci	}
2288c2ecf20Sopenharmony_ci
2298c2ecf20Sopenharmony_ci	fd = open(cgroup_path, O_RDONLY);
2308c2ecf20Sopenharmony_ci	if (fd < 0) {
2318c2ecf20Sopenharmony_ci		log_err("Opening Cgroup");
2328c2ecf20Sopenharmony_ci		return -1;
2338c2ecf20Sopenharmony_ci	}
2348c2ecf20Sopenharmony_ci
2358c2ecf20Sopenharmony_ci	return fd;
2368c2ecf20Sopenharmony_ci}
2378c2ecf20Sopenharmony_ci
2388c2ecf20Sopenharmony_ci/**
2398c2ecf20Sopenharmony_ci * get_cgroup_id() - Get cgroup id for a particular cgroup path
2408c2ecf20Sopenharmony_ci * @path: The cgroup path, relative to the workdir, to join
2418c2ecf20Sopenharmony_ci *
2428c2ecf20Sopenharmony_ci * On success, it returns the cgroup id. On failure it returns 0,
2438c2ecf20Sopenharmony_ci * which is an invalid cgroup id.
2448c2ecf20Sopenharmony_ci * If there is a failure, it prints the error to stderr.
2458c2ecf20Sopenharmony_ci */
2468c2ecf20Sopenharmony_ciunsigned long long get_cgroup_id(const char *path)
2478c2ecf20Sopenharmony_ci{
2488c2ecf20Sopenharmony_ci	int dirfd, err, flags, mount_id, fhsize;
2498c2ecf20Sopenharmony_ci	union {
2508c2ecf20Sopenharmony_ci		unsigned long long cgid;
2518c2ecf20Sopenharmony_ci		unsigned char raw_bytes[8];
2528c2ecf20Sopenharmony_ci	} id;
2538c2ecf20Sopenharmony_ci	char cgroup_workdir[PATH_MAX + 1];
2548c2ecf20Sopenharmony_ci	struct file_handle *fhp, *fhp2;
2558c2ecf20Sopenharmony_ci	unsigned long long ret = 0;
2568c2ecf20Sopenharmony_ci
2578c2ecf20Sopenharmony_ci	format_cgroup_path(cgroup_workdir, path);
2588c2ecf20Sopenharmony_ci
2598c2ecf20Sopenharmony_ci	dirfd = AT_FDCWD;
2608c2ecf20Sopenharmony_ci	flags = 0;
2618c2ecf20Sopenharmony_ci	fhsize = sizeof(*fhp);
2628c2ecf20Sopenharmony_ci	fhp = calloc(1, fhsize);
2638c2ecf20Sopenharmony_ci	if (!fhp) {
2648c2ecf20Sopenharmony_ci		log_err("calloc");
2658c2ecf20Sopenharmony_ci		return 0;
2668c2ecf20Sopenharmony_ci	}
2678c2ecf20Sopenharmony_ci	err = name_to_handle_at(dirfd, cgroup_workdir, fhp, &mount_id, flags);
2688c2ecf20Sopenharmony_ci	if (err >= 0 || fhp->handle_bytes != 8) {
2698c2ecf20Sopenharmony_ci		log_err("name_to_handle_at");
2708c2ecf20Sopenharmony_ci		goto free_mem;
2718c2ecf20Sopenharmony_ci	}
2728c2ecf20Sopenharmony_ci
2738c2ecf20Sopenharmony_ci	fhsize = sizeof(struct file_handle) + fhp->handle_bytes;
2748c2ecf20Sopenharmony_ci	fhp2 = realloc(fhp, fhsize);
2758c2ecf20Sopenharmony_ci	if (!fhp2) {
2768c2ecf20Sopenharmony_ci		log_err("realloc");
2778c2ecf20Sopenharmony_ci		goto free_mem;
2788c2ecf20Sopenharmony_ci	}
2798c2ecf20Sopenharmony_ci	err = name_to_handle_at(dirfd, cgroup_workdir, fhp2, &mount_id, flags);
2808c2ecf20Sopenharmony_ci	fhp = fhp2;
2818c2ecf20Sopenharmony_ci	if (err < 0) {
2828c2ecf20Sopenharmony_ci		log_err("name_to_handle_at");
2838c2ecf20Sopenharmony_ci		goto free_mem;
2848c2ecf20Sopenharmony_ci	}
2858c2ecf20Sopenharmony_ci
2868c2ecf20Sopenharmony_ci	memcpy(id.raw_bytes, fhp->f_handle, 8);
2878c2ecf20Sopenharmony_ci	ret = id.cgid;
2888c2ecf20Sopenharmony_ci
2898c2ecf20Sopenharmony_cifree_mem:
2908c2ecf20Sopenharmony_ci	free(fhp);
2918c2ecf20Sopenharmony_ci	return ret;
2928c2ecf20Sopenharmony_ci}
2938c2ecf20Sopenharmony_ci
2948c2ecf20Sopenharmony_ciint cgroup_setup_and_join(const char *path) {
2958c2ecf20Sopenharmony_ci	int cg_fd;
2968c2ecf20Sopenharmony_ci
2978c2ecf20Sopenharmony_ci	if (setup_cgroup_environment()) {
2988c2ecf20Sopenharmony_ci		fprintf(stderr, "Failed to setup cgroup environment\n");
2998c2ecf20Sopenharmony_ci		return -EINVAL;
3008c2ecf20Sopenharmony_ci	}
3018c2ecf20Sopenharmony_ci
3028c2ecf20Sopenharmony_ci	cg_fd = create_and_get_cgroup(path);
3038c2ecf20Sopenharmony_ci	if (cg_fd < 0) {
3048c2ecf20Sopenharmony_ci		fprintf(stderr, "Failed to create test cgroup\n");
3058c2ecf20Sopenharmony_ci		cleanup_cgroup_environment();
3068c2ecf20Sopenharmony_ci		return cg_fd;
3078c2ecf20Sopenharmony_ci	}
3088c2ecf20Sopenharmony_ci
3098c2ecf20Sopenharmony_ci	if (join_cgroup(path)) {
3108c2ecf20Sopenharmony_ci		fprintf(stderr, "Failed to join cgroup\n");
3118c2ecf20Sopenharmony_ci		cleanup_cgroup_environment();
3128c2ecf20Sopenharmony_ci		return -EINVAL;
3138c2ecf20Sopenharmony_ci	}
3148c2ecf20Sopenharmony_ci	return cg_fd;
3158c2ecf20Sopenharmony_ci}
316