Path: blob/master/tools/testing/selftests/cgroup/lib/cgroup_util.c
26288 views
/* SPDX-License-Identifier: GPL-2.0 */12#define _GNU_SOURCE34#include <errno.h>5#include <fcntl.h>6#include <linux/limits.h>7#include <poll.h>8#include <signal.h>9#include <stdio.h>10#include <stdlib.h>11#include <string.h>12#include <sys/inotify.h>13#include <sys/stat.h>14#include <sys/types.h>15#include <sys/wait.h>16#include <unistd.h>1718#include "cgroup_util.h"19#include "../../clone3/clone3_selftests.h"2021bool cg_test_v1_named;2223/* Returns read len on success, or -errno on failure. */24ssize_t read_text(const char *path, char *buf, size_t max_len)25{26ssize_t len;27int fd;2829fd = open(path, O_RDONLY);30if (fd < 0)31return -errno;3233len = read(fd, buf, max_len - 1);3435if (len >= 0)36buf[len] = 0;3738close(fd);39return len < 0 ? -errno : len;40}4142/* Returns written len on success, or -errno on failure. */43ssize_t write_text(const char *path, char *buf, ssize_t len)44{45int fd;4647fd = open(path, O_WRONLY | O_APPEND);48if (fd < 0)49return -errno;5051len = write(fd, buf, len);52close(fd);53return len < 0 ? -errno : len;54}5556char *cg_name(const char *root, const char *name)57{58size_t len = strlen(root) + strlen(name) + 2;59char *ret = malloc(len);6061snprintf(ret, len, "%s/%s", root, name);6263return ret;64}6566char *cg_name_indexed(const char *root, const char *name, int index)67{68size_t len = strlen(root) + strlen(name) + 10;69char *ret = malloc(len);7071snprintf(ret, len, "%s/%s_%d", root, name, index);7273return ret;74}7576char *cg_control(const char *cgroup, const char *control)77{78size_t len = strlen(cgroup) + strlen(control) + 2;79char *ret = malloc(len);8081snprintf(ret, len, "%s/%s", cgroup, control);8283return ret;84}8586/* Returns 0 on success, or -errno on failure. */87int cg_read(const char *cgroup, const char *control, char *buf, size_t len)88{89char path[PATH_MAX];90ssize_t ret;9192snprintf(path, sizeof(path), "%s/%s", cgroup, control);9394ret = read_text(path, buf, len);95return ret >= 0 ? 0 : ret;96}9798int cg_read_strcmp(const char *cgroup, const char *control,99const char *expected)100{101size_t size;102char *buf;103int ret;104105/* Handle the case of comparing against empty string */106if (!expected)107return -1;108else109size = strlen(expected) + 1;110111buf = malloc(size);112if (!buf)113return -1;114115if (cg_read(cgroup, control, buf, size)) {116free(buf);117return -1;118}119120ret = strcmp(expected, buf);121free(buf);122return ret;123}124125int cg_read_strstr(const char *cgroup, const char *control, const char *needle)126{127char buf[PAGE_SIZE];128129if (cg_read(cgroup, control, buf, sizeof(buf)))130return -1;131132return strstr(buf, needle) ? 0 : -1;133}134135long cg_read_long(const char *cgroup, const char *control)136{137char buf[128];138139if (cg_read(cgroup, control, buf, sizeof(buf)))140return -1;141142return atol(buf);143}144145long cg_read_long_fd(int fd)146{147char buf[128];148149if (pread(fd, buf, sizeof(buf), 0) <= 0)150return -1;151152return atol(buf);153}154155long cg_read_key_long(const char *cgroup, const char *control, const char *key)156{157char buf[PAGE_SIZE];158char *ptr;159160if (cg_read(cgroup, control, buf, sizeof(buf)))161return -1;162163ptr = strstr(buf, key);164if (!ptr)165return -1;166167return atol(ptr + strlen(key));168}169170long cg_read_lc(const char *cgroup, const char *control)171{172char buf[PAGE_SIZE];173const char delim[] = "\n";174char *line;175long cnt = 0;176177if (cg_read(cgroup, control, buf, sizeof(buf)))178return -1;179180for (line = strtok(buf, delim); line; line = strtok(NULL, delim))181cnt++;182183return cnt;184}185186/* Returns 0 on success, or -errno on failure. */187int cg_write(const char *cgroup, const char *control, char *buf)188{189char path[PATH_MAX];190ssize_t len = strlen(buf), ret;191192snprintf(path, sizeof(path), "%s/%s", cgroup, control);193ret = write_text(path, buf, len);194return ret == len ? 0 : ret;195}196197/*198* Returns fd on success, or -1 on failure.199* (fd should be closed with close() as usual)200*/201int cg_open(const char *cgroup, const char *control, int flags)202{203char path[PATH_MAX];204205snprintf(path, sizeof(path), "%s/%s", cgroup, control);206return open(path, flags);207}208209int cg_write_numeric(const char *cgroup, const char *control, long value)210{211char buf[64];212int ret;213214ret = sprintf(buf, "%lu", value);215if (ret < 0)216return ret;217218return cg_write(cgroup, control, buf);219}220221static int cg_find_root(char *root, size_t len, const char *controller,222bool *nsdelegate)223{224char buf[10 * PAGE_SIZE];225char *fs, *mount, *type, *options;226const char delim[] = "\n\t ";227228if (read_text("/proc/self/mounts", buf, sizeof(buf)) <= 0)229return -1;230231/*232* Example:233* cgroup /sys/fs/cgroup cgroup2 rw,seclabel,noexec,relatime 0 0234*/235for (fs = strtok(buf, delim); fs; fs = strtok(NULL, delim)) {236mount = strtok(NULL, delim);237type = strtok(NULL, delim);238options = strtok(NULL, delim);239strtok(NULL, delim);240strtok(NULL, delim);241if (strcmp(type, "cgroup") == 0) {242if (!controller || !strstr(options, controller))243continue;244} else if (strcmp(type, "cgroup2") == 0) {245if (controller &&246cg_read_strstr(mount, "cgroup.controllers", controller))247continue;248} else {249continue;250}251strncpy(root, mount, len);252253if (nsdelegate)254*nsdelegate = !!strstr(options, "nsdelegate");255return 0;256257}258259return -1;260}261262int cg_find_controller_root(char *root, size_t len, const char *controller)263{264return cg_find_root(root, len, controller, NULL);265}266267int cg_find_unified_root(char *root, size_t len, bool *nsdelegate)268{269return cg_find_root(root, len, NULL, nsdelegate);270}271272int cg_create(const char *cgroup)273{274return mkdir(cgroup, 0755);275}276277int cg_wait_for_proc_count(const char *cgroup, int count)278{279char buf[10 * PAGE_SIZE] = {0};280int attempts;281char *ptr;282283for (attempts = 10; attempts >= 0; attempts--) {284int nr = 0;285286if (cg_read(cgroup, "cgroup.procs", buf, sizeof(buf)))287break;288289for (ptr = buf; *ptr; ptr++)290if (*ptr == '\n')291nr++;292293if (nr >= count)294return 0;295296usleep(100000);297}298299return -1;300}301302int cg_killall(const char *cgroup)303{304char buf[PAGE_SIZE];305char *ptr = buf;306307/* If cgroup.kill exists use it. */308if (!cg_write(cgroup, "cgroup.kill", "1"))309return 0;310311if (cg_read(cgroup, "cgroup.procs", buf, sizeof(buf)))312return -1;313314while (ptr < buf + sizeof(buf)) {315int pid = strtol(ptr, &ptr, 10);316317if (pid == 0)318break;319if (*ptr)320ptr++;321else322break;323if (kill(pid, SIGKILL))324return -1;325}326327return 0;328}329330int cg_destroy(const char *cgroup)331{332int ret;333334if (!cgroup)335return 0;336retry:337ret = rmdir(cgroup);338if (ret && errno == EBUSY) {339cg_killall(cgroup);340usleep(100);341goto retry;342}343344if (ret && errno == ENOENT)345ret = 0;346347return ret;348}349350int cg_enter(const char *cgroup, int pid)351{352char pidbuf[64];353354snprintf(pidbuf, sizeof(pidbuf), "%d", pid);355return cg_write(cgroup, "cgroup.procs", pidbuf);356}357358int cg_enter_current(const char *cgroup)359{360return cg_write(cgroup, "cgroup.procs", "0");361}362363int cg_enter_current_thread(const char *cgroup)364{365return cg_write(cgroup, CG_THREADS_FILE, "0");366}367368int cg_run(const char *cgroup,369int (*fn)(const char *cgroup, void *arg),370void *arg)371{372int pid, retcode;373374pid = fork();375if (pid < 0) {376return pid;377} else if (pid == 0) {378char buf[64];379380snprintf(buf, sizeof(buf), "%d", getpid());381if (cg_write(cgroup, "cgroup.procs", buf))382exit(EXIT_FAILURE);383exit(fn(cgroup, arg));384} else {385waitpid(pid, &retcode, 0);386if (WIFEXITED(retcode))387return WEXITSTATUS(retcode);388else389return -1;390}391}392393pid_t clone_into_cgroup(int cgroup_fd)394{395#ifdef CLONE_ARGS_SIZE_VER2396pid_t pid;397398struct __clone_args args = {399.flags = CLONE_INTO_CGROUP,400.exit_signal = SIGCHLD,401.cgroup = cgroup_fd,402};403404pid = sys_clone3(&args, sizeof(struct __clone_args));405/*406* Verify that this is a genuine test failure:407* ENOSYS -> clone3() not available408* E2BIG -> CLONE_INTO_CGROUP not available409*/410if (pid < 0 && (errno == ENOSYS || errno == E2BIG))411goto pretend_enosys;412413return pid;414415pretend_enosys:416#endif417errno = ENOSYS;418return -ENOSYS;419}420421int clone_reap(pid_t pid, int options)422{423int ret;424siginfo_t info = {425.si_signo = 0,426};427428again:429ret = waitid(P_PID, pid, &info, options | __WALL | __WNOTHREAD);430if (ret < 0) {431if (errno == EINTR)432goto again;433return -1;434}435436if (options & WEXITED) {437if (WIFEXITED(info.si_status))438return WEXITSTATUS(info.si_status);439}440441if (options & WSTOPPED) {442if (WIFSTOPPED(info.si_status))443return WSTOPSIG(info.si_status);444}445446if (options & WCONTINUED) {447if (WIFCONTINUED(info.si_status))448return 0;449}450451return -1;452}453454int dirfd_open_opath(const char *dir)455{456return open(dir, O_DIRECTORY | O_CLOEXEC | O_NOFOLLOW | O_PATH);457}458459#define close_prot_errno(fd) \460if (fd >= 0) { \461int _e_ = errno; \462close(fd); \463errno = _e_; \464}465466static int clone_into_cgroup_run_nowait(const char *cgroup,467int (*fn)(const char *cgroup, void *arg),468void *arg)469{470int cgroup_fd;471pid_t pid;472473cgroup_fd = dirfd_open_opath(cgroup);474if (cgroup_fd < 0)475return -1;476477pid = clone_into_cgroup(cgroup_fd);478close_prot_errno(cgroup_fd);479if (pid == 0)480exit(fn(cgroup, arg));481482return pid;483}484485int cg_run_nowait(const char *cgroup,486int (*fn)(const char *cgroup, void *arg),487void *arg)488{489int pid;490491pid = clone_into_cgroup_run_nowait(cgroup, fn, arg);492if (pid > 0)493return pid;494495/* Genuine test failure. */496if (pid < 0 && errno != ENOSYS)497return -1;498499pid = fork();500if (pid == 0) {501char buf[64];502503snprintf(buf, sizeof(buf), "%d", getpid());504if (cg_write(cgroup, "cgroup.procs", buf))505exit(EXIT_FAILURE);506exit(fn(cgroup, arg));507}508509return pid;510}511512int proc_mount_contains(const char *option)513{514char buf[4 * PAGE_SIZE];515ssize_t read;516517read = read_text("/proc/mounts", buf, sizeof(buf));518if (read < 0)519return read;520521return strstr(buf, option) != NULL;522}523524ssize_t proc_read_text(int pid, bool thread, const char *item, char *buf, size_t size)525{526char path[PATH_MAX];527ssize_t ret;528529if (!pid)530snprintf(path, sizeof(path), "/proc/%s/%s",531thread ? "thread-self" : "self", item);532else533snprintf(path, sizeof(path), "/proc/%d/%s", pid, item);534535ret = read_text(path, buf, size);536return ret < 0 ? -1 : ret;537}538539int proc_read_strstr(int pid, bool thread, const char *item, const char *needle)540{541char buf[PAGE_SIZE];542543if (proc_read_text(pid, thread, item, buf, sizeof(buf)) < 0)544return -1;545546return strstr(buf, needle) ? 0 : -1;547}548549int clone_into_cgroup_run_wait(const char *cgroup)550{551int cgroup_fd;552pid_t pid;553554cgroup_fd = dirfd_open_opath(cgroup);555if (cgroup_fd < 0)556return -1;557558pid = clone_into_cgroup(cgroup_fd);559close_prot_errno(cgroup_fd);560if (pid < 0)561return -1;562563if (pid == 0)564exit(EXIT_SUCCESS);565566/*567* We don't care whether this fails. We only care whether the initial568* clone succeeded.569*/570(void)clone_reap(pid, WEXITED);571return 0;572}573574static int __prepare_for_wait(const char *cgroup, const char *filename)575{576int fd, ret = -1;577578fd = inotify_init1(0);579if (fd == -1)580return fd;581582ret = inotify_add_watch(fd, cg_control(cgroup, filename), IN_MODIFY);583if (ret == -1) {584close(fd);585fd = -1;586}587588return fd;589}590591int cg_prepare_for_wait(const char *cgroup)592{593return __prepare_for_wait(cgroup, "cgroup.events");594}595596int memcg_prepare_for_wait(const char *cgroup)597{598return __prepare_for_wait(cgroup, "memory.events");599}600601int cg_wait_for(int fd)602{603int ret = -1;604struct pollfd fds = {605.fd = fd,606.events = POLLIN,607};608609while (true) {610ret = poll(&fds, 1, 10000);611612if (ret == -1) {613if (errno == EINTR)614continue;615616break;617}618619if (ret > 0 && fds.revents & POLLIN) {620ret = 0;621break;622}623}624625return ret;626}627628629