951 lines · 19.2 KB · C
RawDownload
/* SPDX-License-Identifier: GPL-2.0 */ #define _GNU_SOURCE#include <linux/limits.h>#include <linux/sched.h>#include <sys/types.h>#include <sys/mman.h>#include <sys/mount.h>#include <sys/stat.h>#include <sys/wait.h>#include <unistd.h>#include <fcntl.h>#include <sched.h>#include <stdio.h>#include <errno.h>#include <signal.h>#include <string.h>#include <pthread.h> #include "kselftest.h"#include "cgroup_util.h" static bool nsdelegate;#ifndef CLONE_NEWCGROUP#define CLONE_NEWCGROUP 0#endif static int touch_anon(char *buf, size_t size){	int fd;	char *pos = buf; 	fd = open("/dev/urandom", O_RDONLY);	if (fd < 0)		return -1; 	while (size > 0) {		ssize_t ret = read(fd, pos, size); 		if (ret < 0) {			if (errno != EINTR) {				close(fd);				return -1;			}		} else {			pos += ret;			size -= ret;		}	}	close(fd); 	return 0;} static int alloc_and_touch_anon_noexit(const char *cgroup, void *arg){	int ppid = getppid();	size_t size = (size_t)arg;	void *buf; 	buf = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON,		   0, 0);	if (buf == MAP_FAILED)		return -1; 	if (touch_anon((char *)buf, size)) {		munmap(buf, size);		return -1;	} 	while (getppid() == ppid)		sleep(1); 	munmap(buf, size);	return 0;} /* * Create a child process that allocates and touches 100MB, then waits to be * killed. Wait until the child is attached to the cgroup, kill all processes * in that cgroup and wait until "cgroup.procs" is empty. At this point try to * destroy the empty cgroup. The test helps detect race conditions between * dying processes leaving the cgroup and cgroup destruction path. */static int test_cgcore_destroy(const char *root){	int ret = KSFT_FAIL;	char *cg_test = NULL;	int child_pid;	char buf[BUF_SIZE]; 	cg_test = cg_name(root, "cg_test"); 	if (!cg_test)		goto cleanup; 	for (int i = 0; i < 10; i++) {		if (cg_create(cg_test))			goto cleanup; 		child_pid = cg_run_nowait(cg_test, alloc_and_touch_anon_noexit,					  (void *) MB(100)); 		if (child_pid < 0)			goto cleanup; 		/* wait for the child to enter cgroup */		if (cg_wait_for_proc_count(cg_test, 1))			goto cleanup; 		if (cg_killall(cg_test))			goto cleanup; 		/* wait for cgroup to be empty */		while (1) {			if (cg_read(cg_test, "cgroup.procs", buf, sizeof(buf)))				goto cleanup;			if (buf[0] == '\0')				break;			usleep(1000);		} 		if (rmdir(cg_test))			goto cleanup; 		if (waitpid(child_pid, NULL, 0) < 0)			goto cleanup;	}	ret = KSFT_PASS;cleanup:	if (cg_test)		cg_destroy(cg_test);	free(cg_test);	return ret;} /* * A(0) - B(0) - C(1) *        \ D(0) * * A, B and C's "populated" fields would be 1 while D's 0. * test that after the one process in C is moved to root, * A,B and C's "populated" fields would flip to "0" and file * modified events will be generated on the * "cgroup.events" files of both cgroups. */static int test_cgcore_populated(const char *root){	int ret = KSFT_FAIL;	int err;	char *cg_test_a = NULL, *cg_test_b = NULL;	char *cg_test_c = NULL, *cg_test_d = NULL;	int cgroup_fd = -EBADF;	pid_t pid; 	if (cg_test_v1_named)		return KSFT_SKIP; 	cg_test_a = cg_name(root, "cg_test_a");	cg_test_b = cg_name(root, "cg_test_a/cg_test_b");	cg_test_c = cg_name(root, "cg_test_a/cg_test_b/cg_test_c");	cg_test_d = cg_name(root, "cg_test_a/cg_test_b/cg_test_d"); 	if (!cg_test_a || !cg_test_b || !cg_test_c || !cg_test_d)		goto cleanup; 	if (cg_create(cg_test_a))		goto cleanup; 	if (cg_create(cg_test_b))		goto cleanup; 	if (cg_create(cg_test_c))		goto cleanup; 	if (cg_create(cg_test_d))		goto cleanup; 	if (cg_enter_current(cg_test_c))		goto cleanup; 	if (cg_read_strcmp(cg_test_a, "cgroup.events", "populated 1\n"))		goto cleanup; 	if (cg_read_strcmp(cg_test_b, "cgroup.events", "populated 1\n"))		goto cleanup; 	if (cg_read_strcmp(cg_test_c, "cgroup.events", "populated 1\n"))		goto cleanup; 	if (cg_read_strcmp(cg_test_d, "cgroup.events", "populated 0\n"))		goto cleanup; 	if (cg_enter_current(root))		goto cleanup; 	if (cg_read_strcmp(cg_test_a, "cgroup.events", "populated 0\n"))		goto cleanup; 	if (cg_read_strcmp(cg_test_b, "cgroup.events", "populated 0\n"))		goto cleanup; 	if (cg_read_strcmp(cg_test_c, "cgroup.events", "populated 0\n"))		goto cleanup; 	if (cg_read_strcmp(cg_test_d, "cgroup.events", "populated 0\n"))		goto cleanup; 	/* Test that we can directly clone into a new cgroup. */	cgroup_fd = dirfd_open_opath(cg_test_d);	if (cgroup_fd < 0)		goto cleanup; 	pid = clone_into_cgroup(cgroup_fd);	if (pid < 0) {		if (errno == ENOSYS)			goto cleanup_pass;		goto cleanup;	} 	if (pid == 0) {		if (raise(SIGSTOP))			exit(EXIT_FAILURE);		exit(EXIT_SUCCESS);	} 	err = cg_read_strcmp(cg_test_d, "cgroup.events", "populated 1\n"); 	(void)clone_reap(pid, WSTOPPED);	(void)kill(pid, SIGCONT);	(void)clone_reap(pid, WEXITED); 	if (err)		goto cleanup; 	if (cg_read_strcmp_wait(cg_test_d, "cgroup.events",				   "populated 0\n"))		goto cleanup; 	/* Remove cgroup. */	if (cg_test_d) {		cg_destroy(cg_test_d);		free(cg_test_d);		cg_test_d = NULL;	} 	pid = clone_into_cgroup(cgroup_fd);	if (pid < 0)		goto cleanup_pass;	if (pid == 0)		exit(EXIT_SUCCESS);	(void)clone_reap(pid, WEXITED);	goto cleanup; cleanup_pass:	ret = KSFT_PASS; cleanup:	if (cg_test_d)		cg_destroy(cg_test_d);	if (cg_test_c)		cg_destroy(cg_test_c);	if (cg_test_b)		cg_destroy(cg_test_b);	if (cg_test_a)		cg_destroy(cg_test_a);	free(cg_test_d);	free(cg_test_c);	free(cg_test_b);	free(cg_test_a);	if (cgroup_fd >= 0)		close(cgroup_fd);	return ret;} /* * A (domain threaded) - B (threaded) - C (domain) * * test that C can't be used until it is turned into a * threaded cgroup.  "cgroup.type" file will report "domain (invalid)" in * these cases. Operations which fail due to invalid topology use * EOPNOTSUPP as the errno. */static int test_cgcore_invalid_domain(const char *root){	int ret = KSFT_FAIL;	char *grandparent = NULL, *parent = NULL, *child = NULL; 	if (cg_test_v1_named)		return KSFT_SKIP; 	grandparent = cg_name(root, "cg_test_grandparent");	parent = cg_name(root, "cg_test_grandparent/cg_test_parent");	child = cg_name(root, "cg_test_grandparent/cg_test_parent/cg_test_child");	if (!parent || !child || !grandparent)		goto cleanup; 	if (cg_create(grandparent))		goto cleanup; 	if (cg_create(parent))		goto cleanup; 	if (cg_create(child))		goto cleanup; 	if (cg_write(parent, "cgroup.type", "threaded"))		goto cleanup; 	if (cg_read_strcmp(child, "cgroup.type", "domain invalid\n"))		goto cleanup; 	if (!cg_enter_current(child))		goto cleanup; 	if (errno != EOPNOTSUPP)		goto cleanup; 	if (!clone_into_cgroup_run_wait(child))		goto cleanup; 	if (errno == ENOSYS)		goto cleanup_pass; 	if (errno != EOPNOTSUPP)		goto cleanup; cleanup_pass:	ret = KSFT_PASS; cleanup:	cg_enter_current(root);	if (child)		cg_destroy(child);	if (parent)		cg_destroy(parent);	if (grandparent)		cg_destroy(grandparent);	free(child);	free(parent);	free(grandparent);	return ret;} /* * Test that when a child becomes threaded * the parent type becomes domain threaded. */static int test_cgcore_parent_becomes_threaded(const char *root){	int ret = KSFT_FAIL;	char *parent = NULL, *child = NULL; 	if (cg_test_v1_named)		return KSFT_SKIP; 	parent = cg_name(root, "cg_test_parent");	child = cg_name(root, "cg_test_parent/cg_test_child");	if (!parent || !child)		goto cleanup; 	if (cg_create(parent))		goto cleanup; 	if (cg_create(child))		goto cleanup; 	if (cg_write(child, "cgroup.type", "threaded"))		goto cleanup; 	if (cg_read_strcmp(parent, "cgroup.type", "domain threaded\n"))		goto cleanup; 	ret = KSFT_PASS; cleanup:	if (child)		cg_destroy(child);	if (parent)		cg_destroy(parent);	free(child);	free(parent);	return ret; } /* * Test that there's no internal process constrain on threaded cgroups. * You can add threads/processes on a parent with a controller enabled. */static int test_cgcore_no_internal_process_constraint_on_threads(const char *root){	int ret = KSFT_FAIL;	char *parent = NULL, *child = NULL; 	if (cg_test_v1_named ||	    cg_read_strstr(root, "cgroup.controllers", "cpu") ||	    cg_write(root, "cgroup.subtree_control", "+cpu")) {		ret = KSFT_SKIP;		goto cleanup;	} 	parent = cg_name(root, "cg_test_parent");	child = cg_name(root, "cg_test_parent/cg_test_child");	if (!parent || !child)		goto cleanup; 	if (cg_create(parent))		goto cleanup; 	if (cg_create(child))		goto cleanup; 	if (cg_write(parent, "cgroup.type", "threaded"))		goto cleanup; 	if (cg_write(child, "cgroup.type", "threaded"))		goto cleanup; 	if (cg_write(parent, "cgroup.subtree_control", "+cpu"))		goto cleanup; 	if (cg_enter_current(parent))		goto cleanup; 	ret = KSFT_PASS; cleanup:	cg_enter_current(root);	if (child)		cg_destroy(child);	if (parent)		cg_destroy(parent);	free(child);	free(parent);	return ret;} /* * Test that you can't enable a controller on a child if it's not enabled * on the parent. */static int test_cgcore_top_down_constraint_enable(const char *root){	int ret = KSFT_FAIL;	char *parent = NULL, *child = NULL; 	if (cg_test_v1_named)		return KSFT_SKIP; 	parent = cg_name(root, "cg_test_parent");	child = cg_name(root, "cg_test_parent/cg_test_child");	if (!parent || !child)		goto cleanup; 	if (cg_create(parent))		goto cleanup; 	if (cg_create(child))		goto cleanup; 	if (!cg_write(child, "cgroup.subtree_control", "+memory"))		goto cleanup; 	ret = KSFT_PASS; cleanup:	if (child)		cg_destroy(child);	if (parent)		cg_destroy(parent);	free(child);	free(parent);	return ret;} /* * Test that you can't disable a controller on a parent * if it's enabled in a child. */static int test_cgcore_top_down_constraint_disable(const char *root){	int ret = KSFT_FAIL;	char *parent = NULL, *child = NULL; 	if (cg_test_v1_named)		return KSFT_SKIP; 	parent = cg_name(root, "cg_test_parent");	child = cg_name(root, "cg_test_parent/cg_test_child");	if (!parent || !child)		goto cleanup; 	if (cg_create(parent))		goto cleanup; 	if (cg_create(child))		goto cleanup; 	if (cg_write(parent, "cgroup.subtree_control", "+memory"))

Lines 1–500 of 951.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t