|  | // SPDX-License-Identifier: GPL-2.0 | 
|  | #define _GNU_SOURCE | 
|  | #include <sched.h> | 
|  | #include <sys/mount.h> | 
|  | #include <sys/stat.h> | 
|  | #include <sys/types.h> | 
|  | #include <linux/limits.h> | 
|  | #include <stdio.h> | 
|  | #include <stdlib.h> | 
|  | #include <linux/sched.h> | 
|  | #include <fcntl.h> | 
|  | #include <unistd.h> | 
|  | #include <ftw.h> | 
|  |  | 
|  |  | 
|  | #include "cgroup_helpers.h" | 
|  |  | 
|  | /* | 
|  | * To avoid relying on the system setup, when setup_cgroup_env is called | 
|  | * we create a new mount namespace, and cgroup namespace. The cgroup2 | 
|  | * root is mounted at CGROUP_MOUNT_PATH | 
|  | * | 
|  | * Unfortunately, most people don't have cgroupv2 enabled at this point in time. | 
|  | * It's easier to create our own mount namespace and manage it ourselves. | 
|  | * | 
|  | * We assume /mnt exists. | 
|  | */ | 
|  |  | 
|  | #define WALK_FD_LIMIT			16 | 
|  | #define CGROUP_MOUNT_PATH		"/mnt" | 
|  | #define CGROUP_WORK_DIR			"/cgroup-test-work-dir" | 
|  | #define format_cgroup_path(buf, path) \ | 
|  | snprintf(buf, sizeof(buf), "%s%s%s", CGROUP_MOUNT_PATH, \ | 
|  | CGROUP_WORK_DIR, path) | 
|  |  | 
|  | /** | 
|  | * setup_cgroup_environment() - Setup the cgroup environment | 
|  | * | 
|  | * After calling this function, cleanup_cgroup_environment should be called | 
|  | * once testing is complete. | 
|  | * | 
|  | * This function will print an error to stderr and return 1 if it is unable | 
|  | * to setup the cgroup environment. If setup is successful, 0 is returned. | 
|  | */ | 
|  | int setup_cgroup_environment(void) | 
|  | { | 
|  | char cgroup_workdir[PATH_MAX + 1]; | 
|  |  | 
|  | format_cgroup_path(cgroup_workdir, ""); | 
|  |  | 
|  | if (unshare(CLONE_NEWNS)) { | 
|  | log_err("unshare"); | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | if (mount("none", "/", NULL, MS_REC | MS_PRIVATE, NULL)) { | 
|  | log_err("mount fakeroot"); | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | if (mount("none", CGROUP_MOUNT_PATH, "cgroup2", 0, NULL) && errno != EBUSY) { | 
|  | log_err("mount cgroup2"); | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | /* Cleanup existing failed runs, now that the environment is setup */ | 
|  | cleanup_cgroup_environment(); | 
|  |  | 
|  | if (mkdir(cgroup_workdir, 0777) && errno != EEXIST) { | 
|  | log_err("mkdir cgroup work dir"); | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int nftwfunc(const char *filename, const struct stat *statptr, | 
|  | int fileflags, struct FTW *pfwt) | 
|  | { | 
|  | if ((fileflags & FTW_D) && rmdir(filename)) | 
|  | log_err("Removing cgroup: %s", filename); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  |  | 
|  | static int join_cgroup_from_top(char *cgroup_path) | 
|  | { | 
|  | char cgroup_procs_path[PATH_MAX + 1]; | 
|  | pid_t pid = getpid(); | 
|  | int fd, rc = 0; | 
|  |  | 
|  | snprintf(cgroup_procs_path, sizeof(cgroup_procs_path), | 
|  | "%s/cgroup.procs", cgroup_path); | 
|  |  | 
|  | fd = open(cgroup_procs_path, O_WRONLY); | 
|  | if (fd < 0) { | 
|  | log_err("Opening Cgroup Procs: %s", cgroup_procs_path); | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | if (dprintf(fd, "%d\n", pid) < 0) { | 
|  | log_err("Joining Cgroup"); | 
|  | rc = 1; | 
|  | } | 
|  |  | 
|  | close(fd); | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * join_cgroup() - Join a cgroup | 
|  | * @path: The cgroup path, relative to the workdir, to join | 
|  | * | 
|  | * This function expects a cgroup to already be created, relative to the cgroup | 
|  | * work dir, and it joins it. For example, passing "/my-cgroup" as the path | 
|  | * would actually put the calling process into the cgroup | 
|  | * "/cgroup-test-work-dir/my-cgroup" | 
|  | * | 
|  | * On success, it returns 0, otherwise on failure it returns 1. | 
|  | */ | 
|  | int join_cgroup(const char *path) | 
|  | { | 
|  | char cgroup_path[PATH_MAX + 1]; | 
|  |  | 
|  | format_cgroup_path(cgroup_path, path); | 
|  | return join_cgroup_from_top(cgroup_path); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * cleanup_cgroup_environment() - Cleanup Cgroup Testing Environment | 
|  | * | 
|  | * This is an idempotent function to delete all temporary cgroups that | 
|  | * have been created during the test, including the cgroup testing work | 
|  | * directory. | 
|  | * | 
|  | * At call time, it moves the calling process to the root cgroup, and then | 
|  | * runs the deletion process. It is idempotent, and should not fail, unless | 
|  | * a process is lingering. | 
|  | * | 
|  | * On failure, it will print an error to stderr, and try to continue. | 
|  | */ | 
|  | void cleanup_cgroup_environment(void) | 
|  | { | 
|  | char cgroup_workdir[PATH_MAX + 1]; | 
|  |  | 
|  | format_cgroup_path(cgroup_workdir, ""); | 
|  | join_cgroup_from_top(CGROUP_MOUNT_PATH); | 
|  | nftw(cgroup_workdir, nftwfunc, WALK_FD_LIMIT, FTW_DEPTH | FTW_MOUNT); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * create_and_get_cgroup() - Create a cgroup, relative to workdir, and get the FD | 
|  | * @path: The cgroup path, relative to the workdir, to join | 
|  | * | 
|  | * This function creates a cgroup under the top level workdir and returns the | 
|  | * file descriptor. It is idempotent. | 
|  | * | 
|  | * On success, it returns the file descriptor. On failure it returns 0. | 
|  | * If there is a failure, it prints the error to stderr. | 
|  | */ | 
|  | int create_and_get_cgroup(const char *path) | 
|  | { | 
|  | char cgroup_path[PATH_MAX + 1]; | 
|  | int fd; | 
|  |  | 
|  | format_cgroup_path(cgroup_path, path); | 
|  | if (mkdir(cgroup_path, 0777) && errno != EEXIST) { | 
|  | log_err("mkdiring cgroup %s .. %s", path, cgroup_path); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | fd = open(cgroup_path, O_RDONLY); | 
|  | if (fd < 0) { | 
|  | log_err("Opening Cgroup"); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | return fd; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * get_cgroup_id() - Get cgroup id for a particular cgroup path | 
|  | * @path: The cgroup path, relative to the workdir, to join | 
|  | * | 
|  | * On success, it returns the cgroup id. On failure it returns 0, | 
|  | * which is an invalid cgroup id. | 
|  | * If there is a failure, it prints the error to stderr. | 
|  | */ | 
|  | unsigned long long get_cgroup_id(const char *path) | 
|  | { | 
|  | int dirfd, err, flags, mount_id, fhsize; | 
|  | union { | 
|  | unsigned long long cgid; | 
|  | unsigned char raw_bytes[8]; | 
|  | } id; | 
|  | char cgroup_workdir[PATH_MAX + 1]; | 
|  | struct file_handle *fhp, *fhp2; | 
|  | unsigned long long ret = 0; | 
|  |  | 
|  | format_cgroup_path(cgroup_workdir, path); | 
|  |  | 
|  | dirfd = AT_FDCWD; | 
|  | flags = 0; | 
|  | fhsize = sizeof(*fhp); | 
|  | fhp = calloc(1, fhsize); | 
|  | if (!fhp) { | 
|  | log_err("calloc"); | 
|  | return 0; | 
|  | } | 
|  | err = name_to_handle_at(dirfd, cgroup_workdir, fhp, &mount_id, flags); | 
|  | if (err >= 0 || fhp->handle_bytes != 8) { | 
|  | log_err("name_to_handle_at"); | 
|  | goto free_mem; | 
|  | } | 
|  |  | 
|  | fhsize = sizeof(struct file_handle) + fhp->handle_bytes; | 
|  | fhp2 = realloc(fhp, fhsize); | 
|  | if (!fhp2) { | 
|  | log_err("realloc"); | 
|  | goto free_mem; | 
|  | } | 
|  | err = name_to_handle_at(dirfd, cgroup_workdir, fhp2, &mount_id, flags); | 
|  | fhp = fhp2; | 
|  | if (err < 0) { | 
|  | log_err("name_to_handle_at"); | 
|  | goto free_mem; | 
|  | } | 
|  |  | 
|  | memcpy(id.raw_bytes, fhp->f_handle, 8); | 
|  | ret = id.cgid; | 
|  |  | 
|  | free_mem: | 
|  | free(fhp); | 
|  | return ret; | 
|  | } |