1 // SPDX-License-Identifier: GPL-2.0 2 #define _GNU_SOURCE 3 #include <sched.h> 4 #include <sys/mount.h> 5 #include <sys/stat.h> 6 #include <sys/types.h> 7 #include <linux/limits.h> 8 #include <stdio.h> 9 #include <stdlib.h> 10 #include <linux/sched.h> 11 #include <fcntl.h> 12 #include <unistd.h> 13 #include <ftw.h> 14 15 16 #include "cgroup_helpers.h" 17 18 /* 19 * To avoid relying on the system setup, when setup_cgroup_env is called 20 * we create a new mount namespace, and cgroup namespace. The cgroup2 21 * root is mounted at CGROUP_MOUNT_PATH 22 * 23 * Unfortunately, most people don't have cgroupv2 enabled at this point in time. 24 * It's easier to create our own mount namespace and manage it ourselves. 25 * 26 * We assume /mnt exists. 27 */ 28 29 #define WALK_FD_LIMIT 16 30 #define CGROUP_MOUNT_PATH "/mnt" 31 #define CGROUP_WORK_DIR "/cgroup-test-work-dir" 32 #define format_cgroup_path(buf, path) \ 33 snprintf(buf, sizeof(buf), "%s%s%s", CGROUP_MOUNT_PATH, \ 34 CGROUP_WORK_DIR, path) 35 36 /** 37 * setup_cgroup_environment() - Setup the cgroup environment 38 * 39 * After calling this function, cleanup_cgroup_environment should be called 40 * once testing is complete. 41 * 42 * This function will print an error to stderr and return 1 if it is unable 43 * to setup the cgroup environment. If setup is successful, 0 is returned. 44 */ 45 int setup_cgroup_environment(void) 46 { 47 char cgroup_workdir[PATH_MAX + 1]; 48 49 format_cgroup_path(cgroup_workdir, ""); 50 51 if (unshare(CLONE_NEWNS)) { 52 log_err("unshare"); 53 return 1; 54 } 55 56 if (mount("none", "/", NULL, MS_REC | MS_PRIVATE, NULL)) { 57 log_err("mount fakeroot"); 58 return 1; 59 } 60 61 if (mount("none", CGROUP_MOUNT_PATH, "cgroup2", 0, NULL) && errno != EBUSY) { 62 log_err("mount cgroup2"); 63 return 1; 64 } 65 66 /* Cleanup existing failed runs, now that the environment is setup */ 67 cleanup_cgroup_environment(); 68 69 if (mkdir(cgroup_workdir, 0777) && errno != EEXIST) { 70 log_err("mkdir cgroup work dir"); 71 return 1; 72 } 73 74 return 0; 75 } 76 77 static int nftwfunc(const char *filename, const struct stat *statptr, 78 int fileflags, struct FTW *pfwt) 79 { 80 if ((fileflags & FTW_D) && rmdir(filename)) 81 log_err("Removing cgroup: %s", filename); 82 return 0; 83 } 84 85 86 static int join_cgroup_from_top(char *cgroup_path) 87 { 88 char cgroup_procs_path[PATH_MAX + 1]; 89 pid_t pid = getpid(); 90 int fd, rc = 0; 91 92 snprintf(cgroup_procs_path, sizeof(cgroup_procs_path), 93 "%s/cgroup.procs", cgroup_path); 94 95 fd = open(cgroup_procs_path, O_WRONLY); 96 if (fd < 0) { 97 log_err("Opening Cgroup Procs: %s", cgroup_procs_path); 98 return 1; 99 } 100 101 if (dprintf(fd, "%d\n", pid) < 0) { 102 log_err("Joining Cgroup"); 103 rc = 1; 104 } 105 106 close(fd); 107 return rc; 108 } 109 110 /** 111 * join_cgroup() - Join a cgroup 112 * @path: The cgroup path, relative to the workdir, to join 113 * 114 * This function expects a cgroup to already be created, relative to the cgroup 115 * work dir, and it joins it. For example, passing "/my-cgroup" as the path 116 * would actually put the calling process into the cgroup 117 * "/cgroup-test-work-dir/my-cgroup" 118 * 119 * On success, it returns 0, otherwise on failure it returns 1. 120 */ 121 int join_cgroup(const char *path) 122 { 123 char cgroup_path[PATH_MAX + 1]; 124 125 format_cgroup_path(cgroup_path, path); 126 return join_cgroup_from_top(cgroup_path); 127 } 128 129 /** 130 * cleanup_cgroup_environment() - Cleanup Cgroup Testing Environment 131 * 132 * This is an idempotent function to delete all temporary cgroups that 133 * have been created during the test, including the cgroup testing work 134 * directory. 135 * 136 * At call time, it moves the calling process to the root cgroup, and then 137 * runs the deletion process. It is idempotent, and should not fail, unless 138 * a process is lingering. 139 * 140 * On failure, it will print an error to stderr, and try to continue. 141 */ 142 void cleanup_cgroup_environment(void) 143 { 144 char cgroup_workdir[PATH_MAX + 1]; 145 146 format_cgroup_path(cgroup_workdir, ""); 147 join_cgroup_from_top(CGROUP_MOUNT_PATH); 148 nftw(cgroup_workdir, nftwfunc, WALK_FD_LIMIT, FTW_DEPTH | FTW_MOUNT); 149 } 150 151 /** 152 * create_and_get_cgroup() - Create a cgroup, relative to workdir, and get the FD 153 * @path: The cgroup path, relative to the workdir, to join 154 * 155 * This function creates a cgroup under the top level workdir and returns the 156 * file descriptor. It is idempotent. 157 * 158 * On success, it returns the file descriptor. On failure it returns 0. 159 * If there is a failure, it prints the error to stderr. 160 */ 161 int create_and_get_cgroup(const char *path) 162 { 163 char cgroup_path[PATH_MAX + 1]; 164 int fd; 165 166 format_cgroup_path(cgroup_path, path); 167 if (mkdir(cgroup_path, 0777) && errno != EEXIST) { 168 log_err("mkdiring cgroup %s .. %s", path, cgroup_path); 169 return 0; 170 } 171 172 fd = open(cgroup_path, O_RDONLY); 173 if (fd < 0) { 174 log_err("Opening Cgroup"); 175 return 0; 176 } 177 178 return fd; 179 } 180 181 /** 182 * get_cgroup_id() - Get cgroup id for a particular cgroup path 183 * @path: The cgroup path, relative to the workdir, to join 184 * 185 * On success, it returns the cgroup id. On failure it returns 0, 186 * which is an invalid cgroup id. 187 * If there is a failure, it prints the error to stderr. 188 */ 189 unsigned long long get_cgroup_id(const char *path) 190 { 191 int dirfd, err, flags, mount_id, fhsize; 192 union { 193 unsigned long long cgid; 194 unsigned char raw_bytes[8]; 195 } id; 196 char cgroup_workdir[PATH_MAX + 1]; 197 struct file_handle *fhp, *fhp2; 198 unsigned long long ret = 0; 199 200 format_cgroup_path(cgroup_workdir, path); 201 202 dirfd = AT_FDCWD; 203 flags = 0; 204 fhsize = sizeof(*fhp); 205 fhp = calloc(1, fhsize); 206 if (!fhp) { 207 log_err("calloc"); 208 return 0; 209 } 210 err = name_to_handle_at(dirfd, cgroup_workdir, fhp, &mount_id, flags); 211 if (err >= 0 || fhp->handle_bytes != 8) { 212 log_err("name_to_handle_at"); 213 goto free_mem; 214 } 215 216 fhsize = sizeof(struct file_handle) + fhp->handle_bytes; 217 fhp2 = realloc(fhp, fhsize); 218 if (!fhp2) { 219 log_err("realloc"); 220 goto free_mem; 221 } 222 err = name_to_handle_at(dirfd, cgroup_workdir, fhp2, &mount_id, flags); 223 fhp = fhp2; 224 if (err < 0) { 225 log_err("name_to_handle_at"); 226 goto free_mem; 227 } 228 229 memcpy(id.raw_bytes, fhp->f_handle, 8); 230 ret = id.cgid; 231 232 free_mem: 233 free(fhp); 234 return ret; 235 } 236