Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0
0002 #define _GNU_SOURCE
0003 #include <sched.h>
0004 #include <sys/mount.h>
0005 #include <sys/stat.h>
0006 #include <sys/types.h>
0007 #include <linux/limits.h>
0008 #include <stdio.h>
0009 #include <stdlib.h>
0010 #include <linux/sched.h>
0011 #include <fcntl.h>
0012 #include <unistd.h>
0013 #include <ftw.h>
0014 
0015 #include "cgroup_helpers.h"
0016 
0017 /*
0018  * To avoid relying on the system setup, when setup_cgroup_env is called
0019  * we create a new mount namespace, and cgroup namespace. The cgroupv2
0020  * root is mounted at CGROUP_MOUNT_PATH. Unfortunately, most people don't
0021  * have cgroupv2 enabled at this point in time. It's easier to create our
0022  * own mount namespace and manage it ourselves. We assume /mnt exists.
0023  *
0024  * Related cgroupv1 helpers are named *classid*(), since we only use the
0025  * net_cls controller for tagging net_cls.classid. We assume the default
0026  * mount under /sys/fs/cgroup/net_cls, which should be the case for the
0027  * vast majority of users.
0028  */
0029 
0030 #define WALK_FD_LIMIT           16
0031 
0032 #define CGROUP_MOUNT_PATH       "/mnt"
0033 #define CGROUP_MOUNT_DFLT       "/sys/fs/cgroup"
0034 #define NETCLS_MOUNT_PATH       CGROUP_MOUNT_DFLT "/net_cls"
0035 #define CGROUP_WORK_DIR         "/cgroup-test-work-dir"
0036 #define format_cgroup_path(buf, path) \
0037     snprintf(buf, sizeof(buf), "%s%s%d%s", CGROUP_MOUNT_PATH, \
0038     CGROUP_WORK_DIR, getpid(), path)
0039 
0040 #define format_classid_path(buf)                \
0041     snprintf(buf, sizeof(buf), "%s%s", NETCLS_MOUNT_PATH,   \
0042          CGROUP_WORK_DIR)
0043 
0044 /**
0045  * enable_all_controllers() - Enable all available cgroup v2 controllers
0046  *
0047  * Enable all available cgroup v2 controllers in order to increase
0048  * the code coverage.
0049  *
0050  * If successful, 0 is returned.
0051  */
0052 static int enable_all_controllers(char *cgroup_path)
0053 {
0054     char path[PATH_MAX + 1];
0055     char buf[PATH_MAX];
0056     char *c, *c2;
0057     int fd, cfd;
0058     ssize_t len;
0059 
0060     snprintf(path, sizeof(path), "%s/cgroup.controllers", cgroup_path);
0061     fd = open(path, O_RDONLY);
0062     if (fd < 0) {
0063         log_err("Opening cgroup.controllers: %s", path);
0064         return 1;
0065     }
0066 
0067     len = read(fd, buf, sizeof(buf) - 1);
0068     if (len < 0) {
0069         close(fd);
0070         log_err("Reading cgroup.controllers: %s", path);
0071         return 1;
0072     }
0073     buf[len] = 0;
0074     close(fd);
0075 
0076     /* No controllers available? We're probably on cgroup v1. */
0077     if (len == 0)
0078         return 0;
0079 
0080     snprintf(path, sizeof(path), "%s/cgroup.subtree_control", cgroup_path);
0081     cfd = open(path, O_RDWR);
0082     if (cfd < 0) {
0083         log_err("Opening cgroup.subtree_control: %s", path);
0084         return 1;
0085     }
0086 
0087     for (c = strtok_r(buf, " ", &c2); c; c = strtok_r(NULL, " ", &c2)) {
0088         if (dprintf(cfd, "+%s\n", c) <= 0) {
0089             log_err("Enabling controller %s: %s", c, path);
0090             close(cfd);
0091             return 1;
0092         }
0093     }
0094     close(cfd);
0095     return 0;
0096 }
0097 
0098 /**
0099  * setup_cgroup_environment() - Setup the cgroup environment
0100  *
0101  * After calling this function, cleanup_cgroup_environment should be called
0102  * once testing is complete.
0103  *
0104  * This function will print an error to stderr and return 1 if it is unable
0105  * to setup the cgroup environment. If setup is successful, 0 is returned.
0106  */
0107 int setup_cgroup_environment(void)
0108 {
0109     char cgroup_workdir[PATH_MAX - 24];
0110 
0111     format_cgroup_path(cgroup_workdir, "");
0112 
0113     if (unshare(CLONE_NEWNS)) {
0114         log_err("unshare");
0115         return 1;
0116     }
0117 
0118     if (mount("none", "/", NULL, MS_REC | MS_PRIVATE, NULL)) {
0119         log_err("mount fakeroot");
0120         return 1;
0121     }
0122 
0123     if (mount("none", CGROUP_MOUNT_PATH, "cgroup2", 0, NULL) && errno != EBUSY) {
0124         log_err("mount cgroup2");
0125         return 1;
0126     }
0127 
0128     /* Cleanup existing failed runs, now that the environment is setup */
0129     cleanup_cgroup_environment();
0130 
0131     if (mkdir(cgroup_workdir, 0777) && errno != EEXIST) {
0132         log_err("mkdir cgroup work dir");
0133         return 1;
0134     }
0135 
0136     if (enable_all_controllers(cgroup_workdir))
0137         return 1;
0138 
0139     return 0;
0140 }
0141 
0142 static int nftwfunc(const char *filename, const struct stat *statptr,
0143             int fileflags, struct FTW *pfwt)
0144 {
0145     if ((fileflags & FTW_D) && rmdir(filename))
0146         log_err("Removing cgroup: %s", filename);
0147     return 0;
0148 }
0149 
0150 static int join_cgroup_from_top(const char *cgroup_path)
0151 {
0152     char cgroup_procs_path[PATH_MAX + 1];
0153     pid_t pid = getpid();
0154     int fd, rc = 0;
0155 
0156     snprintf(cgroup_procs_path, sizeof(cgroup_procs_path),
0157          "%s/cgroup.procs", cgroup_path);
0158 
0159     fd = open(cgroup_procs_path, O_WRONLY);
0160     if (fd < 0) {
0161         log_err("Opening Cgroup Procs: %s", cgroup_procs_path);
0162         return 1;
0163     }
0164 
0165     if (dprintf(fd, "%d\n", pid) < 0) {
0166         log_err("Joining Cgroup");
0167         rc = 1;
0168     }
0169 
0170     close(fd);
0171     return rc;
0172 }
0173 
0174 /**
0175  * join_cgroup() - Join a cgroup
0176  * @path: The cgroup path, relative to the workdir, to join
0177  *
0178  * This function expects a cgroup to already be created, relative to the cgroup
0179  * work dir, and it joins it. For example, passing "/my-cgroup" as the path
0180  * would actually put the calling process into the cgroup
0181  * "/cgroup-test-work-dir/my-cgroup"
0182  *
0183  * On success, it returns 0, otherwise on failure it returns 1.
0184  */
0185 int join_cgroup(const char *path)
0186 {
0187     char cgroup_path[PATH_MAX + 1];
0188 
0189     format_cgroup_path(cgroup_path, path);
0190     return join_cgroup_from_top(cgroup_path);
0191 }
0192 
0193 /**
0194  * cleanup_cgroup_environment() - Cleanup Cgroup Testing Environment
0195  *
0196  * This is an idempotent function to delete all temporary cgroups that
0197  * have been created during the test, including the cgroup testing work
0198  * directory.
0199  *
0200  * At call time, it moves the calling process to the root cgroup, and then
0201  * runs the deletion process. It is idempotent, and should not fail, unless
0202  * a process is lingering.
0203  *
0204  * On failure, it will print an error to stderr, and try to continue.
0205  */
0206 void cleanup_cgroup_environment(void)
0207 {
0208     char cgroup_workdir[PATH_MAX + 1];
0209 
0210     format_cgroup_path(cgroup_workdir, "");
0211     join_cgroup_from_top(CGROUP_MOUNT_PATH);
0212     nftw(cgroup_workdir, nftwfunc, WALK_FD_LIMIT, FTW_DEPTH | FTW_MOUNT);
0213 }
0214 
0215 /**
0216  * create_and_get_cgroup() - Create a cgroup, relative to workdir, and get the FD
0217  * @path: The cgroup path, relative to the workdir, to join
0218  *
0219  * This function creates a cgroup under the top level workdir and returns the
0220  * file descriptor. It is idempotent.
0221  *
0222  * On success, it returns the file descriptor. On failure it returns -1.
0223  * If there is a failure, it prints the error to stderr.
0224  */
0225 int create_and_get_cgroup(const char *path)
0226 {
0227     char cgroup_path[PATH_MAX + 1];
0228     int fd;
0229 
0230     format_cgroup_path(cgroup_path, path);
0231     if (mkdir(cgroup_path, 0777) && errno != EEXIST) {
0232         log_err("mkdiring cgroup %s .. %s", path, cgroup_path);
0233         return -1;
0234     }
0235 
0236     fd = open(cgroup_path, O_RDONLY);
0237     if (fd < 0) {
0238         log_err("Opening Cgroup");
0239         return -1;
0240     }
0241 
0242     return fd;
0243 }
0244 
0245 /**
0246  * get_cgroup_id() - Get cgroup id for a particular cgroup path
0247  * @path: The cgroup path, relative to the workdir, to join
0248  *
0249  * On success, it returns the cgroup id. On failure it returns 0,
0250  * which is an invalid cgroup id.
0251  * If there is a failure, it prints the error to stderr.
0252  */
0253 unsigned long long get_cgroup_id(const char *path)
0254 {
0255     int dirfd, err, flags, mount_id, fhsize;
0256     union {
0257         unsigned long long cgid;
0258         unsigned char raw_bytes[8];
0259     } id;
0260     char cgroup_workdir[PATH_MAX + 1];
0261     struct file_handle *fhp, *fhp2;
0262     unsigned long long ret = 0;
0263 
0264     format_cgroup_path(cgroup_workdir, path);
0265 
0266     dirfd = AT_FDCWD;
0267     flags = 0;
0268     fhsize = sizeof(*fhp);
0269     fhp = calloc(1, fhsize);
0270     if (!fhp) {
0271         log_err("calloc");
0272         return 0;
0273     }
0274     err = name_to_handle_at(dirfd, cgroup_workdir, fhp, &mount_id, flags);
0275     if (err >= 0 || fhp->handle_bytes != 8) {
0276         log_err("name_to_handle_at");
0277         goto free_mem;
0278     }
0279 
0280     fhsize = sizeof(struct file_handle) + fhp->handle_bytes;
0281     fhp2 = realloc(fhp, fhsize);
0282     if (!fhp2) {
0283         log_err("realloc");
0284         goto free_mem;
0285     }
0286     err = name_to_handle_at(dirfd, cgroup_workdir, fhp2, &mount_id, flags);
0287     fhp = fhp2;
0288     if (err < 0) {
0289         log_err("name_to_handle_at");
0290         goto free_mem;
0291     }
0292 
0293     memcpy(id.raw_bytes, fhp->f_handle, 8);
0294     ret = id.cgid;
0295 
0296 free_mem:
0297     free(fhp);
0298     return ret;
0299 }
0300 
0301 int cgroup_setup_and_join(const char *path) {
0302     int cg_fd;
0303 
0304     if (setup_cgroup_environment()) {
0305         fprintf(stderr, "Failed to setup cgroup environment\n");
0306         return -EINVAL;
0307     }
0308 
0309     cg_fd = create_and_get_cgroup(path);
0310     if (cg_fd < 0) {
0311         fprintf(stderr, "Failed to create test cgroup\n");
0312         cleanup_cgroup_environment();
0313         return cg_fd;
0314     }
0315 
0316     if (join_cgroup(path)) {
0317         fprintf(stderr, "Failed to join cgroup\n");
0318         cleanup_cgroup_environment();
0319         return -EINVAL;
0320     }
0321     return cg_fd;
0322 }
0323 
0324 /**
0325  * setup_classid_environment() - Setup the cgroupv1 net_cls environment
0326  *
0327  * After calling this function, cleanup_classid_environment should be called
0328  * once testing is complete.
0329  *
0330  * This function will print an error to stderr and return 1 if it is unable
0331  * to setup the cgroup environment. If setup is successful, 0 is returned.
0332  */
0333 int setup_classid_environment(void)
0334 {
0335     char cgroup_workdir[PATH_MAX + 1];
0336 
0337     format_classid_path(cgroup_workdir);
0338 
0339     if (mount("tmpfs", CGROUP_MOUNT_DFLT, "tmpfs", 0, NULL) &&
0340         errno != EBUSY) {
0341         log_err("mount cgroup base");
0342         return 1;
0343     }
0344 
0345     if (mkdir(NETCLS_MOUNT_PATH, 0777) && errno != EEXIST) {
0346         log_err("mkdir cgroup net_cls");
0347         return 1;
0348     }
0349 
0350     if (mount("net_cls", NETCLS_MOUNT_PATH, "cgroup", 0, "net_cls") &&
0351         errno != EBUSY) {
0352         log_err("mount cgroup net_cls");
0353         return 1;
0354     }
0355 
0356     cleanup_classid_environment();
0357 
0358     if (mkdir(cgroup_workdir, 0777) && errno != EEXIST) {
0359         log_err("mkdir cgroup work dir");
0360         return 1;
0361     }
0362 
0363     return 0;
0364 }
0365 
0366 /**
0367  * set_classid() - Set a cgroupv1 net_cls classid
0368  * @id: the numeric classid
0369  *
0370  * Writes the passed classid into the cgroup work dir's net_cls.classid
0371  * file in order to later on trigger socket tagging.
0372  *
0373  * On success, it returns 0, otherwise on failure it returns 1. If there
0374  * is a failure, it prints the error to stderr.
0375  */
0376 int set_classid(unsigned int id)
0377 {
0378     char cgroup_workdir[PATH_MAX - 42];
0379     char cgroup_classid_path[PATH_MAX + 1];
0380     int fd, rc = 0;
0381 
0382     format_classid_path(cgroup_workdir);
0383     snprintf(cgroup_classid_path, sizeof(cgroup_classid_path),
0384          "%s/net_cls.classid", cgroup_workdir);
0385 
0386     fd = open(cgroup_classid_path, O_WRONLY);
0387     if (fd < 0) {
0388         log_err("Opening cgroup classid: %s", cgroup_classid_path);
0389         return 1;
0390     }
0391 
0392     if (dprintf(fd, "%u\n", id) < 0) {
0393         log_err("Setting cgroup classid");
0394         rc = 1;
0395     }
0396 
0397     close(fd);
0398     return rc;
0399 }
0400 
0401 /**
0402  * join_classid() - Join a cgroupv1 net_cls classid
0403  *
0404  * This function expects the cgroup work dir to be already created, as we
0405  * join it here. This causes the process sockets to be tagged with the given
0406  * net_cls classid.
0407  *
0408  * On success, it returns 0, otherwise on failure it returns 1.
0409  */
0410 int join_classid(void)
0411 {
0412     char cgroup_workdir[PATH_MAX + 1];
0413 
0414     format_classid_path(cgroup_workdir);
0415     return join_cgroup_from_top(cgroup_workdir);
0416 }
0417 
0418 /**
0419  * cleanup_classid_environment() - Cleanup the cgroupv1 net_cls environment
0420  *
0421  * At call time, it moves the calling process to the root cgroup, and then
0422  * runs the deletion process.
0423  *
0424  * On failure, it will print an error to stderr, and try to continue.
0425  */
0426 void cleanup_classid_environment(void)
0427 {
0428     char cgroup_workdir[PATH_MAX + 1];
0429 
0430     format_classid_path(cgroup_workdir);
0431     join_cgroup_from_top(NETCLS_MOUNT_PATH);
0432     nftw(cgroup_workdir, nftwfunc, WALK_FD_LIMIT, FTW_DEPTH | FTW_MOUNT);
0433 }