684 lines · c
1/* SPDX-License-Identifier: GPL-2.0 */2 3#define _GNU_SOURCE4 5#include <errno.h>6#include <fcntl.h>7#include <linux/limits.h>8#include <poll.h>9#include <signal.h>10#include <stdio.h>11#include <stdlib.h>12#include <string.h>13#include <sys/inotify.h>14#include <sys/stat.h>15#include <sys/types.h>16#include <sys/wait.h>17#include <unistd.h>18 19#include "cgroup_util.h"20#include "../clone3/clone3_selftests.h"21 22/* Returns read len on success, or -errno on failure. */23static ssize_t read_text(const char *path, char *buf, size_t max_len)24{25 ssize_t len;26 int fd;27 28 fd = open(path, O_RDONLY);29 if (fd < 0)30 return -errno;31 32 len = read(fd, buf, max_len - 1);33 34 if (len >= 0)35 buf[len] = 0;36 37 close(fd);38 return len < 0 ? -errno : len;39}40 41/* Returns written len on success, or -errno on failure. */42static ssize_t write_text(const char *path, char *buf, ssize_t len)43{44 int fd;45 46 fd = open(path, O_WRONLY | O_APPEND);47 if (fd < 0)48 return -errno;49 50 len = write(fd, buf, len);51 close(fd);52 return len < 0 ? -errno : len;53}54 55char *cg_name(const char *root, const char *name)56{57 size_t len = strlen(root) + strlen(name) + 2;58 char *ret = malloc(len);59 60 snprintf(ret, len, "%s/%s", root, name);61 62 return ret;63}64 65char *cg_name_indexed(const char *root, const char *name, int index)66{67 size_t len = strlen(root) + strlen(name) + 10;68 char *ret = malloc(len);69 70 snprintf(ret, len, "%s/%s_%d", root, name, index);71 72 return ret;73}74 75char *cg_control(const char *cgroup, const char *control)76{77 size_t len = strlen(cgroup) + strlen(control) + 2;78 char *ret = malloc(len);79 80 snprintf(ret, len, "%s/%s", cgroup, control);81 82 return ret;83}84 85/* Returns 0 on success, or -errno on failure. */86int cg_read(const char *cgroup, const char *control, char *buf, size_t len)87{88 char path[PATH_MAX];89 ssize_t ret;90 91 snprintf(path, sizeof(path), "%s/%s", cgroup, control);92 93 ret = read_text(path, buf, len);94 return ret >= 0 ? 0 : ret;95}96 97int cg_read_strcmp(const char *cgroup, const char *control,98 const char *expected)99{100 size_t size;101 char *buf;102 int ret;103 104 /* Handle the case of comparing against empty string */105 if (!expected)106 return -1;107 else108 size = strlen(expected) + 1;109 110 buf = malloc(size);111 if (!buf)112 return -1;113 114 if (cg_read(cgroup, control, buf, size)) {115 free(buf);116 return -1;117 }118 119 ret = strcmp(expected, buf);120 free(buf);121 return ret;122}123 124int cg_read_strstr(const char *cgroup, const char *control, const char *needle)125{126 char buf[PAGE_SIZE];127 128 if (cg_read(cgroup, control, buf, sizeof(buf)))129 return -1;130 131 return strstr(buf, needle) ? 0 : -1;132}133 134long cg_read_long(const char *cgroup, const char *control)135{136 char buf[128];137 138 if (cg_read(cgroup, control, buf, sizeof(buf)))139 return -1;140 141 return atol(buf);142}143 144long cg_read_long_fd(int fd)145{146 char buf[128];147 148 if (pread(fd, buf, sizeof(buf), 0) <= 0)149 return -1;150 151 return atol(buf);152}153 154long cg_read_key_long(const char *cgroup, const char *control, const char *key)155{156 char buf[PAGE_SIZE];157 char *ptr;158 159 if (cg_read(cgroup, control, buf, sizeof(buf)))160 return -1;161 162 ptr = strstr(buf, key);163 if (!ptr)164 return -1;165 166 return atol(ptr + strlen(key));167}168 169long cg_read_lc(const char *cgroup, const char *control)170{171 char buf[PAGE_SIZE];172 const char delim[] = "\n";173 char *line;174 long cnt = 0;175 176 if (cg_read(cgroup, control, buf, sizeof(buf)))177 return -1;178 179 for (line = strtok(buf, delim); line; line = strtok(NULL, delim))180 cnt++;181 182 return cnt;183}184 185/* Returns 0 on success, or -errno on failure. */186int cg_write(const char *cgroup, const char *control, char *buf)187{188 char path[PATH_MAX];189 ssize_t len = strlen(buf), ret;190 191 snprintf(path, sizeof(path), "%s/%s", cgroup, control);192 ret = write_text(path, buf, len);193 return ret == len ? 0 : ret;194}195 196/*197 * Returns fd on success, or -1 on failure.198 * (fd should be closed with close() as usual)199 */200int cg_open(const char *cgroup, const char *control, int flags)201{202 char path[PATH_MAX];203 204 snprintf(path, sizeof(path), "%s/%s", cgroup, control);205 return open(path, flags);206}207 208int cg_write_numeric(const char *cgroup, const char *control, long value)209{210 char buf[64];211 int ret;212 213 ret = sprintf(buf, "%lu", value);214 if (ret < 0)215 return ret;216 217 return cg_write(cgroup, control, buf);218}219 220int cg_find_unified_root(char *root, size_t len, bool *nsdelegate)221{222 char buf[10 * PAGE_SIZE];223 char *fs, *mount, *type, *options;224 const char delim[] = "\n\t ";225 226 if (read_text("/proc/self/mounts", buf, sizeof(buf)) <= 0)227 return -1;228 229 /*230 * Example:231 * cgroup /sys/fs/cgroup cgroup2 rw,seclabel,noexec,relatime 0 0232 */233 for (fs = strtok(buf, delim); fs; fs = strtok(NULL, delim)) {234 mount = strtok(NULL, delim);235 type = strtok(NULL, delim);236 options = strtok(NULL, delim);237 strtok(NULL, delim);238 strtok(NULL, delim);239 240 if (strcmp(type, "cgroup2") == 0) {241 strncpy(root, mount, len);242 if (nsdelegate)243 *nsdelegate = !!strstr(options, "nsdelegate");244 return 0;245 }246 }247 248 return -1;249}250 251int cg_create(const char *cgroup)252{253 return mkdir(cgroup, 0755);254}255 256int cg_wait_for_proc_count(const char *cgroup, int count)257{258 char buf[10 * PAGE_SIZE] = {0};259 int attempts;260 char *ptr;261 262 for (attempts = 10; attempts >= 0; attempts--) {263 int nr = 0;264 265 if (cg_read(cgroup, "cgroup.procs", buf, sizeof(buf)))266 break;267 268 for (ptr = buf; *ptr; ptr++)269 if (*ptr == '\n')270 nr++;271 272 if (nr >= count)273 return 0;274 275 usleep(100000);276 }277 278 return -1;279}280 281int cg_killall(const char *cgroup)282{283 char buf[PAGE_SIZE];284 char *ptr = buf;285 286 /* If cgroup.kill exists use it. */287 if (!cg_write(cgroup, "cgroup.kill", "1"))288 return 0;289 290 if (cg_read(cgroup, "cgroup.procs", buf, sizeof(buf)))291 return -1;292 293 while (ptr < buf + sizeof(buf)) {294 int pid = strtol(ptr, &ptr, 10);295 296 if (pid == 0)297 break;298 if (*ptr)299 ptr++;300 else301 break;302 if (kill(pid, SIGKILL))303 return -1;304 }305 306 return 0;307}308 309int cg_destroy(const char *cgroup)310{311 int ret;312 313 if (!cgroup)314 return 0;315retry:316 ret = rmdir(cgroup);317 if (ret && errno == EBUSY) {318 cg_killall(cgroup);319 usleep(100);320 goto retry;321 }322 323 if (ret && errno == ENOENT)324 ret = 0;325 326 return ret;327}328 329int cg_enter(const char *cgroup, int pid)330{331 char pidbuf[64];332 333 snprintf(pidbuf, sizeof(pidbuf), "%d", pid);334 return cg_write(cgroup, "cgroup.procs", pidbuf);335}336 337int cg_enter_current(const char *cgroup)338{339 return cg_write(cgroup, "cgroup.procs", "0");340}341 342int cg_enter_current_thread(const char *cgroup)343{344 return cg_write(cgroup, "cgroup.threads", "0");345}346 347int cg_run(const char *cgroup,348 int (*fn)(const char *cgroup, void *arg),349 void *arg)350{351 int pid, retcode;352 353 pid = fork();354 if (pid < 0) {355 return pid;356 } else if (pid == 0) {357 char buf[64];358 359 snprintf(buf, sizeof(buf), "%d", getpid());360 if (cg_write(cgroup, "cgroup.procs", buf))361 exit(EXIT_FAILURE);362 exit(fn(cgroup, arg));363 } else {364 waitpid(pid, &retcode, 0);365 if (WIFEXITED(retcode))366 return WEXITSTATUS(retcode);367 else368 return -1;369 }370}371 372pid_t clone_into_cgroup(int cgroup_fd)373{374#ifdef CLONE_ARGS_SIZE_VER2375 pid_t pid;376 377 struct __clone_args args = {378 .flags = CLONE_INTO_CGROUP,379 .exit_signal = SIGCHLD,380 .cgroup = cgroup_fd,381 };382 383 pid = sys_clone3(&args, sizeof(struct __clone_args));384 /*385 * Verify that this is a genuine test failure:386 * ENOSYS -> clone3() not available387 * E2BIG -> CLONE_INTO_CGROUP not available388 */389 if (pid < 0 && (errno == ENOSYS || errno == E2BIG))390 goto pretend_enosys;391 392 return pid;393 394pretend_enosys:395#endif396 errno = ENOSYS;397 return -ENOSYS;398}399 400int clone_reap(pid_t pid, int options)401{402 int ret;403 siginfo_t info = {404 .si_signo = 0,405 };406 407again:408 ret = waitid(P_PID, pid, &info, options | __WALL | __WNOTHREAD);409 if (ret < 0) {410 if (errno == EINTR)411 goto again;412 return -1;413 }414 415 if (options & WEXITED) {416 if (WIFEXITED(info.si_status))417 return WEXITSTATUS(info.si_status);418 }419 420 if (options & WSTOPPED) {421 if (WIFSTOPPED(info.si_status))422 return WSTOPSIG(info.si_status);423 }424 425 if (options & WCONTINUED) {426 if (WIFCONTINUED(info.si_status))427 return 0;428 }429 430 return -1;431}432 433int dirfd_open_opath(const char *dir)434{435 return open(dir, O_DIRECTORY | O_CLOEXEC | O_NOFOLLOW | O_PATH);436}437 438#define close_prot_errno(fd) \439 if (fd >= 0) { \440 int _e_ = errno; \441 close(fd); \442 errno = _e_; \443 }444 445static int clone_into_cgroup_run_nowait(const char *cgroup,446 int (*fn)(const char *cgroup, void *arg),447 void *arg)448{449 int cgroup_fd;450 pid_t pid;451 452 cgroup_fd = dirfd_open_opath(cgroup);453 if (cgroup_fd < 0)454 return -1;455 456 pid = clone_into_cgroup(cgroup_fd);457 close_prot_errno(cgroup_fd);458 if (pid == 0)459 exit(fn(cgroup, arg));460 461 return pid;462}463 464int cg_run_nowait(const char *cgroup,465 int (*fn)(const char *cgroup, void *arg),466 void *arg)467{468 int pid;469 470 pid = clone_into_cgroup_run_nowait(cgroup, fn, arg);471 if (pid > 0)472 return pid;473 474 /* Genuine test failure. */475 if (pid < 0 && errno != ENOSYS)476 return -1;477 478 pid = fork();479 if (pid == 0) {480 char buf[64];481 482 snprintf(buf, sizeof(buf), "%d", getpid());483 if (cg_write(cgroup, "cgroup.procs", buf))484 exit(EXIT_FAILURE);485 exit(fn(cgroup, arg));486 }487 488 return pid;489}490 491int get_temp_fd(void)492{493 return open(".", O_TMPFILE | O_RDWR | O_EXCL);494}495 496int alloc_pagecache(int fd, size_t size)497{498 char buf[PAGE_SIZE];499 struct stat st;500 int i;501 502 if (fstat(fd, &st))503 goto cleanup;504 505 size += st.st_size;506 507 if (ftruncate(fd, size))508 goto cleanup;509 510 for (i = 0; i < size; i += sizeof(buf))511 read(fd, buf, sizeof(buf));512 513 return 0;514 515cleanup:516 return -1;517}518 519int alloc_anon(const char *cgroup, void *arg)520{521 size_t size = (unsigned long)arg;522 char *buf, *ptr;523 524 buf = malloc(size);525 for (ptr = buf; ptr < buf + size; ptr += PAGE_SIZE)526 *ptr = 0;527 528 free(buf);529 return 0;530}531 532int is_swap_enabled(void)533{534 char buf[PAGE_SIZE];535 const char delim[] = "\n";536 int cnt = 0;537 char *line;538 539 if (read_text("/proc/swaps", buf, sizeof(buf)) <= 0)540 return -1;541 542 for (line = strtok(buf, delim); line; line = strtok(NULL, delim))543 cnt++;544 545 return cnt > 1;546}547 548int set_oom_adj_score(int pid, int score)549{550 char path[PATH_MAX];551 int fd, len;552 553 sprintf(path, "/proc/%d/oom_score_adj", pid);554 555 fd = open(path, O_WRONLY | O_APPEND);556 if (fd < 0)557 return fd;558 559 len = dprintf(fd, "%d", score);560 if (len < 0) {561 close(fd);562 return len;563 }564 565 close(fd);566 return 0;567}568 569int proc_mount_contains(const char *option)570{571 char buf[4 * PAGE_SIZE];572 ssize_t read;573 574 read = read_text("/proc/mounts", buf, sizeof(buf));575 if (read < 0)576 return read;577 578 return strstr(buf, option) != NULL;579}580 581ssize_t proc_read_text(int pid, bool thread, const char *item, char *buf, size_t size)582{583 char path[PATH_MAX];584 ssize_t ret;585 586 if (!pid)587 snprintf(path, sizeof(path), "/proc/%s/%s",588 thread ? "thread-self" : "self", item);589 else590 snprintf(path, sizeof(path), "/proc/%d/%s", pid, item);591 592 ret = read_text(path, buf, size);593 return ret < 0 ? -1 : ret;594}595 596int proc_read_strstr(int pid, bool thread, const char *item, const char *needle)597{598 char buf[PAGE_SIZE];599 600 if (proc_read_text(pid, thread, item, buf, sizeof(buf)) < 0)601 return -1;602 603 return strstr(buf, needle) ? 0 : -1;604}605 606int clone_into_cgroup_run_wait(const char *cgroup)607{608 int cgroup_fd;609 pid_t pid;610 611 cgroup_fd = dirfd_open_opath(cgroup);612 if (cgroup_fd < 0)613 return -1;614 615 pid = clone_into_cgroup(cgroup_fd);616 close_prot_errno(cgroup_fd);617 if (pid < 0)618 return -1;619 620 if (pid == 0)621 exit(EXIT_SUCCESS);622 623 /*624 * We don't care whether this fails. We only care whether the initial625 * clone succeeded.626 */627 (void)clone_reap(pid, WEXITED);628 return 0;629}630 631static int __prepare_for_wait(const char *cgroup, const char *filename)632{633 int fd, ret = -1;634 635 fd = inotify_init1(0);636 if (fd == -1)637 return fd;638 639 ret = inotify_add_watch(fd, cg_control(cgroup, filename), IN_MODIFY);640 if (ret == -1) {641 close(fd);642 fd = -1;643 }644 645 return fd;646}647 648int cg_prepare_for_wait(const char *cgroup)649{650 return __prepare_for_wait(cgroup, "cgroup.events");651}652 653int memcg_prepare_for_wait(const char *cgroup)654{655 return __prepare_for_wait(cgroup, "memory.events");656}657 658int cg_wait_for(int fd)659{660 int ret = -1;661 struct pollfd fds = {662 .fd = fd,663 .events = POLLIN,664 };665 666 while (true) {667 ret = poll(&fds, 1, 10000);668 669 if (ret == -1) {670 if (errno == EINTR)671 continue;672 673 break;674 }675 676 if (ret > 0 && fds.revents & POLLIN) {677 ret = 0;678 break;679 }680 }681 682 return ret;683}684