| // SPDX-License-Identifier: GPL-2.0-or-later |
| /* |
| * Copyright (c) 2020 Red Hat, Inc. |
| * Copyright (c) 2020 Li Wang <liwang@redhat.com> |
| */ |
| |
| #define TST_NO_DEFAULT_MAIN |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <sys/mount.h> |
| |
| #include "tst_test.h" |
| #include "tst_safe_macros.h" |
| #include "tst_safe_stdio.h" |
| #include "tst_cgroup.h" |
| #include "tst_device.h" |
| |
| static enum tst_cgroup_ver tst_cg_ver; |
| |
| static int tst_cgroup_check(const char *cgroup) |
| { |
| char line[PATH_MAX]; |
| FILE *file; |
| int cg_check = 0; |
| |
| file = SAFE_FOPEN("/proc/filesystems", "r"); |
| while (fgets(line, sizeof(line), file)) { |
| if (strstr(line, cgroup) != NULL) { |
| cg_check = 1; |
| break; |
| } |
| } |
| SAFE_FCLOSE(file); |
| |
| return cg_check; |
| } |
| |
| enum tst_cgroup_ver tst_cgroup_version(void) |
| { |
| if (tst_cgroup_check("cgroup2")) { |
| if (!tst_is_mounted("cgroup2") && tst_is_mounted("cgroup")) |
| return TST_CGROUP_V1; |
| else |
| return TST_CGROUP_V2; |
| } |
| |
| if (tst_cgroup_check("cgroup")) |
| return TST_CGROUP_V1; |
| |
| tst_brk(TCONF, "Cgroup is not configured"); |
| } |
| |
| static void tst_cgroup1_mount(const char *name, const char *option, |
| const char *mnt_path, const char *new_path) |
| { |
| char knob_path[PATH_MAX]; |
| if (tst_is_mounted(mnt_path)) |
| goto out; |
| |
| SAFE_MKDIR(mnt_path, 0777); |
| if (mount(name, mnt_path, "cgroup", 0, option) == -1) { |
| if (errno == ENODEV) { |
| if (rmdir(mnt_path) == -1) |
| tst_res(TWARN | TERRNO, "rmdir %s failed", mnt_path); |
| tst_brk(TCONF, |
| "Cgroup v1 is not configured in kernel"); |
| } |
| tst_brk(TBROK | TERRNO, "mount %s", mnt_path); |
| } |
| |
| /* |
| * We should assign one or more memory nodes to cpuset.mems and |
| * cpuset.cpus, otherwise, echo $$ > tasks gives “ENOSPC: no space |
| * left on device” when trying to use cpuset. |
| * |
| * Or, setting cgroup.clone_children to 1 can help in automatically |
| * inheriting memory and node setting from parent cgroup when a |
| * child cgroup is created. |
| */ |
| if (strcmp(option, "cpuset") == 0) { |
| sprintf(knob_path, "%s/cgroup.clone_children", mnt_path); |
| SAFE_FILE_PRINTF(knob_path, "%d", 1); |
| } |
| out: |
| SAFE_MKDIR(new_path, 0777); |
| |
| tst_res(TINFO, "Cgroup(%s) v1 mount at %s success", option, mnt_path); |
| } |
| |
| static void tst_cgroup2_mount(const char *mnt_path, const char *new_path) |
| { |
| if (tst_is_mounted(mnt_path)) |
| goto out; |
| |
| SAFE_MKDIR(mnt_path, 0777); |
| if (mount("cgroup2", mnt_path, "cgroup2", 0, NULL) == -1) { |
| if (errno == ENODEV) { |
| if (rmdir(mnt_path) == -1) |
| tst_res(TWARN | TERRNO, "rmdir %s failed", mnt_path); |
| tst_brk(TCONF, |
| "Cgroup v2 is not configured in kernel"); |
| } |
| tst_brk(TBROK | TERRNO, "mount %s", mnt_path); |
| } |
| |
| out: |
| SAFE_MKDIR(new_path, 0777); |
| |
| tst_res(TINFO, "Cgroup v2 mount at %s success", mnt_path); |
| } |
| |
| static void tst_cgroupN_umount(const char *mnt_path, const char *new_path) |
| { |
| FILE *fp; |
| int fd; |
| char s_new[BUFSIZ], s[BUFSIZ], value[BUFSIZ]; |
| |
| if (!tst_is_mounted(mnt_path)) |
| return; |
| |
| /* Move all processes in task(v2: cgroup.procs) to its parent node. */ |
| if (tst_cg_ver & TST_CGROUP_V1) |
| sprintf(s, "%s/tasks", mnt_path); |
| if (tst_cg_ver & TST_CGROUP_V2) |
| sprintf(s, "%s/cgroup.procs", mnt_path); |
| |
| fd = open(s, O_WRONLY); |
| if (fd == -1) |
| tst_res(TWARN | TERRNO, "open %s", s); |
| |
| if (tst_cg_ver & TST_CGROUP_V1) |
| snprintf(s_new, BUFSIZ, "%s/tasks", new_path); |
| if (tst_cg_ver & TST_CGROUP_V2) |
| snprintf(s_new, BUFSIZ, "%s/cgroup.procs", new_path); |
| |
| fp = fopen(s_new, "r"); |
| if (fp == NULL) |
| tst_res(TWARN | TERRNO, "fopen %s", s_new); |
| if ((fd != -1) && (fp != NULL)) { |
| while (fgets(value, BUFSIZ, fp) != NULL) |
| if (write(fd, value, strlen(value) - 1) |
| != (ssize_t)strlen(value) - 1) |
| tst_res(TWARN | TERRNO, "write %s", s); |
| } |
| if (fd != -1) |
| close(fd); |
| if (fp != NULL) |
| fclose(fp); |
| if (rmdir(new_path) == -1) |
| tst_res(TWARN | TERRNO, "rmdir %s", new_path); |
| if (umount(mnt_path) == -1) |
| tst_res(TWARN | TERRNO, "umount %s", mnt_path); |
| if (rmdir(mnt_path) == -1) |
| tst_res(TWARN | TERRNO, "rmdir %s", mnt_path); |
| |
| if (tst_cg_ver & TST_CGROUP_V1) |
| tst_res(TINFO, "Cgroup v1 unmount success"); |
| if (tst_cg_ver & TST_CGROUP_V2) |
| tst_res(TINFO, "Cgroup v2 unmount success"); |
| } |
| |
| struct tst_cgroup_path { |
| char *mnt_path; |
| char *new_path; |
| struct tst_cgroup_path *next; |
| }; |
| |
| static struct tst_cgroup_path *tst_cgroup_paths; |
| |
| static void tst_cgroup_set_path(const char *cgroup_dir) |
| { |
| char cgroup_new_dir[PATH_MAX]; |
| struct tst_cgroup_path *tst_cgroup_path, *a; |
| |
| if (!cgroup_dir) |
| tst_brk(TBROK, "Invalid cgroup dir, plese check cgroup_dir"); |
| |
| sprintf(cgroup_new_dir, "%s/ltp_%d", cgroup_dir, rand()); |
| |
| /* To store cgroup path in the 'path' list */ |
| tst_cgroup_path = SAFE_MALLOC(sizeof(struct tst_cgroup_path)); |
| tst_cgroup_path->mnt_path = SAFE_MALLOC(strlen(cgroup_dir) + 1); |
| tst_cgroup_path->new_path = SAFE_MALLOC(strlen(cgroup_new_dir) + 1); |
| |
| if (!tst_cgroup_paths) { |
| tst_cgroup_paths = tst_cgroup_path; |
| } else { |
| a = tst_cgroup_paths; |
| do { |
| if (!a->next) { |
| a->next = tst_cgroup_path; |
| break; |
| } |
| a = a->next; |
| } while (a); |
| } |
| |
| sprintf(tst_cgroup_path->mnt_path, "%s", cgroup_dir); |
| sprintf(tst_cgroup_path->new_path, "%s", cgroup_new_dir); |
| } |
| |
| static char *tst_cgroup_get_path(const char *cgroup_dir) |
| { |
| struct tst_cgroup_path *a; |
| |
| if (!tst_cgroup_paths) |
| return NULL; |
| |
| a = tst_cgroup_paths; |
| |
| while (strcmp(a->mnt_path, cgroup_dir) != 0){ |
| if (!a->next) { |
| tst_res(TINFO, "%s is not found", cgroup_dir); |
| return NULL; |
| } |
| a = a->next; |
| }; |
| |
| return a->new_path; |
| } |
| |
| static void tst_cgroup_del_path(const char *cgroup_dir) |
| { |
| struct tst_cgroup_path *a, *b; |
| |
| if (tst_cgroup_paths) |
| return; |
| |
| a = b = tst_cgroup_paths; |
| |
| while (strcmp(b->mnt_path, cgroup_dir) != 0) { |
| if (!b->next) { |
| tst_res(TINFO, "%s is not found", cgroup_dir); |
| return; |
| } |
| a = b; |
| b = b->next; |
| }; |
| |
| if (b == tst_cgroup_paths) |
| tst_cgroup_paths = b->next; |
| else |
| a->next = b->next; |
| |
| free(b->mnt_path); |
| free(b->new_path); |
| free(b); |
| } |
| |
| void tst_cgroup_mount(enum tst_cgroup_ctrl ctrl, const char *cgroup_dir) |
| { |
| char *cgroup_new_dir; |
| char knob_path[PATH_MAX]; |
| |
| tst_cg_ver = tst_cgroup_version(); |
| |
| tst_cgroup_set_path(cgroup_dir); |
| cgroup_new_dir = tst_cgroup_get_path(cgroup_dir); |
| |
| if (tst_cg_ver & TST_CGROUP_V1) { |
| switch(ctrl) { |
| case TST_CGROUP_MEMCG: |
| tst_cgroup1_mount("memcg", "memory", cgroup_dir, cgroup_new_dir); |
| break; |
| case TST_CGROUP_CPUSET: |
| tst_cgroup1_mount("cpusetcg", "cpuset", cgroup_dir, cgroup_new_dir); |
| break; |
| default: |
| tst_brk(TBROK, "Invalid cgroup controller: %d", ctrl); |
| } |
| } |
| |
| if (tst_cg_ver & TST_CGROUP_V2) { |
| tst_cgroup2_mount(cgroup_dir, cgroup_new_dir); |
| |
| switch(ctrl) { |
| case TST_CGROUP_MEMCG: |
| sprintf(knob_path, "%s/cgroup.subtree_control", cgroup_dir); |
| SAFE_FILE_PRINTF(knob_path, "%s", "+memory"); |
| break; |
| case TST_CGROUP_CPUSET: |
| tst_brk(TCONF, "Cgroup v2 hasn't achieve cpuset subsystem"); |
| break; |
| default: |
| tst_brk(TBROK, "Invalid cgroup controller: %d", ctrl); |
| } |
| } |
| } |
| |
| void tst_cgroup_umount(const char *cgroup_dir) |
| { |
| char *cgroup_new_dir; |
| |
| cgroup_new_dir = tst_cgroup_get_path(cgroup_dir); |
| tst_cgroupN_umount(cgroup_dir, cgroup_new_dir); |
| tst_cgroup_del_path(cgroup_dir); |
| } |
| |
| void tst_cgroup_set_knob(const char *cgroup_dir, const char *knob, long value) |
| { |
| char *cgroup_new_dir; |
| char knob_path[PATH_MAX]; |
| |
| cgroup_new_dir = tst_cgroup_get_path(cgroup_dir); |
| sprintf(knob_path, "%s/%s", cgroup_new_dir, knob); |
| SAFE_FILE_PRINTF(knob_path, "%ld", value); |
| } |
| |
| void tst_cgroup_move_current(const char *cgroup_dir) |
| { |
| if (tst_cg_ver & TST_CGROUP_V1) |
| tst_cgroup_set_knob(cgroup_dir, "tasks", getpid()); |
| |
| if (tst_cg_ver & TST_CGROUP_V2) |
| tst_cgroup_set_knob(cgroup_dir, "cgroup.procs", getpid()); |
| } |
| |
| void tst_cgroup_mem_set_maxbytes(const char *cgroup_dir, long memsz) |
| { |
| if (tst_cg_ver & TST_CGROUP_V1) |
| tst_cgroup_set_knob(cgroup_dir, "memory.limit_in_bytes", memsz); |
| |
| if (tst_cg_ver & TST_CGROUP_V2) |
| tst_cgroup_set_knob(cgroup_dir, "memory.max", memsz); |
| } |
| |
| int tst_cgroup_mem_swapacct_enabled(const char *cgroup_dir) |
| { |
| char *cgroup_new_dir; |
| char knob_path[PATH_MAX]; |
| |
| cgroup_new_dir = tst_cgroup_get_path(cgroup_dir); |
| |
| if (tst_cg_ver & TST_CGROUP_V1) { |
| sprintf(knob_path, "%s/%s", |
| cgroup_new_dir, "/memory.memsw.limit_in_bytes"); |
| |
| if ((access(knob_path, F_OK) == -1)) { |
| if (errno == ENOENT) |
| tst_res(TCONF, "memcg swap accounting is disabled"); |
| else |
| tst_brk(TBROK | TERRNO, "failed to access %s", knob_path); |
| } else { |
| return 1; |
| } |
| } |
| |
| if (tst_cg_ver & TST_CGROUP_V2) { |
| sprintf(knob_path, "%s/%s", |
| cgroup_new_dir, "/memory.swap.max"); |
| |
| if ((access(knob_path, F_OK) == -1)) { |
| if (errno == ENOENT) |
| tst_res(TCONF, "memcg swap accounting is disabled"); |
| else |
| tst_brk(TBROK | TERRNO, "failed to access %s", knob_path); |
| } else { |
| return 1; |
| } |
| } |
| |
| return 0; |
| } |
| |
| void tst_cgroup_mem_set_maxswap(const char *cgroup_dir, long memsz) |
| { |
| if (tst_cg_ver & TST_CGROUP_V1) |
| tst_cgroup_set_knob(cgroup_dir, "memory.memsw.limit_in_bytes", memsz); |
| |
| if (tst_cg_ver & TST_CGROUP_V2) |
| tst_cgroup_set_knob(cgroup_dir, "memory.swap.max", memsz); |
| } |
| |
| void tst_cgroup_cpuset_read_files(const char *cgroup_dir, const char *filename, char *retbuf) |
| { |
| int fd; |
| char *cgroup_new_dir; |
| char knob_path[PATH_MAX]; |
| |
| cgroup_new_dir = tst_cgroup_get_path(cgroup_dir); |
| |
| /* |
| * try either '/dev/cpuset/XXXX' or '/dev/cpuset/cpuset.XXXX' |
| * please see Documentation/cgroups/cpusets.txt from kernel src |
| * for details |
| */ |
| sprintf(knob_path, "%s/%s", cgroup_new_dir, filename); |
| fd = open(knob_path, O_RDONLY); |
| if (fd == -1) { |
| if (errno == ENOENT) { |
| sprintf(knob_path, "%s/cpuset.%s", |
| cgroup_new_dir, filename); |
| fd = SAFE_OPEN(knob_path, O_RDONLY); |
| } else |
| tst_brk(TBROK | TERRNO, "open %s", knob_path); |
| } |
| |
| if (read(fd, retbuf, sizeof(retbuf)) < 0) |
| tst_brk(TBROK | TERRNO, "read %s", knob_path); |
| |
| close(fd); |
| } |
| |
| void tst_cgroup_cpuset_write_files(const char *cgroup_dir, const char *filename, const char *buf) |
| { |
| int fd; |
| char *cgroup_new_dir; |
| char knob_path[PATH_MAX]; |
| |
| cgroup_new_dir = tst_cgroup_get_path(cgroup_dir); |
| |
| /* |
| * try either '/dev/cpuset/XXXX' or '/dev/cpuset/cpuset.XXXX' |
| * please see Documentation/cgroups/cpusets.txt from kernel src |
| * for details |
| */ |
| sprintf(knob_path, "%s/%s", cgroup_new_dir, filename); |
| fd = open(knob_path, O_WRONLY); |
| if (fd == -1) { |
| if (errno == ENOENT) { |
| sprintf(knob_path, "%s/cpuset.%s", cgroup_new_dir, filename); |
| fd = SAFE_OPEN(knob_path, O_WRONLY); |
| } else |
| tst_brk(TBROK | TERRNO, "open %s", knob_path); |
| } |
| |
| SAFE_WRITE(1, fd, buf, strlen(buf)); |
| |
| close(fd); |
| } |