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