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 * enable_all_controllers() - Enable all available cgroup v2 controllers 38 * 39 * Enable all available cgroup v2 controllers in order to increase 40 * the code coverage. 41 * 42 * If successful, 0 is returned. 43 */ 44 static int enable_all_controllers(char *cgroup_path) 45 { 46 char path[PATH_MAX + 1]; 47 char buf[PATH_MAX]; 48 char *c, *c2; 49 int fd, cfd; 50 ssize_t len; 51 52 snprintf(path, sizeof(path), "%s/cgroup.controllers", cgroup_path); 53 fd = open(path, O_RDONLY); 54 if (fd < 0) { 55 log_err("Opening cgroup.controllers: %s", path); 56 return 1; 57 } 58 59 len = read(fd, buf, sizeof(buf) - 1); 60 if (len < 0) { 61 close(fd); 62 log_err("Reading cgroup.controllers: %s", path); 63 return 1; 64 } 65 buf[len] = 0; 66 close(fd); 67 68 /* No controllers available? We're probably on cgroup v1. */ 69 if (len == 0) 70 return 0; 71 72 snprintf(path, sizeof(path), "%s/cgroup.subtree_control", cgroup_path); 73 cfd = open(path, O_RDWR); 74 if (cfd < 0) { 75 log_err("Opening cgroup.subtree_control: %s", path); 76 return 1; 77 } 78 79 for (c = strtok_r(buf, " ", &c2); c; c = strtok_r(NULL, " ", &c2)) { 80 if (dprintf(cfd, "+%s\n", c) <= 0) { 81 log_err("Enabling controller %s: %s", c, path); 82 close(cfd); 83 return 1; 84 } 85 } 86 close(cfd); 87 return 0; 88 } 89 90 /** 91 * setup_cgroup_environment() - Setup the cgroup environment 92 * 93 * After calling this function, cleanup_cgroup_environment should be called 94 * once testing is complete. 95 * 96 * This function will print an error to stderr and return 1 if it is unable 97 * to setup the cgroup environment. If setup is successful, 0 is returned. 98 */ 99 int setup_cgroup_environment(void) 100 { 101 char cgroup_workdir[PATH_MAX - 24]; 102 103 format_cgroup_path(cgroup_workdir, ""); 104 105 if (unshare(CLONE_NEWNS)) { 106 log_err("unshare"); 107 return 1; 108 } 109 110 if (mount("none", "/", NULL, MS_REC | MS_PRIVATE, NULL)) { 111 log_err("mount fakeroot"); 112 return 1; 113 } 114 115 if (mount("none", CGROUP_MOUNT_PATH, "cgroup2", 0, NULL) && errno != EBUSY) { 116 log_err("mount cgroup2"); 117 return 1; 118 } 119 120 /* Cleanup existing failed runs, now that the environment is setup */ 121 cleanup_cgroup_environment(); 122 123 if (mkdir(cgroup_workdir, 0777) && errno != EEXIST) { 124 log_err("mkdir cgroup work dir"); 125 return 1; 126 } 127 128 if (enable_all_controllers(cgroup_workdir)) 129 return 1; 130 131 return 0; 132 } 133 134 static int nftwfunc(const char *filename, const struct stat *statptr, 135 int fileflags, struct FTW *pfwt) 136 { 137 if ((fileflags & FTW_D) && rmdir(filename)) 138 log_err("Removing cgroup: %s", filename); 139 return 0; 140 } 141 142 143 static int join_cgroup_from_top(char *cgroup_path) 144 { 145 char cgroup_procs_path[PATH_MAX + 1]; 146 pid_t pid = getpid(); 147 int fd, rc = 0; 148 149 snprintf(cgroup_procs_path, sizeof(cgroup_procs_path), 150 "%s/cgroup.procs", cgroup_path); 151 152 fd = open(cgroup_procs_path, O_WRONLY); 153 if (fd < 0) { 154 log_err("Opening Cgroup Procs: %s", cgroup_procs_path); 155 return 1; 156 } 157 158 if (dprintf(fd, "%d\n", pid) < 0) { 159 log_err("Joining Cgroup"); 160 rc = 1; 161 } 162 163 close(fd); 164 return rc; 165 } 166 167 /** 168 * join_cgroup() - Join a cgroup 169 * @path: The cgroup path, relative to the workdir, to join 170 * 171 * This function expects a cgroup to already be created, relative to the cgroup 172 * work dir, and it joins it. For example, passing "/my-cgroup" as the path 173 * would actually put the calling process into the cgroup 174 * "/cgroup-test-work-dir/my-cgroup" 175 * 176 * On success, it returns 0, otherwise on failure it returns 1. 177 */ 178 int join_cgroup(const char *path) 179 { 180 char cgroup_path[PATH_MAX + 1]; 181 182 format_cgroup_path(cgroup_path, path); 183 return join_cgroup_from_top(cgroup_path); 184 } 185 186 /** 187 * cleanup_cgroup_environment() - Cleanup Cgroup Testing Environment 188 * 189 * This is an idempotent function to delete all temporary cgroups that 190 * have been created during the test, including the cgroup testing work 191 * directory. 192 * 193 * At call time, it moves the calling process to the root cgroup, and then 194 * runs the deletion process. It is idempotent, and should not fail, unless 195 * a process is lingering. 196 * 197 * On failure, it will print an error to stderr, and try to continue. 198 */ 199 void cleanup_cgroup_environment(void) 200 { 201 char cgroup_workdir[PATH_MAX + 1]; 202 203 format_cgroup_path(cgroup_workdir, ""); 204 join_cgroup_from_top(CGROUP_MOUNT_PATH); 205 nftw(cgroup_workdir, nftwfunc, WALK_FD_LIMIT, FTW_DEPTH | FTW_MOUNT); 206 } 207 208 /** 209 * create_and_get_cgroup() - Create a cgroup, relative to workdir, and get the FD 210 * @path: The cgroup path, relative to the workdir, to join 211 * 212 * This function creates a cgroup under the top level workdir and returns the 213 * file descriptor. It is idempotent. 214 * 215 * On success, it returns the file descriptor. On failure it returns -1. 216 * If there is a failure, it prints the error to stderr. 217 */ 218 int create_and_get_cgroup(const char *path) 219 { 220 char cgroup_path[PATH_MAX + 1]; 221 int fd; 222 223 format_cgroup_path(cgroup_path, path); 224 if (mkdir(cgroup_path, 0777) && errno != EEXIST) { 225 log_err("mkdiring cgroup %s .. %s", path, cgroup_path); 226 return -1; 227 } 228 229 fd = open(cgroup_path, O_RDONLY); 230 if (fd < 0) { 231 log_err("Opening Cgroup"); 232 return -1; 233 } 234 235 return fd; 236 } 237 238 /** 239 * get_cgroup_id() - Get cgroup id for a particular cgroup path 240 * @path: The cgroup path, relative to the workdir, to join 241 * 242 * On success, it returns the cgroup id. On failure it returns 0, 243 * which is an invalid cgroup id. 244 * If there is a failure, it prints the error to stderr. 245 */ 246 unsigned long long get_cgroup_id(const char *path) 247 { 248 int dirfd, err, flags, mount_id, fhsize; 249 union { 250 unsigned long long cgid; 251 unsigned char raw_bytes[8]; 252 } id; 253 char cgroup_workdir[PATH_MAX + 1]; 254 struct file_handle *fhp, *fhp2; 255 unsigned long long ret = 0; 256 257 format_cgroup_path(cgroup_workdir, path); 258 259 dirfd = AT_FDCWD; 260 flags = 0; 261 fhsize = sizeof(*fhp); 262 fhp = calloc(1, fhsize); 263 if (!fhp) { 264 log_err("calloc"); 265 return 0; 266 } 267 err = name_to_handle_at(dirfd, cgroup_workdir, fhp, &mount_id, flags); 268 if (err >= 0 || fhp->handle_bytes != 8) { 269 log_err("name_to_handle_at"); 270 goto free_mem; 271 } 272 273 fhsize = sizeof(struct file_handle) + fhp->handle_bytes; 274 fhp2 = realloc(fhp, fhsize); 275 if (!fhp2) { 276 log_err("realloc"); 277 goto free_mem; 278 } 279 err = name_to_handle_at(dirfd, cgroup_workdir, fhp2, &mount_id, flags); 280 fhp = fhp2; 281 if (err < 0) { 282 log_err("name_to_handle_at"); 283 goto free_mem; 284 } 285 286 memcpy(id.raw_bytes, fhp->f_handle, 8); 287 ret = id.cgid; 288 289 free_mem: 290 free(fhp); 291 return ret; 292 } 293