All of lore.kernel.org
 help / color / mirror / Atom feed
* [LTP] [RFC PATCH v2 0/7] CGroup API rewrite
@ 2021-03-02 15:25 Richard Palethorpe
  2021-03-02 15:25 ` [LTP] [RFC PATCH v2 1/7] API: Add safe openat, printfat, readat and unlinkat Richard Palethorpe
                   ` (6 more replies)
  0 siblings, 7 replies; 13+ messages in thread
From: Richard Palethorpe @ 2021-03-02 15:25 UTC (permalink / raw)
  To: ltp

Hello,

This is a complete rewrite of the CGroups API. It has big changes even
compared to V1 of the patchset. To understand why this is so
complicated, please see the commments in tst_cgroup.h and
tst_cgroup.c.

V2:

* Created an object (Item) API which looks a bit like the unified V2
  hierarchy. The implementation is quite verbose, but not complicated
  IMO.

* Add the ability to extend the LTP CGroup hierarchy with child
  groups. We already have a reproducer that requires such a hierarchy,
  but I have not had chance to turn it into a test case yet.

* Add documentation for the new API in test-writing-guidelines.txt.

* Convert madvise06 to the CGroups API

* Better error reporting for the *at functions. Add tst_decode_fd
  which tries to print the path an FD was opened with.

TODO/NOTES:

* Some objects are allocated in guarded buffers, some with malloc,
  others are static. This is partially because some of the struct
  types are static. It might be better to expose them all to the test
  executable so it can allocate them statitically or use guarded
  buffers for everything.

* ksm02 segfaults, although this does not appear to be caused by the
  CGroup API.

* cpuset01 triggers an ASAN splat, which again does not appear to be
  caused by the new API.

* There are probably other tests like madvise06 which mount CGroups in
  an ad-hoc way and need to be converted to the new API.

Richard Palethorpe (7):
  API: Add safe openat, printfat, readat and unlinkat
  API: Add macro for the container_of trick
  Add new CGroups Core and Item APIs
  Add new CGroups API library tests
  docs: Update CGroups API
  mem: Convert tests to new CGroups API
  madvise06: Convert to new CGroups API

 doc/test-writing-guidelines.txt               |  165 ++-
 include/tst_cgroup.h                          |  124 +-
 include/tst_cgroup_core.h                     |   58 +
 include/tst_cgroup_item.h                     |  139 ++
 include/tst_common.h                          |    5 +
 include/tst_safe_file_ops.h                   |   39 +
 include/tst_test.h                            |    1 -
 lib/newlib_tests/.gitignore                   |    2 +
 lib/newlib_tests/test21.c                     |   43 +-
 lib/newlib_tests/tst_cgroup01.c               |   51 +
 lib/newlib_tests/tst_cgroup02.c               |   87 ++
 lib/tst_cgroup.c                              | 1258 ++++++++++++-----
 lib/tst_safe_file_ops.c                       |  171 +++
 testcases/kernel/mem/cpuset/cpuset01.c        |   34 +-
 testcases/kernel/mem/include/mem.h            |    2 +-
 testcases/kernel/mem/ksm/ksm02.c              |   13 +-
 testcases/kernel/mem/ksm/ksm03.c              |   12 +-
 testcases/kernel/mem/ksm/ksm04.c              |   17 +-
 testcases/kernel/mem/lib/mem.c                |   10 +-
 testcases/kernel/mem/oom/oom03.c              |   18 +-
 testcases/kernel/mem/oom/oom04.c              |   19 +-
 testcases/kernel/mem/oom/oom05.c              |   32 +-
 testcases/kernel/syscalls/madvise/madvise06.c |   83 +-
 23 files changed, 1878 insertions(+), 505 deletions(-)
 create mode 100644 include/tst_cgroup_core.h
 create mode 100644 include/tst_cgroup_item.h
 create mode 100644 lib/newlib_tests/tst_cgroup01.c
 create mode 100644 lib/newlib_tests/tst_cgroup02.c
 create mode 100644 lib/tst_safe_file_ops.c

-- 
2.30.0


^ permalink raw reply	[flat|nested] 13+ messages in thread

* [LTP] [RFC PATCH v2 1/7] API: Add safe openat, printfat, readat and unlinkat
  2021-03-02 15:25 [LTP] [RFC PATCH v2 0/7] CGroup API rewrite Richard Palethorpe
@ 2021-03-02 15:25 ` Richard Palethorpe
  2021-03-08  8:37   ` Li Wang
  2021-03-02 15:25 ` [LTP] [RFC PATCH v2 2/7] API: Add macro for the container_of trick Richard Palethorpe
                   ` (5 subsequent siblings)
  6 siblings, 1 reply; 13+ messages in thread
From: Richard Palethorpe @ 2021-03-02 15:25 UTC (permalink / raw)
  To: ltp

Add 'at' variants for a number of system calls and LTP SAFE API
functions. This avoids using sprintf everywhere to build paths.

Also adds tst_decode_fd which allows us to retrieve the path for an FD
for debugging purposes without having to store it ourselves. However
the proc symlink may not be available on some systems.

Signed-off-by: Richard Palethorpe <rpalethorpe@suse.com>
---
 include/tst_safe_file_ops.h |  39 ++++++++
 lib/tst_safe_file_ops.c     | 171 ++++++++++++++++++++++++++++++++++++
 2 files changed, 210 insertions(+)
 create mode 100644 lib/tst_safe_file_ops.c

diff --git a/include/tst_safe_file_ops.h b/include/tst_safe_file_ops.h
index 223eddd1f..dff6a793c 100644
--- a/include/tst_safe_file_ops.h
+++ b/include/tst_safe_file_ops.h
@@ -57,4 +57,43 @@
 #define TST_MOUNT_OVERLAY() \
 	(mount_overlay(__FILE__, __LINE__, 0) == 0)
 
+#define SAFE_OPENAT(dirfd, path, oflags, ...)			\
+	safe_openat(__FILE__, __LINE__,				\
+		    (dirfd), (path), (oflags), ## __VA_ARGS__)
+
+#define SAFE_FILE_READAT(dirfd, path, buf, nbyte)			\
+	safe_file_readat(__FILE__, __LINE__,				\
+			 (dirfd), (path), (buf), (nbyte))
+
+
+#define SAFE_FILE_PRINTFAT(dirfd, path, fmt, ...)			\
+	safe_file_printfat(__FILE__, __LINE__,				\
+			   (dirfd), (path), (fmt), __VA_ARGS__)
+
+#define SAFE_UNLINKAT(dirfd, path, flags)				\
+	safe_unlinkat(__FILE__, __LINE__, (dirfd), (path), (flags))
+
+char *tst_decode_fd(int fd);
+
+int safe_openat(const char *file, const int lineno,
+		int dirfd, const char *path, int oflags, ...);
+
+ssize_t safe_file_readat(const char *file, const int lineno,
+			 int dirfd, const char *path, char *buf, size_t nbyte);
+
+int tst_file_vprintfat(int dirfd, const char *path, const char *fmt, va_list va);
+int tst_file_printfat(int dirfd, const char *path, const char *fmt, ...)
+			__attribute__ ((format (printf, 3, 4)));
+
+int safe_file_vprintfat(const char *file, const int lineno,
+			int dirfd, const char *path,
+			const char *fmt, va_list va);
+
+int safe_file_printfat(const char *file, const int lineno,
+		       int dirfd, const char *path, const char *fmt, ...)
+			__attribute__ ((format (printf, 5, 6)));
+
+int safe_unlinkat(const char *file, const int lineno,
+		  int dirfd, const char *path, int flags);
+
 #endif /* TST_SAFE_FILE_OPS */
diff --git a/lib/tst_safe_file_ops.c b/lib/tst_safe_file_ops.c
new file mode 100644
index 000000000..af4157476
--- /dev/null
+++ b/lib/tst_safe_file_ops.c
@@ -0,0 +1,171 @@
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include "lapi/fcntl.h"
+
+#define TST_NO_DEFAULT_MAIN
+#include "tst_test.h"
+
+char fd_path[PATH_MAX];
+
+char *tst_decode_fd(int fd)
+{
+	ssize_t ret;
+	char proc_path[32];
+
+	if (fd < 0)
+		return "!";
+
+	sprintf(proc_path, "/proc/self/fd/%d", fd);
+	ret = readlink(proc_path, fd_path, sizeof(fd_path));
+
+	if (ret < 0)
+		return "?";
+
+	fd_path[ret] = '\0';
+
+	return fd_path;
+}
+
+int safe_openat(const char *file, const int lineno,
+		int dirfd, const char *path, int oflags, ...)
+{
+	va_list ap;
+	int fd;
+	mode_t mode;
+
+	va_start(ap, oflags);
+	mode = va_arg(ap, int);
+	va_end(ap);
+
+	fd = openat(dirfd, path, oflags, mode);
+	if (fd > -1)
+		return fd;
+
+	tst_brk_(file, lineno, TBROK | TERRNO,
+		 "openat(%d<%s>, '%s', %o, %o)",
+		 dirfd, tst_decode_fd(dirfd), path, oflags, mode);
+
+	return fd;
+}
+
+ssize_t safe_file_readat(const char *file, const int lineno,
+			 int dirfd, const char *path, char *buf, size_t nbyte)
+{
+	int fd = safe_openat(file, lineno, dirfd, path, O_RDONLY);
+	ssize_t rval;
+
+	if (fd < 0)
+		return -1;
+
+	rval = safe_read(file, lineno, NULL, 0, fd, buf, nbyte - 1);
+	if (rval < 0)
+		return -1;
+
+	close(fd);
+	buf[rval] = '\0';
+
+	if (rval >= (ssize_t)nbyte - 1) {
+		tst_brk_(file, lineno, TBROK,
+			"Buffer length %zu too small to read %d<%s>/%s",
+			nbyte, dirfd, tst_decode_fd(dirfd), path);
+	}
+
+	return rval;
+}
+
+int tst_file_vprintfat(int dirfd, const char *path, const char *fmt, va_list va)
+{
+	int fd = openat(dirfd, path, O_WRONLY);
+
+	if (fd < 0)
+		return -1;
+
+	TEST(vdprintf(fd, fmt, va));
+	close(fd);
+
+	if (TST_RET < 0) {
+		errno = TST_ERR;
+		return -2;
+	}
+
+	return TST_RET;
+}
+
+int tst_file_printfat(int dirfd, const char *path, const char *fmt, ...)
+{
+	va_list va;
+	int rval;
+
+	va_start(va, fmt);
+	rval = tst_file_vprintfat(dirfd, path, fmt, va);
+	va_end(va);
+
+	return rval;
+}
+
+int safe_file_vprintfat(const char *file, const int lineno,
+			int dirfd, const char *path,
+			const char *fmt, va_list va)
+{
+	char buf[16];
+	va_list vac;
+	int rval;
+
+	va_copy(vac, va);
+
+	TEST(tst_file_vprintfat(dirfd, path, fmt, va));
+
+	if (TST_RET == -2) {
+		rval = vsnprintf(buf, sizeof(buf), fmt, vac);
+		va_end(vac);
+
+		if (rval >= (ssize_t)sizeof(buf))
+			strcpy(buf + sizeof(buf) - 5, "...");
+
+		tst_brk_(file, lineno, TBROK | TTERRNO,
+			 "vdprintf(%d<%s>, '%s', '%s'<%s>)",
+			 dirfd, tst_decode_fd(dirfd), path, fmt,
+			 rval > 0 ? buf : "???");
+		return -1;
+	}
+
+	va_end(vac);
+
+	if (TST_RET == -1) {
+		tst_brk_(file, lineno, TBROK | TTERRNO,
+			"openat(%d<%s>, '%s', O_WRONLY)",
+			dirfd, tst_decode_fd(dirfd), path);
+	}
+
+	return TST_RET;
+}
+
+int safe_file_printfat(const char *file, const int lineno,
+		       int dirfd, const char *path,
+		       const char *fmt, ...)
+{
+	va_list va;
+	int rval;
+
+	va_start(va, fmt);
+	rval = safe_file_vprintfat(file, lineno, dirfd, path, fmt, va);
+	va_end(va);
+
+	return rval;
+}
+
+int safe_unlinkat(const char *file, const int lineno,
+		  int dirfd, const char *path, int flags)
+{
+	int rval = unlinkat(dirfd, path, flags);
+
+	if (rval > -1)
+		return rval;
+
+	tst_brk_(file, lineno, TBROK | TERRNO,
+		 "unlinkat(%d<%s>, '%s', %s)", dirfd, tst_decode_fd(dirfd), path,
+		 flags == AT_REMOVEDIR ? "AT_REMOVEDIR" : (flags ? "?" : "0"));
+
+	return rval;
+}
-- 
2.30.0


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [LTP] [RFC PATCH v2 2/7] API: Add macro for the container_of trick
  2021-03-02 15:25 [LTP] [RFC PATCH v2 0/7] CGroup API rewrite Richard Palethorpe
  2021-03-02 15:25 ` [LTP] [RFC PATCH v2 1/7] API: Add safe openat, printfat, readat and unlinkat Richard Palethorpe
@ 2021-03-02 15:25 ` Richard Palethorpe
  2021-03-08  8:39   ` Li Wang
  2021-03-02 15:25 ` [LTP] [RFC PATCH v2 3/7] Add new CGroups Core and Item APIs Richard Palethorpe
                   ` (4 subsequent siblings)
  6 siblings, 1 reply; 13+ messages in thread
From: Richard Palethorpe @ 2021-03-02 15:25 UTC (permalink / raw)
  To: ltp

Signed-off-by: Richard Palethorpe <rpalethorpe@suse.com>
---
 include/tst_common.h | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/include/tst_common.h b/include/tst_common.h
index fd7a900d4..317925d1d 100644
--- a/include/tst_common.h
+++ b/include/tst_common.h
@@ -83,4 +83,9 @@
 #define TST_RES_SUPPORTS_TCONF_TFAIL_TINFO_TPASS_TWARN(condition) \
 	TST_BUILD_BUG_ON(condition)
 
+#define tst_container_of(ptr, type, member) ({		     \
+	const typeof( ((type *)0)->member ) *__mptr = (ptr); \
+	(type *)( (char *)__mptr - offsetof(type,member) );  \
+})
+
 #endif /* TST_COMMON_H__ */
-- 
2.30.0


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [LTP] [RFC PATCH v2 3/7] Add new CGroups Core and Item APIs
  2021-03-02 15:25 [LTP] [RFC PATCH v2 0/7] CGroup API rewrite Richard Palethorpe
  2021-03-02 15:25 ` [LTP] [RFC PATCH v2 1/7] API: Add safe openat, printfat, readat and unlinkat Richard Palethorpe
  2021-03-02 15:25 ` [LTP] [RFC PATCH v2 2/7] API: Add macro for the container_of trick Richard Palethorpe
@ 2021-03-02 15:25 ` Richard Palethorpe
  2021-03-08  9:04   ` Li Wang
  2021-03-02 15:25 ` [LTP] [RFC PATCH v2 4/7] Add new CGroups API library tests Richard Palethorpe
                   ` (3 subsequent siblings)
  6 siblings, 1 reply; 13+ messages in thread
From: Richard Palethorpe @ 2021-03-02 15:25 UTC (permalink / raw)
  To: ltp

Complete rewrite of the CGroups API which provides two layers of
indirection between the test author and the SUT's CGroup
configuration.

Signed-off-by: Richard Palethorpe <rpalethorpe@suse.com>
---
 include/tst_cgroup.h      |  124 ++--
 include/tst_cgroup_core.h |   58 ++
 include/tst_cgroup_item.h |  139 ++++
 include/tst_test.h        |    1 -
 lib/tst_cgroup.c          | 1258 +++++++++++++++++++++++++++----------
 5 files changed, 1224 insertions(+), 356 deletions(-)
 create mode 100644 include/tst_cgroup_core.h
 create mode 100644 include/tst_cgroup_item.h

diff --git a/include/tst_cgroup.h b/include/tst_cgroup.h
index bfd848260..3263537c2 100644
--- a/include/tst_cgroup.h
+++ b/include/tst_cgroup.h
@@ -2,46 +2,98 @@
 /*
  * Copyright (c) 2020 Red Hat, Inc.
  * Copyright (c) 2020 Li Wang <liwang@redhat.com>
+ * Copyright (c) 2020-2021 SUSE LLC <rpalethorpe@suse.com>
  */
+/*\
+ * [DESCRIPTION]
+ *
+ * The LTP Control Groups library can be seperated into two parts.
+ * - CGroup Core
+ * - CGroup Item
+ *
+ * The CGroup Core is where we scan the current system, build a model
+ * of the current CGroup configuration, then add missing configuration
+ * necessary for the LTP or give up. Once testing is finished it then
+ * also attempts cleanup.
+ *
+ * The CGroup Item interface is mostly what the test author interacts
+ * with. It mirrors the CGroups V2 (unified) kernel API. So even if
+ * the system has some nasty V1 configuration, the test author may
+ * write their code as if for V2 (with exceptions). It builds on the
+ * model created by CGroup Core.
+ *
+ * You may ask; "Why don't you just mount a simple CGroup hierarchy,
+ * instead of scanning the current setup?". The short answer is that
+ * it is not possible unless no CGroups are currently active and
+ * almost all of our users will have CGroups active. Even if
+ * unmounting the current CGroup hierarchy is a reasonable thing to do
+ * to the sytem manager, it is highly unlikely the CGroup hierarchy
+ * will be destroyed. So users would be forced to remove their CGroup
+ * configuration and reboot the system.
+ *
+ * The core library tries to ensure an LTP CGroup exists on each
+ * hierarchy root. Inside the LTP group it ensures a 'drain' group
+ * exists and creats a test group for the current test. In the worst
+ * case we end up with a set of hierarchies like the follwoing. Where
+ * existing system manager created CGroups have been omitted.
+ *
+ * 	(V2 Root)	(V1 Root 1)	...	(V1 Root N)
+ * 	    |		     |			     |
+ *	  (ltp)		   (ltp)	...	   (ltp)
+ *	 /     \	  /	\		  /	\
+ *  (drain) (test-n) (drain)  (test-n)  ...  (drain)  (test-n)
+ *
+ * V2 CGroup controllers use a single unified hierarchy on a single
+ * root. Two or more V1 controllers may share a root or have their own
+ * root. However there may exist only one instance of a controller.
+ * So you can not have the same V1 controller on multiple roots.
+ *
+ * It is possible to have both a V2 hierarchy and V1 hierarchies
+ * active at the same time. Which is what is shown above. Any
+ * controllers attached to V1 hierarchies will not be available in the
+ * V2 hierarchy. The reverse is also true.
+ *
+ * Note that a single hierarchy may be mounted multiple
+ * times. Allowing it to be accessed at different locations. However
+ * subsequent mount operations will fail if the mount options are
+ * different from the first.
+ *
+ * The user may pre-create the CGroup hierarchies and the ltp CGroup,
+ * otherwise the library will try to create them. If the ltp group
+ * already exists and has appropriate permissions, then admin
+ * privileges will not be required to run the tests.
+ *
+ * Because the test may not have access to the CGroup root(s), the
+ * drain CGroup is created. This can be used to store processes which
+ * would otherwise block the destruction of the individual test CGroup
+ * or one of its descendants.
+ *
+ * The test author may create child CGroups within the test CGroup
+ * using the CGroup Item API. The library will create the new CGroup
+ * in all the relevant hierarchies.
+ *
+ * There are many differences between the V1 and V2 CGroup APIs. If a
+ * controller is on both V1 and V2, it may have different parameters
+ * and control files. Some of these control files have a different
+ * name, but the same functionality. In this case the Item API uses
+ * the V2 names and aliases them to the V1 name when appropriate.
+ *
+ * Some control files only exist on one of the versions or they can be
+ * missing due to other reasons. The Item API allows the user to check
+ * if the file exists before trying to use it.
+ *
+ * In some cases a control file has almost the same functionality
+ * between V1 and V2. Which means it can be used in the same way most
+ * of the time, but not all. For now this is handled by exposing the
+ * API version a controller is using to allow the test author to
+ * handle edge cases. (e.g. V2 memory.swap.max accepts "max", but V1
+ * memory.memsw.limit_in_bytes does not).
+\*/
 
 #ifndef TST_CGROUP_H
 #define TST_CGROUP_H
 
-#define PATH_TMP_CG_MEM	"/tmp/cgroup_mem"
-#define PATH_TMP_CG_CST	"/tmp/cgroup_cst"
-
-enum tst_cgroup_ver {
-	TST_CGROUP_V1 = 1,
-	TST_CGROUP_V2 = 2,
-};
-
-enum tst_cgroup_ctrl {
-	TST_CGROUP_MEMCG = 1,
-	TST_CGROUP_CPUSET = 2,
-	/* add cgroup controller */
-};
-
-enum tst_cgroup_ver tst_cgroup_version(void);
-
-/* To mount/umount specified cgroup controller on 'cgroup_dir' path */
-void tst_cgroup_mount(enum tst_cgroup_ctrl ctrl, const char *cgroup_dir);
-void tst_cgroup_umount(const char *cgroup_dir);
-
-/* To move current process PID to the mounted cgroup tasks */
-void tst_cgroup_move_current(const char *cgroup_dir);
-
-/* To set cgroup controller knob with new value */
-void tst_cgroup_set_knob(const char *cgroup_dir, const char *knob, long value);
-
-/* Set of functions to set knobs under the memory controller */
-void tst_cgroup_mem_set_maxbytes(const char *cgroup_dir, long memsz);
-int  tst_cgroup_mem_swapacct_enabled(const char *cgroup_dir);
-void tst_cgroup_mem_set_maxswap(const char *cgroup_dir, long memsz);
-
-/* Set of functions to read/write cpuset controller files content */
-void tst_cgroup_cpuset_read_files(const char *cgroup_dir, const char *filename,
-	char *retbuf, size_t retbuf_sz);
-void tst_cgroup_cpuset_write_files(const char *cgroup_dir, const char *filename,
-	const char *buf);
+#include "tst_cgroup_core.h"
+#include "tst_cgroup_item.h"
 
 #endif /* TST_CGROUP_H */
diff --git a/include/tst_cgroup_core.h b/include/tst_cgroup_core.h
new file mode 100644
index 000000000..00c1b6f84
--- /dev/null
+++ b/include/tst_cgroup_core.h
@@ -0,0 +1,58 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2020-2021 SUSE LLC <rpalethorpe@suse.com>
+ */
+#ifndef TST_CGROUP_CORE_H
+#define TST_CGROUP_CORE_H
+
+enum tst_cgroup_ver {
+	TST_CGROUP_V1 = 1,
+	TST_CGROUP_V2 = 2,
+};
+
+enum tst_cgroup_ctrl {
+	TST_CGROUP_MEMORY = 1,
+	TST_CGROUP_CPUSET = 2,
+};
+#define TST_CGROUP_MAX TST_CGROUP_CPUSET
+
+/* At most we can have one cgroup V1 tree for each controller and one
+ * (empty) v2 tree.
+ */
+#define TST_CGROUP_MAX_TREES (TST_CGROUP_MAX + 1)
+
+
+/* Used to specify CGroup hierarchy configuration options, allowing a
+ * test to request a particular CGroup structure.
+ */
+struct tst_cgroup_opts {
+	/* Only try to mount V1 CGroup controllers. This will not
+	 * prevent V2 from being used if it is already mounted, it
+	 * only indicates that we should mount V1 controllers if
+	 * nothing is present. By default we try to mount V2 first. */
+	int only_mount_v1:1;
+};
+
+struct tst_cgroup_tree;
+
+struct tst_cgroup_location {
+	const char *name;
+	const struct tst_cgroup_tree *tree;
+};
+
+/* Search the system for mounted cgroups and available
+ * controllers. Called automatically by tst_cgroup_require.
+ */
+void tst_cgroup_scan(void);
+/* Print the config detected by tst_cgroup_scan */
+void tst_cgroup_print_config(void);
+
+/* Ensure the specified controller is available in the test's default
+ * CGroup, mounting/enabling it if necessary */
+void tst_cgroup_require(enum tst_cgroup_ctrl type,
+			const struct tst_cgroup_opts *options);
+
+/* Tear down any CGroups created by calls to tst_cgroup_require */
+void tst_cgroup_cleanup(void);
+
+#endif /* TST_CGROUP_CORE_H */
diff --git a/include/tst_cgroup_item.h b/include/tst_cgroup_item.h
new file mode 100644
index 000000000..476c06d86
--- /dev/null
+++ b/include/tst_cgroup_item.h
@@ -0,0 +1,139 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2020-2021 SUSE LLC <rpalethorpe@suse.com>
+ */
+#ifndef TST_CGROUP_ITEM_H
+#define TST_CGROUP_ITEM_H
+
+/* CGroup Item Declarations */
+
+
+typedef const struct tst_cgroup_item *const *tst_cgroup_item_ptr;
+/* Base CGroup Item operations
+ *
+ * Note, for all callbacks, we pass a pointer to the item field within
+ * the containing object. Then use the container_of trick to get the
+ * specific tst_cgroup_* struct. This avoids passing a void pointer
+ * which has no type checking atall.
+ */
+struct tst_cgroup_item {
+	int (*exists)(const char *, const int, tst_cgroup_item_ptr);
+};
+
+struct tst_cgroup_file {
+	const struct tst_cgroup_item *item;
+
+	unsigned int n;
+	struct tst_cgroup_location locations[TST_CGROUP_MAX_TREES];
+};
+
+/* Generic Control Group (cgroup) parameters */
+struct tst_cgroup_cgroup {
+	const struct tst_cgroup_item *item;
+
+	/* tasks on V1, cgroup.procs on V2 */
+	struct tst_cgroup_file procs;
+	/* Only V1 */
+	struct tst_cgroup_file clone_children;
+	/* Only V2 */
+	struct tst_cgroup_file subtree_control;
+};
+
+/* On V1 this is memsw */
+struct tst_cgroup_memory_swap {
+	const struct tst_cgroup_item *item;
+
+	struct tst_cgroup_file current;
+	struct tst_cgroup_file max;
+};
+
+/* Only on V1 (but same info may be in memory.stat on V2) */
+struct tst_cgroup_memory_kmem {
+	const struct tst_cgroup_item *item;
+
+	struct tst_cgroup_file current;
+	struct tst_cgroup_file max;
+};
+
+/* memory (memcg) controller */
+struct tst_cgroup_memory {
+	const struct tst_cgroup_item *item;
+	enum tst_cgroup_ver ver;
+
+	struct tst_cgroup_memory_kmem kmem;
+	struct tst_cgroup_memory_swap swap;
+
+	/* usage_in_bytes on V1 */
+	struct tst_cgroup_file current;
+	/* limit_in_bytes on V1 */
+	struct tst_cgroup_file max;
+	/* Only on V1 as of 5.11 */
+	struct tst_cgroup_file swappiness;
+};
+
+/* cpuset controller */
+struct tst_cgroup_cpuset {
+	const struct tst_cgroup_item *item;
+	enum tst_cgroup_ver ver;
+
+	struct tst_cgroup_file cpus;
+	struct tst_cgroup_file mems;
+
+	struct tst_cgroup_file memory_migrate;
+};
+
+/* A Control Group in LTP's aggregated hierarchy */
+struct tst_cgroup {
+	const struct tst_cgroup_item *item;
+
+	unsigned int n;
+	struct tst_cgroup_tree *trees[TST_CGROUP_MAX_TREES];
+
+	struct tst_cgroup_cgroup cgroup;
+	struct tst_cgroup_cpuset cpuset;
+	struct tst_cgroup_memory memory;
+};
+
+/* Get the default CGroup for the test. It allocates memory (in a
+ * guarded buffer) so should always be called from setup
+ */
+const struct tst_cgroup *tst_cgroup_get_default(void);
+/* Get the shared drain group. Also should be called from setup */
+const struct tst_cgroup *tst_cgroup_get_drain(void);
+/* Create a descendant CGroup */
+struct tst_cgroup *tst_cgroup_mk(const struct tst_cgroup *parent,
+				 const char *name);
+/* Remove a descendant CGroup */
+struct tst_cgroup *tst_cgroup_rm(struct tst_cgroup *cg);
+
+#define SAFE_CGROUP_READ(file, out, len) \
+	safe_cgroup_read(__FILE__, __LINE__, (file), (out), (len));
+
+ssize_t safe_cgroup_read(const char *file, const int lineno,
+			 const struct tst_cgroup_file *cgf,
+			 char *out, size_t len);
+
+/* Print to a control file in the test's default CGroup */
+#define SAFE_CGROUP_PRINTF(file, fmt, ...) \
+	safe_cgroup_printf(__FILE__, __LINE__, (file), (fmt), __VA_ARGS__)
+
+#define SAFE_CGROUP_PRINT(file, str) \
+	safe_cgroup_printf(__FILE__, __LINE__, (file), "%s", (str))
+
+void safe_cgroup_printf(const char *file, const int lineno,
+			const struct tst_cgroup_file *cgf,
+			const char *fmt, ...)
+			__attribute__ ((format (printf, 4, 5)));
+
+#define SAFE_CGROUP_SCANF(file, fmt, ...)				\
+	safe_cgroup_scanf(__FILE__, __LINE__, (file), (fmt), __VA_ARGS__)
+
+void safe_cgroup_scanf(const char *file, const int lineno,
+		       const struct tst_cgroup_file *cgf,
+		       const char *fmt, ...)
+		       __attribute__ ((format (scanf, 4, 5)));
+
+#define TST_CGROUP_HAS(obj) \
+	(((obj)->item)->exists(__FILE__, __LINE__, &(obj)->item))
+
+#endif /* TST_CGROUP_ITEM_H */
diff --git a/include/tst_test.h b/include/tst_test.h
index 1fbebe752..62ab2981f 100644
--- a/include/tst_test.h
+++ b/include/tst_test.h
@@ -39,7 +39,6 @@
 #include "tst_capability.h"
 #include "tst_hugepage.h"
 #include "tst_assert.h"
-#include "tst_cgroup.h"
 #include "tst_lockdown.h"
 #include "tst_fips.h"
 #include "tst_taint.h"
diff --git a/lib/tst_cgroup.c b/lib/tst_cgroup.c
index 96c9524d2..6fa725f37 100644
--- a/lib/tst_cgroup.c
+++ b/lib/tst_cgroup.c
@@ -2,453 +2,1073 @@
 /*
  * Copyright (c) 2020 Red Hat, Inc.
  * Copyright (c) 2020 Li Wang <liwang@redhat.com>
+ * Copyright (c) 2020-2021 SUSE LLC <rpalethorpe@suse.com>
  */
 
 #define TST_NO_DEFAULT_MAIN
 
 #include <stdio.h>
+#include <stddef.h>
 #include <stdlib.h>
+#include <mntent.h>
 #include <sys/mount.h>
-#include <fcntl.h>
-#include <unistd.h>
 
 #include "tst_test.h"
-#include "tst_safe_macros.h"
-#include "tst_safe_stdio.h"
+#include "lapi/fcntl.h"
+#include "lapi/mount.h"
+#include "lapi/mkdirat.h"
 #include "tst_cgroup.h"
-#include "tst_device.h"
 
-static enum tst_cgroup_ver tst_cg_ver;
-static int clone_children;
+/* CGroup Core Implementation
+ *
+ * CGroup Item Implementation is towards the bottom.
+ */
+
+struct cgroup_root;
+
+/* A node in a single CGroup hierarchy. It exists mainly for
+ * convenience so that we do not have to traverse the CGroup structure
+ * for frequent operations.
+ *
+ * This is actually a single-linked list not a tree. We only need to
+ * traverse from leaf towards root.
+ */
+struct tst_cgroup_tree {
+	/* The tree's name and parent */
+	struct tst_cgroup_location loc;
+	/* Shortcut to root */
+	const struct cgroup_root *root;
+
+	/* Subsystems (controllers) bit field. Only controllers which
+	 * were required and configured for this node are added to
+	 * this field. So it may be different from root->ss_field.
+	 */
+	uint32_t ss_field;
+
+	/* In general we avoid having sprintfs everywhere and use
+	 * openat, linkat, etc.
+	 */
+	int dir;
+
+	int we_created_it:1;
+};
+
+/* The root of a CGroup hierarchy/tree */
+struct cgroup_root {
+	enum tst_cgroup_ver ver;
+	/* A mount path */
+	char path[PATH_MAX/2];
+	/* Subsystems (controllers) bit field. Includes all
+	 * controllers found while scanningthis root.
+	 */
+	uint32_t ss_field;
+
+	/* CGroup hierarchy: mnt -> ltp -> {drain, test -> ??? } We
+	 * keep a flat reference to ltp, drain and test for
+	 * convenience.
+	 */
+
+	/* Mount directory */
+	struct tst_cgroup_tree mnt;
+	/* LTP CGroup directory, contains drain and test dirs */
+	struct tst_cgroup_tree ltp;
+	/* Drain CGroup, see cgroup_cleanup */
+	struct tst_cgroup_tree drain;
+	/* CGroup for current test. Which may have children. */
+	struct tst_cgroup_tree test;
+
+	int we_mounted_it:1;
+	/* cpuset is in compatability mode */
+	int no_prefix:1;
+};
+
+/* 'ss' stands for subsystem which is the same as 'controller' */
+struct cgroup_ss {
+	enum tst_cgroup_ctrl indx;
+	const char *name;
+	struct cgroup_root *tree;
+
+	int we_require_it:1;
+};
+
+/* Always use first item for unified hierarchy */
+struct cgroup_root roots[TST_CGROUP_MAX_TREES + 1];
 
-static int tst_cgroup_check(const char *cgroup)
+static struct cgroup_ss css[TST_CGROUP_MAX + 1] = {
+	{ TST_CGROUP_MEMORY, "memory", NULL, 0 },
+	{ TST_CGROUP_CPUSET, "cpuset", NULL, 0 },
+};
+
+static const struct tst_cgroup_opts default_opts = { 0 };
+
+/* We should probably allow these to be set in environment
+ * variables */
+static const char *ltp_cgroup_dir = "ltp";
+static const char *ltp_cgroup_drain_dir = "drain";
+static char test_cgroup_dir[PATH_MAX/4];
+static const char *ltp_mount_prefix = "/tmp/cgroup_";
+static const char *ltp_v2_mount = "unified";
+
+#define first_root				\
+	(roots[0].ver ? roots : roots + 1)
+#define for_each_root(r)			\
+	for ((r) = first_root; (r)->ver; (r)++)
+#define for_each_v1_root(r)			\
+	for ((r) = roots + 1; (r)->ver; (r)++)
+#define for_each_css(ss)			\
+	for ((ss) = css; (ss)->indx; (ss)++)
+
+static int has_css(uint32_t ss_field, enum tst_cgroup_ctrl type)
 {
-	char line[PATH_MAX];
-	FILE *file;
-	int cg_check = 0;
+	return !!(ss_field & (1 << type));
+}
 
-	file = SAFE_FOPEN("/proc/filesystems", "r");
-	while (fgets(line, sizeof(line), file)) {
-		if (strstr(line, cgroup) != NULL) {
-			cg_check = 1;
-			break;
-		}
+static void add_css(uint32_t *ss_field, int cond, enum tst_cgroup_ctrl type)
+{
+	*ss_field |= (!!cond) << type;
+}
+
+struct cgroup_root *tst_cgroup_root_get(void)
+{
+	return roots[0].ver ? roots : roots + 1;
+}
+
+static int cgroup_v2_mounted(void)
+{
+	return !!roots[0].ver;
+}
+
+static int cgroup_v1_mounted(void)
+{
+	return !!roots[1].ver;
+}
+
+static int cgroup_mounted(void)
+{
+	return cgroup_v2_mounted() || cgroup_v1_mounted();
+}
+
+static struct cgroup_ss *cgroup_get_ss(enum tst_cgroup_ctrl type)
+{
+	return &css[type - 1];
+}
+
+static int cgroup_ss_on_v2(const struct cgroup_ss *ss)
+{
+	return ss->tree->ver == TST_CGROUP_V2;
+}
+
+int tst_cgroup_tree_mk(const struct tst_cgroup_tree *parent,
+		       const char *name,
+		       struct tst_cgroup_tree *new)
+{
+	char *dpath;
+
+	new->root = parent->root;
+	new->loc.name = name;
+	new->loc.tree = parent;
+	new->ss_field = parent->ss_field;
+	new->we_created_it = 0;
+
+	if (!mkdirat(parent->dir, name, 0777)) {
+		new->we_created_it = 1;
+		goto opendir;
+	}
+
+	if (errno == EEXIST)
+		goto opendir;
+
+	dpath = tst_decode_fd(parent->dir);
+
+	if (errno == EACCES) {
+		tst_brk(TCONF | TERRNO,
+			"Lack permission to make '%s/%s'; premake it or run as root",
+			dpath, name);
+	} else {
+		tst_brk(TBROK | TERRNO,
+			"mkdirat(%d<%s>, '%s', 0777)", parent->dir, dpath, name);
+	}
+
+	return -1;
+opendir:
+	new->dir = SAFE_OPENAT(parent->dir, name, O_PATH | O_DIRECTORY);
+
+	return 0;
+}
+
+void tst_cgroup_print_config(void)
+{
+	struct cgroup_root *t;
+	struct cgroup_ss *ss;
+
+	tst_res(TINFO, "Detected Controllers:");
+
+	for_each_css(ss) {
+		t = ss->tree;
+
+		if (!t)
+			continue;
+
+		tst_res(TINFO, "\t%.10s %s @ %s:%s",
+			ss->name,
+			t->no_prefix ? "[noprefix]" : "",
+			t->ver == TST_CGROUP_V1 ? "V1" : "V2",
+			t->path);
+	}
+}
+
+/* Determine if a mounted cgroup hierarchy (tree) is unique and record it if so.
+ *
+ * For CGroups V2 this is very simple as there is only one
+ * hierarchy. We just record which controllers are available and check
+ * if this matches what we read from any previous mount points.
+ *
+ * For V1 the set of controllers S is partitioned into sets {P_1, P_2,
+ * ..., P_n} with one or more controllers in each partion. Each
+ * partition P_n can be mounted multiple times, but the same
+ * controller can not appear in more than one partition. Usually each
+ * partition contains a single controller, but, for example, cpu and
+ * cpuacct are often mounted together in the same partiion.
+ *
+ * Each controller partition has its own hierarchy which we must track
+ * and update independently.
+ */
+static void cgroup_root_scan(const char *type, const char *path, char *opts)
+{
+	struct cgroup_root *t = roots;
+	struct cgroup_ss *c;
+	uint32_t ss_field = 0;
+	int no_prefix = 0;
+	char buf[BUFSIZ];
+	char *tok;
+	int dfd = SAFE_OPEN(path, O_PATH | O_DIRECTORY);
+
+	if (!strcmp(type, "cgroup"))
+		goto v1;
+
+	SAFE_FILE_READAT(dfd, "cgroup.controllers", buf, sizeof(buf));
+
+	for (tok = strtok(buf, " "); tok; tok = strtok(NULL, " ")) {
+		for_each_css(c)
+			add_css(&ss_field, !strcmp(c->name, tok), c->indx);
+	}
+
+	if (t->ver && ss_field == t->ss_field)
+		goto discard;
+
+	if (t->ss_field)
+		tst_brk(TBROK, "Available V2 controllers are changing between scans?");
+
+	t->ver = TST_CGROUP_V2;
+
+	goto backref;
+
+v1:
+	for (tok = strtok(opts, ","); tok; tok = strtok(NULL, ",")) {
+		for_each_css(c)
+			add_css(&ss_field, !strcmp(c->name, tok), c->indx);
+
+		no_prefix |= !strcmp("noprefix", tok);
+	}
+
+	if (!ss_field)
+		goto discard;
+
+	for_each_v1_root(t) {
+		if (!(ss_field & t->ss_field))
+			continue;
+
+		if (ss_field == t->ss_field)
+			goto discard;
+
+		tst_brk(TBROK,
+			"The intersection of two distinct sets of mounted controllers should be null?"
+			"Check '%s' and '%s'", t->path, path);
+	}
+
+	if (t >= roots + TST_CGROUP_MAX_TREES) {
+		tst_brk(TBROK, "Unique controller mounts have exceeded our limit %d?",
+			TST_CGROUP_MAX_TREES);
+	}
+
+	t->ver = TST_CGROUP_V1;
+
+backref:
+	strcpy(t->path, path);
+	t->mnt.root = t;
+	t->mnt.loc.name = t->path;
+	t->mnt.dir = dfd;
+	t->ss_field = ss_field;
+	t->no_prefix = no_prefix;
+
+	for_each_css(c) {
+		if (has_css(t->ss_field, c->indx))
+			c->tree = t;
 	}
-	SAFE_FCLOSE(file);
 
-	return cg_check;
+	return;
+
+discard:
+	close(dfd);
 }
 
-enum tst_cgroup_ver tst_cgroup_version(void)
+void tst_cgroup_scan(void)
 {
-        enum tst_cgroup_ver cg_ver;
+	struct mntent *mnt;
+	FILE *f = setmntent("/proc/self/mounts", "r");
+
+	if (!f)
+		tst_brk(TBROK | TERRNO, "Can't open /proc/self/mounts");
+
+	mnt = getmntent(f);
+	if (!mnt)
+		tst_brk(TBROK | TERRNO, "Can't read mounts or no mounts?");
 
-        if (tst_cgroup_check("cgroup2")) {
-                if (!tst_is_mounted("cgroup2") && tst_is_mounted("cgroup"))
-                        cg_ver = TST_CGROUP_V1;
-                else
-                        cg_ver = TST_CGROUP_V2;
+	do {
+		if (strncmp(mnt->mnt_type, "cgroup", 6))
+			continue;
+
+		cgroup_root_scan(mnt->mnt_type, mnt->mnt_dir, mnt->mnt_opts);
+	} while ((mnt = getmntent(f)));
+}
+
+static void cgroup_mount_v2(void)
+{
+	char path[PATH_MAX];
 
-                goto out;
-        }
+	sprintf(path, "%s%s", ltp_mount_prefix, ltp_v2_mount);
 
-        if (tst_cgroup_check("cgroup"))
-                cg_ver = TST_CGROUP_V1;
+	if (!mkdir(path, 0777)) {
+		roots[0].mnt.we_created_it = 1;
+		goto mount;
+	}
+
+	if (errno == EEXIST)
+		goto mount;
+
+	if (errno == EACCES) {
+		tst_res(TINFO | TERRNO,
+			"Lack permission to make %s, premake it or run as root",
+			path);
+		return;
+	}
+
+	tst_brk(TBROK | TERRNO, "mkdir(%s, 0777)", path);
+
+mount:
+	if (!mount("cgroup2", path, "cgroup2", 0, NULL)) {
+		tst_res(TINFO, "Mounted V2 CGroups on %s", path);
+		tst_cgroup_scan();
+		roots[0].we_mounted_it = 1;
+		return;
+	}
 
-        if (!cg_ver)
-                tst_brk(TCONF, "Cgroup is not configured");
+	tst_res(TINFO | TERRNO, "Could not mount V2 CGroups on %s", path);
 
-out:
-        return cg_ver;
+	if (roots[0].mnt.we_created_it) {
+		roots[0].mnt.we_created_it = 0;
+		SAFE_RMDIR(path);
+	}
 }
 
-static void tst_cgroup1_mount(const char *name, const char *option,
-			const char *mnt_path, const char *new_path)
+static void cgroup_mount_v1(enum tst_cgroup_ctrl type)
 {
-	char knob_path[PATH_MAX];
-	if (tst_is_mounted(mnt_path))
-		goto out;
+	struct cgroup_ss *ss = cgroup_get_ss(type);
+	char path[PATH_MAX];
+	int made_dir = 0;
 
-	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);
+	sprintf(path, "%s%s", ltp_mount_prefix, ss->name);
+
+	if (!mkdir(path, 0777)) {
+		made_dir = 1;
+		goto mount;
 	}
 
-	/*
-	 * 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_SCANF(knob_path, "%d", &clone_children);
-		SAFE_FILE_PRINTF(knob_path, "%d", 1);
+	if (errno == EEXIST)
+		goto mount;
+
+	if (errno == EACCES) {
+		tst_res(TINFO | TERRNO,
+			"Lack permission to make %s, premake it or run as root",
+			path);
+		return;
 	}
-out:
-	SAFE_MKDIR(new_path, 0777);
 
-	tst_res(TINFO, "Cgroup(%s) v1 mount at %s success", option, mnt_path);
+	tst_brk(TBROK | TERRNO, "mkdir(%s, 0777)", path);
+
+mount:
+	if (mount(ss->name, path, "cgroup", 0, ss->name)) {
+		tst_res(TINFO | TERRNO,
+			"Could not mount V1 CGroup on %s", path);
+
+		if (made_dir)
+			SAFE_RMDIR(path);
+		return;
+	}
+
+	tst_res(TINFO, "Mounted V1 %s CGroup on %s", ss->name, path);
+	tst_cgroup_scan();
+	ss->tree->we_mounted_it = 1;
+	ss->tree->mnt.we_created_it = made_dir;
+
+	if (type == TST_CGROUP_MEMORY) {
+		SAFE_FILE_PRINTFAT(ss->tree->mnt.dir,
+				   "memory.use_hierarchy", "%d", 1);
+	}
+}
+
+static void cgroup_copy_cpuset(const struct cgroup_root *t)
+{
+	char buf[BUFSIZ];
+	int i;
+	const char *n0[] = {"mems", "cpus"};
+	const char *n1[] = {"cpuset.mems", "cpuset.cpus"};
+	const char **fname = t->no_prefix ? n0 : n1;
+
+	for (i = 0; i < 2; i++) {
+		SAFE_FILE_READAT(t->mnt.dir, fname[i], buf, sizeof(buf));
+		SAFE_FILE_PRINTFAT(t->ltp.dir, fname[i], "%s", buf);
+	}
 }
 
-static void tst_cgroup2_mount(const char *mnt_path, const char *new_path)
+/* Ensure the specified controller is available.
+ *
+ * First we check if the specified controller has a known mount point,
+ * if not then we scan the system. If we find it then we goto ensuring
+ * the LTP group exists in the hierarchy the controller is using.
+ *
+ * If we can't find the controller, then we try to create it. First we
+ * check if the V2 hierarchy/tree is mounted. If it isn't then we try
+ * mounting it and look for the controller. If it is already mounted
+ * then we know the controller is not available on V2 on this system.
+ *
+ * If we can't mount V2 or the controller is not on V2, then we try
+ * mounting it on its own V1 tree.
+ *
+ * Once we have mounted the controller somehow, we create a hierarchy
+ * of cgroups. If we are on V2 we first need to enable the controller
+ * for all children of root. Then we create hierarchy described in
+ * tst_cgroup.h.
+ *
+ * If we are using V1 cpuset then we copy the available mems and cpus
+ * from root to the ltp group and set clone_children on the ltp group
+ * to distribute these settings to the test cgroups. This means the
+ * test author does not have to copy these settings before using the
+ * cpuset.
+ *
+ */
+void tst_cgroup_require(enum tst_cgroup_ctrl type,
+			const struct tst_cgroup_opts *options)
 {
-	if (tst_is_mounted(mnt_path))
-		goto out;
+	const char *const cgsc = "cgroup.subtree_control";
+	struct cgroup_ss *ss = cgroup_get_ss(type);
+	struct cgroup_root *t;
+	char buf[BUFSIZ];
+
+	if (!options)
+		options = &default_opts;
+
+	if (ss->we_require_it)
+		tst_res(TWARN, "Duplicate tst_cgroup_require(%s, )", ss->name);
+	ss->we_require_it = 1;
+
+	if (ss->tree)
+		goto ltpdir;
+
+	tst_cgroup_scan();
+
+	if (ss->tree)
+		goto ltpdir;
+
+	if (!cgroup_v2_mounted() && !options->only_mount_v1)
+		cgroup_mount_v2();
 
-	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);
+	if (ss->tree)
+		goto ltpdir;
+
+	cgroup_mount_v1(type);
+
+	if (!ss->tree) {
+		tst_brk(TCONF,
+			"'%s' controller required, but not available", ss->name);
+	}
+
+ltpdir:
+	t = ss->tree;
+	add_css(&t->mnt.ss_field, 1, type);
+
+	if (cgroup_ss_on_v2(ss) && t->we_mounted_it) {
+		SAFE_FILE_PRINTFAT(t->mnt.dir, cgsc, "+%s", ss->name);
+	} else if (cgroup_ss_on_v2(ss)) {
+		SAFE_FILE_READAT(t->mnt.dir, cgsc, buf, sizeof(buf));
+		if (!strstr(buf, ss->name)) {
 			tst_brk(TCONF,
-				 "Cgroup v2 is not configured in kernel");
+				"'%s' controller required, but not in %s/%s",
+				ss->name, t->path, cgsc);
 		}
-		tst_brk(TBROK | TERRNO, "mount %s", mnt_path);
 	}
 
-out:
-	SAFE_MKDIR(new_path, 0777);
+	if (!t->ltp.dir)
+		tst_cgroup_tree_mk(&t->mnt, ltp_cgroup_dir, &t->ltp);
+	else
+		t->ltp.ss_field |= t->mnt.ss_field;
+
+	if (!t->ltp.we_created_it)
+		goto testdir;
+
+	if (cgroup_ss_on_v2(ss)) {
+		SAFE_FILE_PRINTFAT(t->ltp.dir, cgsc, "+%s", ss->name);
+	} else {
+		SAFE_FILE_PRINTFAT(t->ltp.dir, "cgroup.clone_children",
+				   "%d", 1);
+
+		if (type == TST_CGROUP_CPUSET)
+			cgroup_copy_cpuset(t);
+	}
+
+testdir:
+	tst_cgroup_tree_mk(&t->ltp, ltp_cgroup_drain_dir, &t->drain);
 
-	tst_res(TINFO, "Cgroup v2 mount at %s success", mnt_path);
+	sprintf(test_cgroup_dir, "test-%d", getpid());
+	tst_cgroup_tree_mk(&t->ltp, test_cgroup_dir, &t->test);
 }
 
-static void tst_cgroupN_umount(const char *mnt_path, const char *new_path)
+static void cgroup_drain(enum tst_cgroup_ver ver, int source, int dest)
 {
-	FILE *fp;
+	char buf[BUFSIZ];
+	char *tok;
+	const char *fname = ver == TST_CGROUP_V1 ? "tasks" : "cgroup.procs";
 	int fd;
-	char s_new[BUFSIZ], s[BUFSIZ], value[BUFSIZ];
-	char knob_path[PATH_MAX];
+	ssize_t ret;
 
-	if (!tst_is_mounted(mnt_path))
+	ret = SAFE_FILE_READAT(source, fname, buf, sizeof(buf));
+	if (ret < 0)
 		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 (tst_cg_ver & TST_CGROUP_V1) {
-		sprintf(knob_path, "%s/cpuset.cpus", mnt_path);
-		if (!access(knob_path, F_OK)) {
-			sprintf(knob_path, "%s/cgroup.clone_children", mnt_path);
-			SAFE_FILE_PRINTF(knob_path, "%d", clone_children);
-		}
+	fd = SAFE_OPENAT(dest, fname, O_WRONLY);
+	if (fd < 0)
+		return;
+
+	for (tok = strtok(buf, "\n"); tok; tok = strtok(NULL, "\n")) {
+		ret = dprintf(fd, "%s", tok);
+
+		if (ret < (ssize_t)strlen(tok))
+			tst_brk(TBROK | TERRNO, "Failed to drain %s", tok);
 	}
-	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;
-};
+	SAFE_CLOSE(fd);
+}
 
-static struct tst_cgroup_path *tst_cgroup_paths;
+static void close_path_fds(struct cgroup_root *t)
+{
+	if (t->test.dir > 0)
+		SAFE_CLOSE(t->test.dir);
+	if (t->ltp.dir > 0)
+		SAFE_CLOSE(t->ltp.dir);
+	if (t->drain.dir > 0)
+		SAFE_CLOSE(t->drain.dir);
+	if (t->mnt.dir > 0)
+		SAFE_CLOSE(t->mnt.dir);
+}
 
-static void tst_cgroup_set_path(const char *cgroup_dir)
+/* Maybe remove CGroups used during testing and clear our data
+ *
+ * This will never remove CGroups we did not create to allow tests to
+ * be run in parallel (see enum tst_cgroup_cleanup).
+ *
+ * Each test process is given its own unique CGroup. Unless we want to
+ * stress test the CGroup system. We should at least remove these
+ * unique test CGroups.
+ *
+ * We probably also want to remove the LTP parent CGroup, although
+ * this may have been created by the system manager or another test
+ * (see notes on parallel testing).
+ *
+ * On systems with no initial CGroup setup we may try to destroy the
+ * CGroup roots we mounted so that they can be recreated by another
+ * test. Note that successfully unmounting a CGroup root does not
+ * necessarily indicate that it was destroyed.
+ *
+ * The ltp/drain CGroup is required for cleaning up test CGroups when
+ * we can not move them to the root CGroup. CGroups can only be
+ * removed when they have no members and only leaf or root CGroups may
+ * have processes within them. As test processes create and destroy
+ * their own CGroups they must move themselves either to root or
+ * another leaf CGroup. So we move them to drain while destroying the
+ * unique test CGroup.
+ *
+ * If we have access to root and created the LTP CGroup we then move
+ * the test process to root and destroy the drain and LTP
+ * CGroups. Otherwise we just leave the test process to die in the
+ * drain, much like many a unwanted terrapin.
+ *
+ * Finally we clear any data we have collected on CGroups. This will
+ * happen regardless of whether anything was removed.
+ */
+void tst_cgroup_cleanup(void)
 {
-	char cgroup_new_dir[PATH_MAX];
-	struct tst_cgroup_path *tst_cgroup_path, *a;
+	struct cgroup_root *t;
+	struct cgroup_ss *ss;
 
-	if (!cgroup_dir)
-		tst_brk(TBROK, "Invalid cgroup dir, plese check cgroup_dir");
+	if (!cgroup_mounted())
+		goto clear_data;
 
-	sprintf(cgroup_new_dir, "%s/ltp_%d", cgroup_dir, rand());
+	for_each_root(t) {
+		if (!t->test.loc.name)
+			continue;
 
-	/* 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);
-	tst_cgroup_path->next = NULL;
+		cgroup_drain(t->ver, t->test.dir, t->drain.dir);
+		SAFE_UNLINKAT(t->ltp.dir, t->test.loc.name, AT_REMOVEDIR);
+	}
 
-	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);
+	for_each_root(t) {
+		if (!t->ltp.we_created_it)
+			continue;
+
+		cgroup_drain(t->ver, t->drain.dir, t->mnt.dir);
+
+		if (t->drain.loc.name)
+			SAFE_UNLINKAT(t->ltp.dir, t->drain.loc.name, AT_REMOVEDIR);
+
+		if (t->ltp.loc.name)
+			SAFE_UNLINKAT(t->mnt.dir, t->ltp.loc.name, AT_REMOVEDIR);
 	}
 
-	sprintf(tst_cgroup_path->mnt_path, "%s", cgroup_dir);
-	sprintf(tst_cgroup_path->new_path, "%s", cgroup_new_dir);
+	for_each_css(ss) {
+		if (!cgroup_ss_on_v2(ss) || !ss->tree->we_mounted_it)
+			continue;
+
+		SAFE_FILE_PRINTFAT(ss->tree->mnt.dir, "cgroup.subtree_control",
+				   "-%s", ss->name);
+	}
+
+	for_each_root(t) {
+		if (!t->we_mounted_it)
+			continue;
+
+		/* This probably does not result in the CGroup root
+		 * being destroyed */
+		if (umount2(t->path, MNT_DETACH))
+			continue;
+
+		SAFE_RMDIR(t->path);
+	}
+
+clear_data:
+	memset(css, 0, sizeof(css));
+
+	for_each_root(t)
+		close_path_fds(t);
+
+	memset(roots, 0, sizeof(roots));
 }
 
-static char *tst_cgroup_get_path(const char *cgroup_dir)
+/* CGroup Item Implementation */
+
+static int cgroup_default_exists(const char * file, const int lineno,
+				 tst_cgroup_item_ptr item LTP_ATTRIBUTE_UNUSED)
 {
-	struct tst_cgroup_path *a;
+	tst_brk_(file, lineno, TBROK, "Exists method not implemented for item");
+
+	return 0;
+}
 
-	if (!tst_cgroup_paths)
-		return NULL;
+static const struct tst_cgroup_item cgroup_default_item = {
+	.exists = cgroup_default_exists,
+};
 
-	a = tst_cgroup_paths;
+static int cgroup_ent_exists(const char * file, const int lineno,
+			     const struct tst_cgroup_location *loc)
+{
+	int dirfd, ret;
 
-	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;
-	};
+	if (!loc->tree)
+		return 0;
 
-	return a->new_path;
+	dirfd = loc->tree->dir;
+	ret = faccessat(dirfd, loc->name, F_OK, 0);
+
+	if (!ret)
+		return 1;
+
+	if (errno == ENOENT)
+		return 0;
+
+	tst_brk_(file, lineno, TBROK | TERRNO,
+		 "faccessat(%d<%s>, %s, F_OK, 0)",
+		 dirfd, tst_decode_fd(dirfd), loc->name);
+
+	return 0;
 }
 
-static void tst_cgroup_del_path(const char *cgroup_dir)
+static int cgroup_file_exists(const char * file, const int lineno,
+			      tst_cgroup_item_ptr item)
 {
-	struct tst_cgroup_path *a, *b;
+	const struct tst_cgroup_file *cgf =
+		tst_container_of(item, typeof(*cgf), item);
+	unsigned int i, n = cgf->n;
+	const struct tst_cgroup_location *loc = cgf->locations;
 
-	if (!tst_cgroup_paths)
-		return;
+	for (i = 0; i < n; i++) {
+		if (cgroup_ent_exists(file, lineno, loc + i))
+			return 1;
+	}
 
-	a = b = tst_cgroup_paths;
+	return 0;
+}
 
-	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;
-	};
+static const struct tst_cgroup_item cgroup_file_item = {
+	.exists = cgroup_file_exists,
+};
 
-	if (b == tst_cgroup_paths)
-		tst_cgroup_paths = b->next;
-	else
-		a->next = b->next;
+static void cgroup_file_init(struct tst_cgroup_file *file)
+{
+	file->item = &cgroup_file_item;
+	file->n = 0;
+}
+
+static void cgroup_file_add(struct tst_cgroup_file *file,
+			    const char *name,
+			    const struct tst_cgroup_tree *tree)
+{
+	file->locations[file->n].name = name;
+	file->locations[file->n].tree = tree;
+	file->n++;
+}
+
+static int cgroup_memory_swap_exists(const char * file, const int lineno,
+				     tst_cgroup_item_ptr item)
+{
+	const struct tst_cgroup_memory_swap *c =
+		tst_container_of(item, typeof(*c), item);
 
-	free(b->mnt_path);
-	free(b->new_path);
-	free(b);
+	return cgroup_file_exists(file, lineno, &c->max.item);
 }
 
-void tst_cgroup_mount(enum tst_cgroup_ctrl ctrl, const char *cgroup_dir)
+static const struct tst_cgroup_item cgroup_memory_swap_item = {
+	.exists = cgroup_memory_swap_exists,
+};
+
+static int cgroup_memory_kmem_exists(const char * file, const int lineno,
+				     tst_cgroup_item_ptr item)
 {
-	char *cgroup_new_dir;
-	char knob_path[PATH_MAX];
+	const struct tst_cgroup_memory_kmem *c =
+		tst_container_of(item, typeof(*c), item);
 
-	tst_cg_ver = tst_cgroup_version();
+	return cgroup_file_exists(file, lineno, &c->max.item);
+}
 
-	tst_cgroup_set_path(cgroup_dir);
-	cgroup_new_dir = tst_cgroup_get_path(cgroup_dir);
+static const struct tst_cgroup_item cgroup_memory_kmem_item = {
+	.exists = cgroup_memory_kmem_exists,
+};
 
-	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);
-		}
+static int cgroup_memory_exists(const char * file, const int lineno,
+				tst_cgroup_item_ptr item)
+{
+	const struct tst_cgroup_memory *c =
+		tst_container_of(item, typeof(*c), item);
+
+	return cgroup_file_exists(file, lineno, &c->max.item);
+}
+
+static struct tst_cgroup_item cgroup_memory_item = {
+	.exists = cgroup_memory_exists,
+};
+
+static void cgroup_memory_init(struct tst_cgroup_memory *cgm)
+{
+	cgm->item = &cgroup_memory_item;
+	cgm->ver = 0;
+	cgroup_file_init(&cgm->current);
+	cgroup_file_init(&cgm->max);
+	cgroup_file_init(&cgm->swappiness);
+
+	cgm->swap.item = &cgroup_memory_swap_item;
+	cgroup_file_init(&cgm->swap.current);
+	cgroup_file_init(&cgm->swap.max);
+
+	cgm->kmem.item = &cgroup_memory_kmem_item;
+	cgroup_file_init(&cgm->kmem.current);
+	cgroup_file_init(&cgm->kmem.max);
+}
+
+static void cgroup_memory_add(struct tst_cgroup_memory *cgm,
+			      const struct tst_cgroup_tree *tree)
+{
+	const char *current, *max, *swap_current, *swap_max;
+
+	cgm->ver = tree->root->ver;
+
+	cgroup_file_add(&cgm->kmem.current, "memory.kmem.usage_in_bytes", tree);
+	cgroup_file_add(&cgm->kmem.max, "memory.kmem.limit_in_bytes", tree);
+
+	if (tree->root->ver == TST_CGROUP_V1) {
+		current = "memory.usage_in_bytes";
+		max = "memory.limit_in_bytes";
+		swap_current = "memory.memsw.usage_in_bytes";
+		swap_max = "memory.memsw.limit_in_bytes";
+	} else {
+		current = "memory.current";
+		max = "memory.max";
+		swap_current = "memory.swap.current";
+		swap_max = "memory.swap.max";
 	}
 
-	if (tst_cg_ver & TST_CGROUP_V2) {
-		tst_cgroup2_mount(cgroup_dir, cgroup_new_dir);
+	cgroup_file_add(&cgm->current, current, tree);
+	cgroup_file_add(&cgm->max, max, tree);
+	cgroup_file_add(&cgm->swappiness, "memory.swappiness", tree);
 
-		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);
-		}
+	cgroup_file_add(&cgm->swap.current, swap_current, tree);
+	cgroup_file_add(&cgm->swap.max, swap_max, tree);
+}
+
+static int cgroup_cpuset_exists(const char * file, const int lineno,
+				tst_cgroup_item_ptr item)
+{
+	const struct tst_cgroup_cpuset *c =
+		tst_container_of(item, typeof(*c), item);
+
+	return cgroup_file_exists(file, lineno, &c->cpus.item);
+}
+
+static struct tst_cgroup_item cgroup_cpuset_item = {
+	.exists = cgroup_cpuset_exists,
+};
+
+static void cgroup_cpuset_init(struct tst_cgroup_cpuset *cgc)
+{
+	cgc->item = &cgroup_cpuset_item;
+	cgc->ver = 0;
+
+	cgroup_file_init(&cgc->cpus);
+	cgroup_file_init(&cgc->mems);
+
+	cgroup_file_init(&cgc->memory_migrate);
+}
+
+static void cgroup_cpuset_add(struct tst_cgroup_cpuset *cgc,
+			      const struct tst_cgroup_tree *tree)
+{
+	const char *cpus, *mems, *memory_migrate;
+
+	cgc->ver = tree->root->ver;
+
+	if (tree->root->no_prefix) {
+		cpus = "cpus";
+		mems = "mems";
+		memory_migrate = "memory_migrate";
+	} else {
+		cpus = "cpuset.cpus";
+		mems = "cpuset.mems";
+		memory_migrate = "cpuset.memory_migrate";
 	}
+
+	cgroup_file_add(&cgc->cpus, cpus, tree);
+	cgroup_file_add(&cgc->mems, mems, tree);
+	cgroup_file_add(&cgc->memory_migrate, memory_migrate, tree);
 }
 
-void tst_cgroup_umount(const char *cgroup_dir)
+static void cgroup_cgroup_init(struct tst_cgroup_cgroup *cgg)
 {
-	char *cgroup_new_dir;
+	cgg->item = &cgroup_default_item;
 
-	cgroup_new_dir = tst_cgroup_get_path(cgroup_dir);
-	tst_cgroupN_umount(cgroup_dir, cgroup_new_dir);
-	tst_cgroup_del_path(cgroup_dir);
+	cgroup_file_init(&cgg->procs);
+	cgroup_file_init(&cgg->clone_children);
+	cgroup_file_init(&cgg->subtree_control);
 }
 
-void tst_cgroup_set_knob(const char *cgroup_dir, const char *knob, long value)
+static void cgroup_cgroup_add(struct tst_cgroup_cgroup *cgg,
+			      const struct tst_cgroup_tree *tree)
 {
-	char *cgroup_new_dir;
-	char knob_path[PATH_MAX];
+	if (tree->root->ver == TST_CGROUP_V1) {
+		cgroup_file_add(&cgg->procs, "tasks", tree);
+		cgroup_file_add(&cgg->clone_children, "clone_children", tree);
+		return;
+	}
 
-	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);
+	cgroup_file_add(&cgg->procs, "cgroup.procs", tree);
+	cgroup_file_add(&cgg->subtree_control, "cgroup.subtree_control", tree);
 }
 
-void tst_cgroup_move_current(const char *cgroup_dir)
+static void cgroup_init(struct tst_cgroup *cg)
 {
-	if (tst_cg_ver & TST_CGROUP_V1)
-		tst_cgroup_set_knob(cgroup_dir, "tasks", getpid());
+	cg->item = &cgroup_default_item;
+	cg->n = 0;
 
-	if (tst_cg_ver & TST_CGROUP_V2)
-		tst_cgroup_set_knob(cgroup_dir, "cgroup.procs", getpid());
+	cgroup_cgroup_init(&cg->cgroup);
+	cgroup_cpuset_init(&cg->cpuset);
+	cgroup_memory_init(&cg->memory);
 }
 
-void tst_cgroup_mem_set_maxbytes(const char *cgroup_dir, long memsz)
+static void cgroup_add(struct tst_cgroup *cg, struct tst_cgroup_tree *tree)
 {
-	if (tst_cg_ver & TST_CGROUP_V1)
-		tst_cgroup_set_knob(cgroup_dir, "memory.limit_in_bytes", memsz);
+	uint32_t ss_field = tree->ss_field;
 
-	if (tst_cg_ver & TST_CGROUP_V2)
-		tst_cgroup_set_knob(cgroup_dir, "memory.max", memsz);
+	cg->trees[cg->n++] = tree;
+	cgroup_cgroup_add(&cg->cgroup, tree);
+
+	if (has_css(ss_field, TST_CGROUP_MEMORY))
+		cgroup_memory_add(&cg->memory, tree);
+
+	if (has_css(ss_field, TST_CGROUP_CPUSET))
+		cgroup_cpuset_add(&cg->cpuset, tree);
 }
 
-int tst_cgroup_mem_swapacct_enabled(const char *cgroup_dir)
+struct tst_cgroup *tst_cgroup_mk(const struct tst_cgroup *parent,
+				 const char *name)
 {
-	char *cgroup_new_dir;
-	char knob_path[PATH_MAX];
+	unsigned int i;
+	struct tst_cgroup *cg;
+	struct tst_cgroup_tree *tree;
 
-	cgroup_new_dir = tst_cgroup_get_path(cgroup_dir);
+	if (!parent->n)
+		tst_brk(TBROK, "Trying to make CGroup in empty parent");
 
-	if (tst_cg_ver & TST_CGROUP_V1) {
-		sprintf(knob_path, "%s/%s",
-				cgroup_new_dir, "/memory.memsw.limit_in_bytes");
+	cg = SAFE_MALLOC(sizeof(*cg));
+	cgroup_init(cg);
 
-		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;
-		}
+	for (i = 0; i < parent->n; i++) {
+		tree = SAFE_MALLOC(sizeof(*tree));
+		tst_cgroup_tree_mk(parent->trees[i], name, tree);
+		cgroup_add(cg, tree);
 	}
 
-	if (tst_cg_ver & TST_CGROUP_V2) {
-		sprintf(knob_path, "%s/%s",
-				cgroup_new_dir, "/memory.swap.max");
+	return cg;
+}
 
-		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;
-		}
+struct tst_cgroup *tst_cgroup_rm(struct tst_cgroup *cg)
+{
+	unsigned int i;
+
+	for (i = 0; i < cg->n; i++) {
+		close(cg->trees[i]->dir);
+		SAFE_UNLINKAT(cg->trees[i]->loc.tree->dir,
+			      cg->trees[i]->loc.name,
+			      AT_REMOVEDIR);
+		free(cg->trees[i]);
 	}
 
-	return 0;
+	free(cg);
+	return NULL;
 }
 
-void tst_cgroup_mem_set_maxswap(const char *cgroup_dir, long memsz)
+static struct tst_cgroup *cgroup_from_roots(size_t tree_off)
 {
-	if (tst_cg_ver & TST_CGROUP_V1)
-		tst_cgroup_set_knob(cgroup_dir, "memory.memsw.limit_in_bytes", memsz);
+	struct cgroup_root *r;
+	struct tst_cgroup_tree *t;
+	struct tst_cgroup *cg;
+
+	cg = tst_alloc(sizeof(*cg));
+	cgroup_init(cg);
+
+	for_each_root(r) {
+		t = (typeof(t))(((char *)r) + tree_off);
+
+		if (t->ss_field)
+			cgroup_add(cg, t);
+	}
 
-	if (tst_cg_ver & TST_CGROUP_V2)
-		tst_cgroup_set_knob(cgroup_dir, "memory.swap.max", memsz);
+	if (cg->n)
+		return cg;
+
+	tst_brk(TBROK,
+		"No CGroups found; maybe you forgot to call tst_cgroup_require?");
+
+	return cg;
 }
 
-void tst_cgroup_cpuset_read_files(const char *cgroup_dir, const char *filename,
-	char *retbuf, size_t retbuf_sz)
+const struct tst_cgroup *tst_cgroup_get_default(void)
 {
-	int fd;
-	char *cgroup_new_dir;
-	char knob_path[PATH_MAX];
+	return cgroup_from_roots(offsetof(struct cgroup_root, test));
+}
 
-	cgroup_new_dir = tst_cgroup_get_path(cgroup_dir);
+const struct tst_cgroup *tst_cgroup_get_drain(void)
+{
+	return cgroup_from_roots(offsetof(struct cgroup_root, drain));
+}
 
-	/*
-	 * 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);
+static void cgroup_file_populated(const char *file, const int lineno,
+				  const struct tst_cgroup_file *cgf)
+{
+	if (cgf->n)
+		return;
+
+	tst_brk_(file, lineno, TBROK,
+		 "CGroup item not populated. Maybe missing tst_cgroup_require() or TST_CGROUP_HAS()");
+}
+
+ssize_t safe_cgroup_read(const char *file, const int lineno,
+			 const struct tst_cgroup_file *cgf,
+			 char *out, size_t len)
+{
+	unsigned int i;
+	size_t min_len;
+	const struct tst_cgroup_location *loc = cgf->locations;
+	char buf[BUFSIZ];
+
+	cgroup_file_populated(file, lineno, cgf);
+
+	for (i = 0; i < cgf->n; i++) {
+		TEST(safe_file_readat(file, lineno,
+				      loc[i].tree->dir, loc[i].name, out, len));
+		if (TST_RET < 0)
+			continue;
+
+		min_len = MIN(sizeof(buf), (size_t)TST_RET);
+
+		if (i > 0 && memcmp(out, buf, min_len)) {
+			tst_brk_(file, lineno, TBROK,
+				 "%s has different value across roots",
+				 loc[i].name);
+			break;
+		}
+
+		if (i >= cgf->n)
+			break;
+
+		memcpy(buf, out, min_len);
 	}
 
-	memset(retbuf, 0, retbuf_sz);
-	if (read(fd, retbuf, retbuf_sz) < 0)
-		tst_brk(TBROK | TERRNO, "read %s", knob_path);
+	out[MAX(TST_RET, 0)] = '\0';
 
-	close(fd);
+	return TST_RET;
 }
 
-void tst_cgroup_cpuset_write_files(const char *cgroup_dir, const char *filename, const char *buf)
+void safe_cgroup_printf(const char *file, const int lineno,
+			const struct tst_cgroup_file *cgf,
+			const char *fmt, ...)
 {
-	int fd;
-	char *cgroup_new_dir;
-	char knob_path[PATH_MAX];
+	unsigned int i;
+	const struct tst_cgroup_location *loc = cgf->locations;
+	va_list va;
 
-	cgroup_new_dir = tst_cgroup_get_path(cgroup_dir);
+	cgroup_file_populated(file, lineno, cgf);
 
-	/*
-	 * 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);
+	for (i = 0; i < cgf->n; i++) {
+		va_start(va, fmt);
+		safe_file_vprintfat(file, lineno,
+				    loc[i].tree->dir, loc[i].name, fmt, va);
+		va_end(va);
 	}
+}
 
-	SAFE_WRITE(1, fd, buf, strlen(buf));
+void safe_cgroup_scanf(const char *file, const int lineno,
+		       const struct tst_cgroup_file *cgf,
+		       const char *fmt, ...)
+{
+	va_list va;
+	char buf[BUFSIZ];
+	const struct tst_cgroup_location *loc;
+	ssize_t len = safe_cgroup_read(file, lineno, cgf, buf, sizeof(buf));
+
+	if (len < 1)
+		return;
 
-	close(fd);
+	va_start(va, fmt);
+	if (vsscanf(buf, fmt, va) < 1) {
+		loc = cgf->locations;
+		tst_brk_(file, lineno, TBROK | TERRNO,
+			 "'%s/%s': vsscanf('%s', '%s', ...)",
+			 tst_decode_fd(loc->tree->dir), loc->name, buf, fmt);
+	}
+	va_end(va);
 }
-- 
2.30.0


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [LTP] [RFC PATCH v2 4/7] Add new CGroups API library tests
  2021-03-02 15:25 [LTP] [RFC PATCH v2 0/7] CGroup API rewrite Richard Palethorpe
                   ` (2 preceding siblings ...)
  2021-03-02 15:25 ` [LTP] [RFC PATCH v2 3/7] Add new CGroups Core and Item APIs Richard Palethorpe
@ 2021-03-02 15:25 ` Richard Palethorpe
  2021-03-02 15:25 ` [LTP] [RFC PATCH v2 5/7] docs: Update CGroups API Richard Palethorpe
                   ` (2 subsequent siblings)
  6 siblings, 0 replies; 13+ messages in thread
From: Richard Palethorpe @ 2021-03-02 15:25 UTC (permalink / raw)
  To: ltp

Signed-off-by: Richard Palethorpe <rpalethorpe@suse.com>
---
 lib/newlib_tests/.gitignore     |  2 +
 lib/newlib_tests/test21.c       | 43 +++++++---------
 lib/newlib_tests/tst_cgroup01.c | 51 +++++++++++++++++++
 lib/newlib_tests/tst_cgroup02.c | 87 +++++++++++++++++++++++++++++++++
 4 files changed, 158 insertions(+), 25 deletions(-)
 create mode 100644 lib/newlib_tests/tst_cgroup01.c
 create mode 100644 lib/newlib_tests/tst_cgroup02.c

diff --git a/lib/newlib_tests/.gitignore b/lib/newlib_tests/.gitignore
index 6c2612259..98611e65d 100644
--- a/lib/newlib_tests/.gitignore
+++ b/lib/newlib_tests/.gitignore
@@ -16,6 +16,8 @@ test15
 test16
 tst_capability01
 tst_capability02
+tst_cgroup01
+tst_cgroup02
 tst_device
 tst_safe_fileops
 tst_res_hexd
diff --git a/lib/newlib_tests/test21.c b/lib/newlib_tests/test21.c
index f29a2f702..e939463d7 100644
--- a/lib/newlib_tests/test21.c
+++ b/lib/newlib_tests/test21.c
@@ -8,54 +8,47 @@
  * Tests tst_cgroup.h APIs
  */
 
+#include <stdlib.h>
+
 #include "tst_test.h"
 #include "tst_cgroup.h"
 
-#define PATH_CGROUP1 "/mnt/liwang1"
-#define PATH_CGROUP2 "/mnt/liwang2"
 #define MEMSIZE 1024 * 1024
 
+static const struct tst_cgroup *cg;
+
 static void do_test(void)
 {
 	pid_t pid = SAFE_FORK();
 
 	switch (pid) {
 	case 0:
-		tst_cgroup_move_current(PATH_CGROUP1);
-		tst_cgroup_mem_set_maxbytes(PATH_CGROUP1, MEMSIZE);
-		tst_cgroup_mem_set_maxswap(PATH_CGROUP1, MEMSIZE);
-
-		tst_cgroup_move_current(PATH_CGROUP2);
-
-	break;
+		SAFE_CGROUP_PRINTF(&cg->cgroup.procs, "%d", getpid());
+		SAFE_CGROUP_PRINTF(&cg->memory.max, "%d", MEMSIZE);
+		if (TST_CGROUP_HAS(&cg->memory.swap))
+			SAFE_CGROUP_PRINTF(&cg->memory.swap.max, "%d", MEMSIZE);
+		exit(0);
+		break;
 	default:
-		tst_cgroup_move_current(PATH_TMP_CG_CST);
-
-		tst_cgroup_move_current(PATH_TMP_CG_MEM);
-		tst_cgroup_mem_set_maxbytes(PATH_TMP_CG_MEM, MEMSIZE);
-		tst_cgroup_mem_set_maxswap(PATH_TMP_CG_MEM, MEMSIZE);
+		SAFE_CGROUP_PRINTF(&cg->cgroup.procs, "%d", getpid());
 	break;
 	}
 
-	tst_res(TPASS, "Cgroup mount test");
+	tst_reap_children();
+	tst_res(TPASS, "Cgroup test");
 }
 
 static void setup(void)
 {
-	tst_cgroup_mount(TST_CGROUP_MEMCG, PATH_TMP_CG_MEM);
-	tst_cgroup_mount(TST_CGROUP_MEMCG, PATH_CGROUP1);
-
-	tst_cgroup_mount(TST_CGROUP_CPUSET, PATH_TMP_CG_CST);
-	tst_cgroup_mount(TST_CGROUP_CPUSET, PATH_CGROUP2);
+	/* Omitting the below causes a warning */
+	/* tst_cgroup_require(TST_CGROUP_CPUSET, NULL); */
+	tst_cgroup_require(TST_CGROUP_MEMORY, NULL);
+	cg = tst_cgroup_get_default();
 }
 
 static void cleanup(void)
 {
-	tst_cgroup_umount(PATH_TMP_CG_MEM);
-	tst_cgroup_umount(PATH_CGROUP1);
-
-	tst_cgroup_umount(PATH_TMP_CG_CST);
-	tst_cgroup_umount(PATH_CGROUP2);
+	tst_cgroup_cleanup();
 }
 
 static struct tst_test test = {
diff --git a/lib/newlib_tests/tst_cgroup01.c b/lib/newlib_tests/tst_cgroup01.c
new file mode 100644
index 000000000..36cf065cb
--- /dev/null
+++ b/lib/newlib_tests/tst_cgroup01.c
@@ -0,0 +1,51 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/* Copyright (c) 2021 SUSE LLC */
+
+#include <stdio.h>
+
+#include "tst_test.h"
+#include "tst_cgroup_core.h"
+
+static char *only_mount_v1;
+static char *no_cleanup;
+static struct tst_option opts[] = {
+	{"v", &only_mount_v1, "-v\tOnly try to mount CGroups V1"},
+	{"n", &no_cleanup, "-n\tLeave CGroups created by test"},
+	{NULL, NULL, NULL},
+};
+struct tst_cgroup_opts cgopts;
+
+static void do_test(void)
+{
+	tst_res(TPASS, "pass");
+}
+
+static void setup(void)
+{
+	cgopts.only_mount_v1 = !!only_mount_v1,
+
+	tst_cgroup_scan();
+	tst_cgroup_print_config();
+
+	tst_cgroup_require(TST_CGROUP_MEMORY, &cgopts);
+	tst_cgroup_print_config();
+	tst_cgroup_require(TST_CGROUP_CPUSET, &cgopts);
+	tst_cgroup_print_config();
+}
+
+static void cleanup(void)
+{
+	if (no_cleanup) {
+		tst_res(TINFO, "no cleanup");
+	} else {
+		tst_res(TINFO, "cleanup");
+		tst_cgroup_cleanup();
+	}
+}
+
+static struct tst_test test = {
+	.test_all = do_test,
+	.setup = setup,
+	.cleanup = cleanup,
+	.options = opts,
+};
diff --git a/lib/newlib_tests/tst_cgroup02.c b/lib/newlib_tests/tst_cgroup02.c
new file mode 100644
index 000000000..9799b2dfd
--- /dev/null
+++ b/lib/newlib_tests/tst_cgroup02.c
@@ -0,0 +1,87 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/* Copyright (c) 2021 SUSE LLC */
+
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "tst_test.h"
+#include "tst_cgroup.h"
+
+static char *only_mount_v1;
+static char *no_cleanup;
+static struct tst_option opts[] = {
+	{"v", &only_mount_v1, "-v\tOnly try to mount CGroups V1"},
+	{"n", &no_cleanup, "-n\tLeave CGroups created by test"},
+	{NULL, NULL, NULL},
+};
+static struct tst_cgroup_opts cgopts;
+static const struct tst_cgroup *cg;
+static const struct tst_cgroup *cg_drain;
+static struct tst_cgroup *cg_child;
+
+static void do_test(void)
+{
+	char buf[BUFSIZ];
+	size_t mem;
+
+	if (cg->memory.ver != TST_CGROUP_V1)
+		SAFE_CGROUP_PRINT(&cg->cgroup.subtree_control, "+memory");
+	if (cg->cpuset.ver != TST_CGROUP_V1)
+		SAFE_CGROUP_PRINT(&cg->cgroup.subtree_control, "+cpuset");
+
+	cg_child = tst_cgroup_mk(cg, "child");
+	if (!SAFE_FORK()) {
+		SAFE_CGROUP_PRINTF(&cg_child->cgroup.procs, "%d", getpid());
+
+		SAFE_CGROUP_SCANF(&cg_child->memory.current, "%zu", &mem);
+		tst_res(TPASS, "child/memory.current = %zu", mem);
+		SAFE_CGROUP_PRINTF(&cg_child->memory.max, "%zu", (1UL << 24) - 1);
+		SAFE_CGROUP_PRINTF(&cg_child->memory.swap.max, "%zu", 1UL << 31);
+
+		SAFE_CGROUP_READ(&cg_child->cpuset.mems, buf, sizeof(buf));
+		tst_res(TPASS, "child/cpuset.mems = %s", buf);
+		SAFE_CGROUP_PRINT(&cg_child->cpuset.mems, buf);
+
+		exit(0);
+	}
+
+	SAFE_CGROUP_PRINTF(&cg_child->cgroup.procs, "%d", getpid());
+	SAFE_CGROUP_SCANF(&cg->memory.current, "%zu", &mem);
+	tst_res(TPASS, "memory.current = %zu", mem);
+
+	tst_reap_children();
+	SAFE_CGROUP_PRINTF(&cg_drain->cgroup.procs, "%d", getpid());
+	cg_child = tst_cgroup_rm(cg_child);
+}
+
+static void setup(void)
+{
+	cgopts.only_mount_v1 = !!only_mount_v1,
+
+	tst_cgroup_scan();
+	tst_cgroup_print_config();
+
+	tst_cgroup_require(TST_CGROUP_MEMORY, &cgopts);
+	tst_cgroup_require(TST_CGROUP_CPUSET, &cgopts);
+
+	cg = tst_cgroup_get_default();
+	cg_drain = tst_cgroup_get_drain();
+}
+
+static void cleanup(void)
+{
+	if (cg_child) {
+		SAFE_CGROUP_PRINTF(&cg_drain->cgroup.procs, "%d", getpid());
+		cg_child = tst_cgroup_rm(cg_child);
+	}
+	if (!no_cleanup)
+		tst_cgroup_cleanup();
+}
+
+static struct tst_test test = {
+	.test_all = do_test,
+	.setup = setup,
+	.cleanup = cleanup,
+	.options = opts,
+	.forks_child = 1,
+};
-- 
2.30.0


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [LTP] [RFC PATCH v2 5/7] docs: Update CGroups API
  2021-03-02 15:25 [LTP] [RFC PATCH v2 0/7] CGroup API rewrite Richard Palethorpe
                   ` (3 preceding siblings ...)
  2021-03-02 15:25 ` [LTP] [RFC PATCH v2 4/7] Add new CGroups API library tests Richard Palethorpe
@ 2021-03-02 15:25 ` Richard Palethorpe
  2021-03-09 14:54   ` Cyril Hrubis
  2021-03-02 15:25 ` [LTP] [RFC PATCH v2 6/7] mem: Convert tests to new " Richard Palethorpe
  2021-03-02 15:25 ` [LTP] [RFC PATCH v2 7/7] madvise06: Convert " Richard Palethorpe
  6 siblings, 1 reply; 13+ messages in thread
From: Richard Palethorpe @ 2021-03-02 15:25 UTC (permalink / raw)
  To: ltp

Signed-off-by: Richard Palethorpe <rpalethorpe@suse.com>
---
 doc/test-writing-guidelines.txt | 165 ++++++++++++++++++++++++++++++--
 1 file changed, 156 insertions(+), 9 deletions(-)

diff --git a/doc/test-writing-guidelines.txt b/doc/test-writing-guidelines.txt
index dd1911ceb..7978c3904 100644
--- a/doc/test-writing-guidelines.txt
+++ b/doc/test-writing-guidelines.txt
@@ -2165,48 +2165,195 @@ the field value of file.
 
 2.2.36 Using Control Group
 ^^^^^^^^^^^^^^^^^^^^^^^^^^
-Some of LTP tests need Control Group in their configuration, tst_cgroup.h provides
-APIs to make cgroup unified mounting at setup phase to be possible. The method?is
+
+Some of LTP tests need Control Groups in their configurations, tst_cgroup.h provides
+APIs to discover and use CGroups. The method?is
 extracted from mem.h with the purpose of?extendible for further developing, and
 trying to compatible the current two versions of cgroup.
 
-Considering there are many differences between cgroup v1 and v2, here we capsulate
+Considering there are many differences between cgroup v1 and v2, here we encapsulate
 the detail of cgroup mounting in high-level functions, which will be easier to use
-cgroup without caring about too much technical thing.? ?
+cgroup without caring about too much technical thing.
 
 Also, we do cgroup mount/umount work for the different hierarchy automatically.
+If that is required.
 
 [source,c]
 -------------------------------------------------------------------------------
 #include "tst_test.h"
+#include "tst_cgroup.h"
+
+static const struct tst_cgroup *cg;
 
 static void run(void)
 {
 	...
+	// do test under cgroup
+	...
+}
+
+static void setup(void)
+{
+	tst_cgroup_require(TST_CGROUP_MEMORY, NULL);
+	cg = tst_cgroup_get_default();
+	SAFE_CGROUP_PRINTF(&cg->cgroup.procs, "%d", getpid());
+	SAFE_CGROUP_PRINTF(&cg->memory.max, "%lu", MEMSIZE);
+	if (TST_CGROUP_HAS(&cg->memory.swap))
+		SAFE_CGROUP_PRINTF(&cg->memory.swap.max, "%zu", memsw);
+}
 
-	tst_cgroup_move_current(PATH_TMP_CG_MEM);
-	tst_cgroup_mem_set_maxbytes(PATH_TMP_CG_MEM, MEMSIZE);
+static void cleanup(void)
+{
+	tst_cgroup_cleanup();
+}
 
-	// do test under cgroup
+struct tst_test test = {
+	.setup = setup,
+	.test_all = run,
+	.cleanup = cleanup,
 	...
+};
+-------------------------------------------------------------------------------
+
+Above, we first ensure the memory controller is available on the
+test's CGroup with 'tst_cgroup_require'. We then get a structure,
+'cg', which represents the test's CGroup. Note that
+'tst_cgroup_get_default' should not be called many times, as it is
+allocated in a guarded buffer (See section 2.2.31). Therefor it is
+best to call it once in 'setup' and not 'run' because 'run' may be
+repeated with the '-i' option.
+
+We then write the current processes PID into 'cgroup.procs', which
+moves the current process into the test's CGroup. After which we set
+the maximum memory size by writing to 'memory.max'. If the memory
+controller is mounted on CGroups V1 then the library will actually
+write to 'memory.limit_in_bytes'. As a general rule, if a file exists
+on both CGroup versions, then we use the V2 naming.
+
+Some controller features, such as 'memory.swap', can be
+disabled. Therefor we need to check if they exist before accessing
+them. This can be done with 'TST_CGROUP_HAS' which can be called on
+any control file or feature.
+
+Most tests only require setting a few limits similar to the above. In
+such cases the differences between V1 and V2 are hidden. Setup and
+cleanup is also mostly hidden. However things can get much worse.
+
+[source,c]
+-------------------------------------------------------------------------------
+static const struct tst_cgroup *cg;
+static const struct tst_cgroup *cg_drain;
+static struct tst_cgroup *cg_child;
+
+static void run(void)
+{
+	char buf[BUFSIZ];
+	size_t mem = 0;
+
+	cg_child = tst_cgroup_mk(cg, "child");
+	SAFE_CGROUP_PRINTF(&cg_child->cgroup.procs, "%d", getpid());
+
+	if (cg->memory.ver != TST_CGROUP_V1)
+		SAFE_CGROUP_PRINT(&cg->cgroup.subtree_control, "+memory");
+	if (cg->cpuset.ver != TST_CGROUP_V1)
+		SAFE_CGROUP_PRINT(&cg->cgroup.subtree_control, "+cpuset");
+
+	if (!SAFE_FORK()) {
+		SAFE_CGROUP_PRINTF(&cg_child->cgroup.procs, "%d", getpid());
+
+		if (TST_CGROUP_HAS(&cg_child->memory.swap))
+			SAFE_CGROUP_SCANF(&cg_child->memory.swap.current, "%zu", &mem);
+		SAFE_CGROUP_READ(&cg_child->cpuset.mems, buf, sizeof(buf));
+
+		// Do something with cpuset.mems and memory.current values
+		...
+
+		exit(0);
+	}
+
+	tst_reap_children();
+	SAFE_CGROUP_PRINTF(&cg_drain->cgroup.procs, "%d", getpid());
+	cg_child = tst_cgroup_rm(cg_child);
 }
 
 static void setup(void)
 {
-	tst_cgroup_mount(TST_CGROUP_MEMCG, PATH_TMP_CG_MEM);
+	tst_cgroup_require(TST_CGROUP_MEMORY, NULL);
+	tst_cgroup_require(TST_CGROUP_CPUSET, NULL);
+
+	cg = tst_cgroup_get_default();
+	cg_drain = tst_cgroup_get_drain();
 }
 
 static void cleanup(void)
 {
-	tst_cgroup_umount(PATH_TMP_CG_MEM);
+	if (cg_child) {
+		SAFE_CGROUP_PRINTF(&cg_drain->cgroup.procs, "%d", getpid());
+		cg_child = tst_cgroup_rm(cg_child);
+	}
+
+	tst_cgroup_cleanup();
 }
 
 struct tst_test test = {
+	.setup = setup,
 	.test_all = run,
+	.cleanup = cleanup,
 	...
 };
 -------------------------------------------------------------------------------
 
+Starting with setup; we can see here that we also fetch the 'drain'
+CGroup. This is a shared group (between parallel tests) which may
+contain processe from other tests. It should have default settings and
+these should not be changed by the test. It can be used to remove
+processes from other CGroups incase the hierarchy root is not
+accessible.
+
+In 'run', we first create a child CGroup with 'tst_cgroup_mk'. As we
+create this CGroup in 'run' we should also remove it at the end of
+run. We also need to check if it exists and remove it in cleanup as
+well. Because there are 'SAFE_' functions which may jump to cleanup.
+
+We then move the main test process into the child CGroup. This is
+important as it means that before we destroy the child CGroup we have
+to move the main test process elsewhere. For that we use the 'drain'
+group.
+
+Next we enable the memory and cpuset controller configuration on the
+test CGroup's descendants (i.e. 'cg_child'). This allows each child to
+have its own settings. The file 'cgroup.subtree_control' does not
+exist on V1. Because it is possible to have both V1 and V2 active at
+the same time. We can not simply check if 'subtree_control' exists
+before writing to it. We have to check if a particular controller is
+on V2 before trying to add it to 'subtree_control'. Trying to add a V1
+controller will result in 'ENOENT'.
+
+We then fork a child process and add this to the child CGroup. Within
+the child process we try to read 'memory.swap.current'. It is possible
+that the memory controller was compiled without swap support, so it is
+necessary to check if 'memory.swap' is enabled. That is unless the
+test will never reach the point where 'memory.swap.*' are used without
+swap support.
+
+The parent process waits for the child process to be reaped before
+destroying the child CGroup. So there is no need to transfer the child
+to drain. However the parent process must be moved otherwise we will
+get 'EBUSY' when trying to remove the child CGroup.
+
+Another example of an edge case is the following.
+
+[source,c]
+-------------------------------------------------------------------------------
+	if (cg->memory.ver == TST_CGROUP_V1)
+		SAFE_CGROUP_PRINTF(&cg->memory.swap.max, "%lu", ~0UL);
+	else
+		SAFE_CGROUP_PRINT(&cg->memory.swap.max, "max");
+-------------------------------------------------------------------------------
+
+CGroups V2 introduced a feature where 'memory[.swap].max' could be set
+to "max". This does not appear to work on V1 however.
+
 2.2.37 Require minimum numbers of CPU for a testcase
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-- 
2.30.0


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [LTP] [RFC PATCH v2 6/7] mem: Convert tests to new CGroups API
  2021-03-02 15:25 [LTP] [RFC PATCH v2 0/7] CGroup API rewrite Richard Palethorpe
                   ` (4 preceding siblings ...)
  2021-03-02 15:25 ` [LTP] [RFC PATCH v2 5/7] docs: Update CGroups API Richard Palethorpe
@ 2021-03-02 15:25 ` Richard Palethorpe
  2021-03-02 15:25 ` [LTP] [RFC PATCH v2 7/7] madvise06: Convert " Richard Palethorpe
  6 siblings, 0 replies; 13+ messages in thread
From: Richard Palethorpe @ 2021-03-02 15:25 UTC (permalink / raw)
  To: ltp

Signed-off-by: Richard Palethorpe <rpalethorpe@suse.com>
---
 testcases/kernel/mem/cpuset/cpuset01.c | 34 ++++++++++++--------------
 testcases/kernel/mem/include/mem.h     |  2 +-
 testcases/kernel/mem/ksm/ksm02.c       | 13 +++++++---
 testcases/kernel/mem/ksm/ksm03.c       | 12 ++++++---
 testcases/kernel/mem/ksm/ksm04.c       | 17 +++++++------
 testcases/kernel/mem/lib/mem.c         | 10 +++-----
 testcases/kernel/mem/oom/oom03.c       | 18 ++++++++------
 testcases/kernel/mem/oom/oom04.c       | 19 ++++++++------
 testcases/kernel/mem/oom/oom05.c       | 32 ++++++++++++++----------
 9 files changed, 87 insertions(+), 70 deletions(-)

diff --git a/testcases/kernel/mem/cpuset/cpuset01.c b/testcases/kernel/mem/cpuset/cpuset01.c
index 528c3eddd..9771fb952 100644
--- a/testcases/kernel/mem/cpuset/cpuset01.c
+++ b/testcases/kernel/mem/cpuset/cpuset01.c
@@ -35,6 +35,8 @@
 
 #ifdef HAVE_NUMA_V2
 
+static const struct tst_cgroup *cg;
+
 volatile int end;
 static int *nodes;
 static int nnodes;
@@ -47,15 +49,14 @@ static long count_cpu(void);
 
 static void test_cpuset(void)
 {
-	int child, i, status;
+	int child, i;
 	unsigned long nmask[MAXNODES / BITS_PER_LONG] = { 0 };
-	char mems[BUFSIZ], buf[BUFSIZ];
+	char buf[BUFSIZ];
 
-	tst_cgroup_cpuset_read_files(PATH_TMP_CG_CST, "cpus", buf, BUFSIZ);
-	tst_cgroup_cpuset_write_files(PATH_TMP_CG_CST, "cpus", buf);
-	tst_cgroup_cpuset_read_files(PATH_TMP_CG_CST, "mems", mems, BUFSIZ);
-	tst_cgroup_cpuset_write_files(PATH_TMP_CG_CST, "mems", mems);
-	tst_cgroup_move_current(PATH_TMP_CG_CST);
+	SAFE_CGROUP_READ(&cg->cpuset.cpus, buf, sizeof(buf));
+	SAFE_CGROUP_PRINT(&cg->cpuset.cpus, buf);
+	SAFE_CGROUP_READ(&cg->cpuset.mems, buf, sizeof(buf));
+	SAFE_CGROUP_PRINT(&cg->cpuset.mems, buf);
 
 	child = SAFE_FORK();
 	if (child == 0) {
@@ -69,33 +70,30 @@ static void test_cpuset(void)
 		exit(mem_hog_cpuset(ncpus > 1 ? ncpus : 1));
 	}
 
-	snprintf(buf, BUFSIZ, "%d", nodes[0]);
-	tst_cgroup_cpuset_write_files(PATH_TMP_CG_CST, "mems", buf);
-	snprintf(buf, BUFSIZ, "%d", nodes[1]);
-	tst_cgroup_cpuset_write_files(PATH_TMP_CG_CST, "mems", buf);
+	SAFE_CGROUP_PRINTF(&cg->cpuset.mems, "%d", nodes[0]);
+	SAFE_CGROUP_PRINTF(&cg->cpuset.mems, "%d", nodes[1]);
 
-	SAFE_WAITPID(child, &status, WUNTRACED | WCONTINUED);
-	if (WEXITSTATUS(status) != 0) {
-		tst_res(TFAIL, "child exit status is %d", WEXITSTATUS(status));
-		return;
-	}
+	tst_reap_children();
 
 	tst_res(TPASS, "cpuset test pass");
 }
 
 static void setup(void)
 {
-	tst_cgroup_mount(TST_CGROUP_CPUSET, PATH_TMP_CG_CST);
+	tst_cgroup_require(TST_CGROUP_CPUSET, NULL);
 	ncpus = count_cpu();
 	if (get_allowed_nodes_arr(NH_MEMS | NH_CPUS, &nnodes, &nodes) < 0)
 		tst_brk(TBROK | TERRNO, "get_allowed_nodes_arr");
 	if (nnodes <= 1)
 		tst_brk(TCONF, "requires a NUMA system.");
+
+	cg = tst_cgroup_get_default();
+	SAFE_CGROUP_PRINTF(&cg->cgroup.procs, "%d", getpid());
 }
 
 static void cleanup(void)
 {
-	tst_cgroup_umount(PATH_TMP_CG_CST);
+	tst_cgroup_cleanup();
 }
 
 static void sighandler(int signo LTP_ATTRIBUTE_UNUSED)
diff --git a/testcases/kernel/mem/include/mem.h b/testcases/kernel/mem/include/mem.h
index 42b12a230..549507b0d 100644
--- a/testcases/kernel/mem/include/mem.h
+++ b/testcases/kernel/mem/include/mem.h
@@ -61,7 +61,7 @@ void check_hugepage(void);
 void write_memcg(void);
 
 /* cpuset/memcg - include/tst_cgroup.h */
-void write_cpusets(const char *cgroup_dir, long nd);
+void write_cpusets(const struct tst_cgroup *cg, long nd);
 
 /* shared */
 unsigned int get_a_numa_node(void);
diff --git a/testcases/kernel/mem/ksm/ksm02.c b/testcases/kernel/mem/ksm/ksm02.c
index 51f5d4cca..fe6d7a096 100644
--- a/testcases/kernel/mem/ksm/ksm02.c
+++ b/testcases/kernel/mem/ksm/ksm02.c
@@ -59,6 +59,9 @@
 #ifdef HAVE_NUMA_V2
 #include <numaif.h>
 
+static const struct tst_cgroup *cg;
+static const struct tst_cgroup *cg_drain;
+
 static void verify_ksm(void)
 {
 	unsigned long nmask[MAXNODES / BITS_PER_LONG] = { 0 };
@@ -76,9 +79,10 @@ static void verify_ksm(void)
 	}
 	create_same_memory(size, num, unit);
 
-	write_cpusets(PATH_TMP_CG_CST, node);
-	tst_cgroup_move_current(PATH_TMP_CG_CST);
+	write_cpusets(cg, node);
+	SAFE_CGROUP_PRINTF(&cg->cgroup.procs, "%d", getpid());
 	create_same_memory(size, num, unit);
+	SAFE_CGROUP_PRINTF(&cg_drain->cgroup.procs, "%d", getpid());
 }
 
 static void cleanup(void)
@@ -87,7 +91,7 @@ static void cleanup(void)
 		FILE_PRINTF(PATH_KSM "merge_across_nodes",
 				 "%d", merge_across_nodes);
 
-	tst_cgroup_umount(PATH_TMP_CG_CST);
+	tst_cgroup_cleanup();
 }
 
 static void setup(void)
@@ -103,7 +107,8 @@ static void setup(void)
 		SAFE_FILE_PRINTF(PATH_KSM "merge_across_nodes", "1");
 	}
 
-	tst_cgroup_mount(TST_CGROUP_CPUSET, PATH_TMP_CG_CST);
+	tst_cgroup_require(TST_CGROUP_CPUSET, NULL);
+	cg = tst_cgroup_get_default();
 }
 
 static struct tst_test test = {
diff --git a/testcases/kernel/mem/ksm/ksm03.c b/testcases/kernel/mem/ksm/ksm03.c
index e9949280e..1fde489f6 100644
--- a/testcases/kernel/mem/ksm/ksm03.c
+++ b/testcases/kernel/mem/ksm/ksm03.c
@@ -59,10 +59,10 @@
 #include "mem.h"
 #include "ksm_common.h"
 
+static const struct tst_cgroup *cg;
+
 static void verify_ksm(void)
 {
-	tst_cgroup_move_current(PATH_TMP_CG_MEM);
-	tst_cgroup_mem_set_maxbytes(PATH_TMP_CG_MEM, TESTMEM);
 	create_same_memory(size, num, unit);
 }
 
@@ -78,7 +78,11 @@ static void setup(void)
 	}
 
 	parse_ksm_options(opt_sizestr, &size, opt_numstr, &num, opt_unitstr, &unit);
-	tst_cgroup_mount(TST_CGROUP_MEMCG, PATH_TMP_CG_MEM);
+
+	tst_cgroup_require(TST_CGROUP_MEMORY, NULL);
+	cg = tst_cgroup_get_default();
+	SAFE_CGROUP_PRINTF(&cg->cgroup.procs, "%d", getpid());
+	SAFE_CGROUP_PRINTF(&cg->memory.max, "%lu", TESTMEM);
 }
 
 static void cleanup(void)
@@ -86,7 +90,7 @@ static void cleanup(void)
 	if (access(PATH_KSM "merge_across_nodes", F_OK) == 0)
 		FILE_PRINTF(PATH_KSM "merge_across_nodes",
 				 "%d", merge_across_nodes);
-	tst_cgroup_umount(PATH_TMP_CG_MEM);
+	tst_cgroup_cleanup();
 }
 
 static struct tst_test test = {
diff --git a/testcases/kernel/mem/ksm/ksm04.c b/testcases/kernel/mem/ksm/ksm04.c
index b4ad41831..56d9ae28f 100644
--- a/testcases/kernel/mem/ksm/ksm04.c
+++ b/testcases/kernel/mem/ksm/ksm04.c
@@ -59,6 +59,8 @@
 #ifdef HAVE_NUMA_V2
 #include <numaif.h>
 
+static const struct tst_cgroup *cg;
+
 static void verify_ksm(void)
 {
 	unsigned long nmask[MAXNODES / BITS_PER_LONG] = { 0 };
@@ -67,8 +69,7 @@ static void verify_ksm(void)
 	node = get_a_numa_node();
 	set_node(nmask, node);
 
-	tst_cgroup_move_current(PATH_TMP_CG_MEM);
-	tst_cgroup_mem_set_maxbytes(PATH_TMP_CG_MEM, TESTMEM);
+	SAFE_CGROUP_PRINTF(&cg->memory.max, "%lu", TESTMEM);
 
 	if (set_mempolicy(MPOL_BIND, nmask, MAXNODES) == -1) {
 		if (errno != ENOSYS)
@@ -79,8 +80,7 @@ static void verify_ksm(void)
 	}
 	create_same_memory(size, num, unit);
 
-	write_cpusets(PATH_TMP_CG_CST, node);
-	tst_cgroup_move_current(PATH_TMP_CG_CST);
+	write_cpusets(cg, node);
 	create_same_memory(size, num, unit);
 }
 
@@ -90,8 +90,7 @@ static void cleanup(void)
 		FILE_PRINTF(PATH_KSM "merge_across_nodes",
 				 "%d", merge_across_nodes);
 
-	tst_cgroup_umount(PATH_TMP_CG_MEM);
-	tst_cgroup_umount(PATH_TMP_CG_CST);
+	tst_cgroup_cleanup();
 }
 
 static void setup(void)
@@ -107,8 +106,10 @@ static void setup(void)
 
 	parse_ksm_options(opt_sizestr, &size, opt_numstr, &num, opt_unitstr, &unit);
 
-	tst_cgroup_mount(TST_CGROUP_MEMCG, PATH_TMP_CG_MEM);
-	tst_cgroup_mount(TST_CGROUP_CPUSET, PATH_TMP_CG_CST);
+	tst_cgroup_require(TST_CGROUP_MEMORY, NULL);
+	tst_cgroup_require(TST_CGROUP_CPUSET, NULL);
+	cg = tst_cgroup_get_default();
+	SAFE_CGROUP_PRINTF(&cg->cgroup.procs, "%d", getpid());
 }
 
 static struct tst_test test = {
diff --git a/testcases/kernel/mem/lib/mem.c b/testcases/kernel/mem/lib/mem.c
index 2de3f83a6..b980daf48 100644
--- a/testcases/kernel/mem/lib/mem.c
+++ b/testcases/kernel/mem/lib/mem.c
@@ -629,13 +629,11 @@ static void gather_node_cpus(char *cpus, long nd)
 	cpus[strlen(cpus) - 1] = '\0';
 }
 
-void write_cpusets(const char *cgroup_dir, long nd)
+void write_cpusets(const struct tst_cgroup *cg, long nd)
 {
-	char buf[BUFSIZ];
 	char cpus[BUFSIZ] = "";
 
-	snprintf(buf, BUFSIZ, "%ld", nd);
-	tst_cgroup_cpuset_write_files(cgroup_dir, "mems", buf);
+	SAFE_CGROUP_PRINTF(&cg->cpuset.mems, "%ld", nd);
 
 	gather_node_cpus(cpus, nd);
 	/*
@@ -644,11 +642,11 @@ void write_cpusets(const char *cgroup_dir, long nd)
 	 * the value of cpuset.cpus.
 	 */
 	if (strlen(cpus) != 0) {
-		tst_cgroup_cpuset_write_files(cgroup_dir, "cpus", cpus);
+		SAFE_CGROUP_PRINT(&cg->cpuset.cpus, cpus);
 	} else {
 		tst_res(TINFO, "No CPUs in the node%ld; "
 				"using only CPU0", nd);
-		tst_cgroup_cpuset_write_files(cgroup_dir, "cpus", "0");
+		SAFE_CGROUP_PRINT(&cg->cpuset.cpus, "0");
 	}
 }
 
diff --git a/testcases/kernel/mem/oom/oom03.c b/testcases/kernel/mem/oom/oom03.c
index fc860c660..abc627833 100644
--- a/testcases/kernel/mem/oom/oom03.c
+++ b/testcases/kernel/mem/oom/oom03.c
@@ -36,19 +36,17 @@
 
 #ifdef HAVE_NUMA_V2
 
+static const struct tst_cgroup *cg;
+
 static void verify_oom(void)
 {
 #ifdef TST_ABI32
 	tst_brk(TCONF, "test is not designed for 32-bit system.");
 #endif
-
-	tst_cgroup_move_current(PATH_TMP_CG_MEM);
-	tst_cgroup_mem_set_maxbytes(PATH_TMP_CG_MEM, TESTMEM);
-
 	testoom(0, 0, ENOMEM, 1);
 
-	if (tst_cgroup_mem_swapacct_enabled(PATH_TMP_CG_MEM)) {
-		tst_cgroup_mem_set_maxswap(PATH_TMP_CG_MEM, TESTMEM);
+	if (TST_CGROUP_HAS(&cg->memory.swap)) {
+		SAFE_CGROUP_PRINTF(&cg->memory.swap.max, "%lu", TESTMEM);
 		testoom(0, 1, ENOMEM, 1);
 	}
 
@@ -65,14 +63,18 @@ static void setup(void)
 {
 	overcommit = get_sys_tune("overcommit_memory");
 	set_sys_tune("overcommit_memory", 1, 1);
-	tst_cgroup_mount(TST_CGROUP_MEMCG, PATH_TMP_CG_MEM);
+
+	tst_cgroup_require(TST_CGROUP_MEMORY, NULL);
+	cg = tst_cgroup_get_default();
+	SAFE_CGROUP_PRINTF(&cg->cgroup.procs, "%d", getpid());
+	SAFE_CGROUP_PRINTF(&cg->memory.max, "%lu", TESTMEM);
 }
 
 static void cleanup(void)
 {
 	if (overcommit != -1)
 		set_sys_tune("overcommit_memory", overcommit, 0);
-	tst_cgroup_umount(PATH_TMP_CG_MEM);
+	tst_cgroup_cleanup();
 }
 
 static struct tst_test test = {
diff --git a/testcases/kernel/mem/oom/oom04.c b/testcases/kernel/mem/oom/oom04.c
index 582663275..acffb0436 100644
--- a/testcases/kernel/mem/oom/oom04.c
+++ b/testcases/kernel/mem/oom/oom04.c
@@ -36,24 +36,25 @@
 
 #ifdef HAVE_NUMA_V2
 
+static const struct tst_cgroup *cg;
+
 static void verify_oom(void)
 {
 #ifdef TST_ABI32
 	tst_brk(TCONF, "test is not designed for 32-bit system.");
 #endif
-
-	tst_cgroup_move_current(PATH_TMP_CG_CST);
-
 	tst_res(TINFO, "OOM on CPUSET...");
 	testoom(0, 0, ENOMEM, 1);
 
-	if (is_numa(NULL, NH_MEMS, 2)) {
+	if (is_numa(NULL, NH_MEMS, 2) &&
+	    TST_CGROUP_HAS(&cg->cpuset.memory_migrate)) {
 		/*
 		 * Under NUMA system, the migration of cpuset's memory
 		 * is in charge of cpuset.memory_migrate, we can write
 		 * 1 to cpuset.memory_migrate to enable the migration.
 		 */
-		tst_cgroup_cpuset_write_files(PATH_TMP_CG_CST, "memory_migrate", "1");
+		SAFE_CGROUP_PRINT(&cg->cpuset.memory_migrate, "1");
+
 		tst_res(TINFO, "OOM on CPUSET with mem migrate:");
 		testoom(0, 0, ENOMEM, 1);
 	}
@@ -69,7 +70,8 @@ static void setup(void)
 	overcommit = get_sys_tune("overcommit_memory");
 	set_sys_tune("overcommit_memory", 1, 1);
 
-	tst_cgroup_mount(TST_CGROUP_CPUSET, PATH_TMP_CG_CST);
+	tst_cgroup_require(TST_CGROUP_CPUSET, NULL);
+	cg = tst_cgroup_get_default();
 
 	/*
 	 * Some nodes do not contain memory, so use
@@ -81,14 +83,15 @@ static void setup(void)
 	if (ret < 0)
 		tst_brk(TBROK, "Failed to get a memory node "
 			      "using get_allowed_nodes()");
-	write_cpusets(PATH_TMP_CG_CST, memnode);
+	write_cpusets(cg, memnode);
+	SAFE_CGROUP_PRINTF(&cg->cgroup.procs, "%d", getpid());
 }
 
 static void cleanup(void)
 {
 	if (overcommit != -1)
 		set_sys_tune("overcommit_memory", overcommit, 0);
-	tst_cgroup_umount(PATH_TMP_CG_CST);
+	tst_cgroup_cleanup();
 }
 
 static struct tst_test test = {
diff --git a/testcases/kernel/mem/oom/oom05.c b/testcases/kernel/mem/oom/oom05.c
index 871f302e3..7a7959d79 100644
--- a/testcases/kernel/mem/oom/oom05.c
+++ b/testcases/kernel/mem/oom/oom05.c
@@ -36,6 +36,8 @@
 
 #ifdef HAVE_NUMA_V2
 
+static const struct tst_cgroup *cg;
+
 static void verify_oom(void)
 {
 #ifdef TST_ABI32
@@ -43,9 +45,6 @@ static void verify_oom(void)
 #endif
 
 	tst_res(TINFO, "OOM on CPUSET & MEMCG...");
-	tst_cgroup_move_current(PATH_TMP_CG_MEM);
-	tst_cgroup_move_current(PATH_TMP_CG_CST);
-	tst_cgroup_mem_set_maxbytes(PATH_TMP_CG_MEM, TESTMEM);
 	testoom(0, 0, ENOMEM, 1);
 
 	/*
@@ -53,22 +52,26 @@ static void verify_oom(void)
 	 * is in charge of cpuset.memory_migrate, we can write
 	 * 1 to cpuset.memory_migrate to enable the migration.
 	 */
-	if (is_numa(NULL, NH_MEMS, 2)) {
-		tst_cgroup_cpuset_write_files(PATH_TMP_CG_CST, "memory_migrate", "1");
+	if (is_numa(NULL, NH_MEMS, 2) &&
+	    TST_CGROUP_HAS(&cg->cpuset.memory_migrate)) {
+		SAFE_CGROUP_PRINT(&cg->cpuset.memory_migrate, "1");
 		tst_res(TINFO, "OOM on CPUSET & MEMCG with "
 				"cpuset.memory_migrate=1");
 		testoom(0, 0, ENOMEM, 1);
 	}
 
-	if (tst_cgroup_mem_swapacct_enabled(PATH_TMP_CG_MEM)) {
+	if (TST_CGROUP_HAS(&cg->memory.swap)) {
 		tst_res(TINFO, "OOM on CPUSET & MEMCG with "
 				"special memswap limitation:");
-		tst_cgroup_mem_set_maxswap(PATH_TMP_CG_MEM, TESTMEM);
+		SAFE_CGROUP_PRINTF(&cg->memory.swap.max, "%lu", TESTMEM);
 		testoom(0, 0, ENOMEM, 1);
 
 		tst_res(TINFO, "OOM on CPUSET & MEMCG with "
 				"disabled memswap limitation:");
-		tst_cgroup_mem_set_maxswap(PATH_TMP_CG_MEM, -1);
+		if (cg->memory.ver == TST_CGROUP_V1)
+			SAFE_CGROUP_PRINTF(&cg->memory.swap.max, "%lu", ~0UL);
+		else
+			SAFE_CGROUP_PRINT(&cg->memory.swap.max, "max");
 		testoom(0, 0, ENOMEM, 1);
 	}
 }
@@ -83,8 +86,9 @@ void setup(void)
 	overcommit = get_sys_tune("overcommit_memory");
 	set_sys_tune("overcommit_memory", 1, 1);
 
-	tst_cgroup_mount(TST_CGROUP_MEMCG, PATH_TMP_CG_MEM);
-	tst_cgroup_mount(TST_CGROUP_CPUSET, PATH_TMP_CG_CST);
+	tst_cgroup_require(TST_CGROUP_MEMORY, NULL);
+	tst_cgroup_require(TST_CGROUP_CPUSET, NULL);
+	cg = tst_cgroup_get_default();
 
 	/*
 	 * Some nodes do not contain memory, so use
@@ -96,15 +100,17 @@ void setup(void)
 	if (ret < 0)
 		tst_brk(TBROK, "Failed to get a memory node "
 			      "using get_allowed_nodes()");
-	write_cpusets(PATH_TMP_CG_CST, memnode);
+
+	write_cpusets(cg, memnode);
+	SAFE_CGROUP_PRINTF(&cg->cgroup.procs, "%d", getpid());
+	SAFE_CGROUP_PRINTF(&cg->memory.max, "%lu", TESTMEM);
 }
 
 void cleanup(void)
 {
 	if (overcommit != -1)
 		set_sys_tune("overcommit_memory", overcommit, 0);
-	tst_cgroup_umount(PATH_TMP_CG_MEM);
-	tst_cgroup_umount(PATH_TMP_CG_CST);
+	tst_cgroup_cleanup();
 }
 
 static struct tst_test test = {
-- 
2.30.0


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [LTP] [RFC PATCH v2 7/7] madvise06: Convert to new CGroups API
  2021-03-02 15:25 [LTP] [RFC PATCH v2 0/7] CGroup API rewrite Richard Palethorpe
                   ` (5 preceding siblings ...)
  2021-03-02 15:25 ` [LTP] [RFC PATCH v2 6/7] mem: Convert tests to new " Richard Palethorpe
@ 2021-03-02 15:25 ` Richard Palethorpe
  6 siblings, 0 replies; 13+ messages in thread
From: Richard Palethorpe @ 2021-03-02 15:25 UTC (permalink / raw)
  To: ltp

Signed-off-by: Richard Palethorpe <rpalethorpe@suse.com>
---
 testcases/kernel/syscalls/madvise/madvise06.c | 83 +++++++++----------
 1 file changed, 38 insertions(+), 45 deletions(-)

diff --git a/testcases/kernel/syscalls/madvise/madvise06.c b/testcases/kernel/syscalls/madvise/madvise06.c
index 2099df6c3..209066d77 100644
--- a/testcases/kernel/syscalls/madvise/madvise06.c
+++ b/testcases/kernel/syscalls/madvise/madvise06.c
@@ -3,8 +3,8 @@
  * Copyright (c) 2016 Red Hat, Inc.
  */
 
-/*
- * DESCRIPTION
+/*\
+ * [DESCRIPTION]
  *
  *   Page fault occurs in spite that madvise(WILLNEED) system call is called
  *   to prefetch the page. This issue is reproduced by running a program
@@ -36,13 +36,14 @@
  *   else unexpected, but irrelevant procedure, registers a fault to
  *   our process.
  *
- */
+\*/
 
 #include <errno.h>
 #include <stdio.h>
 #include <sys/mount.h>
 #include <sys/sysinfo.h>
 #include "tst_test.h"
+#include "tst_cgroup.h"
 
 #define CHUNK_SZ (400*1024*1024L)
 #define MEM_LIMIT (CHUNK_SZ / 2)
@@ -50,8 +51,7 @@
 #define PASS_THRESHOLD (CHUNK_SZ / 4)
 #define PASS_THRESHOLD_KB (PASS_THRESHOLD / 1024)
 
-#define MNT_NAME "memory"
-#define GROUP_NAME "madvise06"
+static const struct tst_cgroup *cg;
 
 static const char drop_caches_fname[] = "/proc/sys/vm/drop_caches";
 static int pg_sz, stat_refresh_sup;
@@ -64,17 +64,20 @@ static void check_path(const char *path)
 		tst_brk(TCONF, "file needed: %s\n", path);
 }
 
-#define READ_CGMEM(item)						\
-	({long tst_rval = 0;						\
-	  const char *cgpath = MNT_NAME"/"GROUP_NAME"/memory."item;	\
-	  if (!access(cgpath, R_OK))					\
-		  SAFE_FILE_LINES_SCANF(cgpath, "%ld", &tst_rval);	\
-	  tst_rval;})
+static void print_cgmem(const struct tst_cgroup_file *file)
+{
+	const char *name = file->locations[0].name;
+	long ret;
+
+	if (!TST_CGROUP_HAS(file))
+		return;
+
+	SAFE_CGROUP_SCANF(file, "%ld", &ret);
+	tst_res(TINFO, "\t%s: %ld Kb", name, ret / 1024);
+}
 
 static void meminfo_diag(const char *point)
 {
-	long rval;
-
 	if (stat_refresh_sup)
 		SAFE_FILE_PRINTF("/proc/sys/vm/stat_refresh", "1");
 
@@ -85,16 +88,10 @@ static void meminfo_diag(const char *point)
 		SAFE_READ_MEMINFO("SwapCached:") - init_swap_cached);
 	tst_res(TINFO, "\tCached: %ld Kb",
 		SAFE_READ_MEMINFO("Cached:") - init_cached);
-	tst_res(TINFO, "\tcgmem.usage_in_bytes: %ld Kb",
-		READ_CGMEM("usage_in_bytes") / 1024);
-
-	rval = READ_CGMEM("memsw.usage_in_bytes") / 1024;
-	if (rval)
-		tst_res(TINFO, "\tcgmem.memsw.usage_in_bytes: %ld Kb", rval);
 
-	rval = READ_CGMEM("kmem.usage_in_bytes") / 1024;
-	if (rval)
-		tst_res(TINFO, "\tcgmem.kmem.usage_in_bytes: %ld Kb", rval);
+	print_cgmem(&cg->memory.current);
+	print_cgmem(&cg->memory.swap.current);
+	print_cgmem(&cg->memory.kmem.current);
 }
 
 static void setup(void)
@@ -117,28 +114,24 @@ static void setup(void)
 			2 * CHUNK_SZ);
 	}
 
-	SAFE_MKDIR(MNT_NAME, 0700);
-	if (mount("memory", MNT_NAME, "cgroup", 0, "memory") == -1) {
-		if (errno == ENODEV || errno == ENOENT)
-			tst_brk(TCONF, "memory cgroup needed");
-	}
-	SAFE_MKDIR(MNT_NAME"/"GROUP_NAME, 0700);
-
 	check_path("/proc/self/oom_score_adj");
-	check_path(MNT_NAME"/"GROUP_NAME"/memory.limit_in_bytes");
-	check_path(MNT_NAME"/"GROUP_NAME"/memory.swappiness");
-	check_path(MNT_NAME"/"GROUP_NAME"/tasks");
-
 	SAFE_FILE_PRINTF("/proc/self/oom_score_adj", "%d", -1000);
-	SAFE_FILE_PRINTF(MNT_NAME"/"GROUP_NAME"/memory.limit_in_bytes", "%ld\n",
-			 MEM_LIMIT);
 
-	if (!access(MNT_NAME"/"GROUP_NAME"/memory.memsw.limit_in_bytes", W_OK)) {
-		SAFE_FILE_PRINTF(MNT_NAME"/"GROUP_NAME"/memory.memsw.limit_in_bytes",
-				 "%ld\n", MEMSW_LIMIT);
+	tst_cgroup_require(TST_CGROUP_MEMORY, NULL);
+	cg = tst_cgroup_get_default();
+
+	SAFE_CGROUP_PRINTF(&cg->memory.max, "%ld", MEM_LIMIT);
+	if (TST_CGROUP_HAS(&cg->memory.swap))
+		SAFE_CGROUP_PRINTF(&cg->memory.swap.max, "%ld", MEMSW_LIMIT);
+
+	if (TST_CGROUP_HAS(&cg->memory.swappiness)) {
+		SAFE_CGROUP_PRINT(&cg->memory.swappiness, "60");
+	} else {
+		check_path("/proc/sys/vm/swappiness");
+		SAFE_FILE_PRINTF("/proc/sys/vm/swappiness", "%d", 60);
 	}
-	SAFE_FILE_PRINTF(MNT_NAME"/"GROUP_NAME"/memory.swappiness", "60");
-	SAFE_FILE_PRINTF(MNT_NAME"/"GROUP_NAME"/tasks", "%d\n", getpid());
+
+	SAFE_CGROUP_PRINTF(&cg->cgroup.procs, "%d", getpid());
 
 	meminfo_diag("Initial meminfo, later values are relative to this (except memcg)");
 	init_swap = SAFE_READ_MEMINFO("SwapTotal:") - SAFE_READ_MEMINFO("SwapFree:");
@@ -154,11 +147,7 @@ static void setup(void)
 
 static void cleanup(void)
 {
-	if (!access(MNT_NAME"/tasks", F_OK)) {
-		SAFE_FILE_PRINTF(MNT_NAME"/tasks", "%d\n", getpid());
-		SAFE_RMDIR(MNT_NAME"/"GROUP_NAME);
-		SAFE_UMOUNT(MNT_NAME);
-	}
+	tst_cgroup_cleanup();
 }
 
 static void dirty_pages(char *ptr, long size)
@@ -248,6 +237,10 @@ static struct tst_test test = {
 	.min_kver = "3.10.0",
 	.needs_tmpdir = 1,
 	.needs_root = 1,
+	.save_restore = (const char * const[]) {
+		"?/proc/sys/vm/swappiness",
+		NULL
+	},
 	.tags = (const struct tst_tag[]) {
 		{"linux-git", "55231e5c898c"},
 		{"linux-git", "8de15e920dc8"},
-- 
2.30.0


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [LTP] [RFC PATCH v2 1/7] API: Add safe openat, printfat, readat and unlinkat
  2021-03-02 15:25 ` [LTP] [RFC PATCH v2 1/7] API: Add safe openat, printfat, readat and unlinkat Richard Palethorpe
@ 2021-03-08  8:37   ` Li Wang
  0 siblings, 0 replies; 13+ messages in thread
From: Li Wang @ 2021-03-08  8:37 UTC (permalink / raw)
  To: ltp

Hi Richard,

Richard Palethorpe <rpalethorpe@suse.com> wrote:

...
> +char *tst_decode_fd(int fd);
> +
> +int safe_openat(const char *file, const int lineno,
> +               int dirfd, const char *path, int oflags, ...);
> +
> +ssize_t safe_file_readat(const char *file, const int lineno,
> +                        int dirfd, const char *path, char *buf, size_t
> nbyte);
> +
> +int tst_file_vprintfat(int dirfd, const char *path, const char *fmt,
> va_list va);
> +int tst_file_printfat(int dirfd, const char *path, const char *fmt, ...)
> +                       __attribute__ ((format (printf, 3, 4)));
> +
> +int safe_file_vprintfat(const char *file, const int lineno,
> +                       int dirfd, const char *path,
> +                       const char *fmt, va_list va);
> +
> +int safe_file_printfat(const char *file, const int lineno,
> +                      int dirfd, const char *path, const char *fmt, ...)
> +                       __attribute__ ((format (printf, 5, 6)));
> +
> +int safe_unlinkat(const char *file, const int lineno,
> +                 int dirfd, const char *path, int flags);
>

I guess these prototypes should be moved to "safe_file_ops_fn.h"?
(It seems the author did that on purpose to separate macros and function
declaration)


--- /dev/null
> +++ b/lib/tst_safe_file_ops.c
>

Then we can achieve all the above functions in "lib/safe_file_ops.c"
without creating a new file.



> @@ -0,0 +1,171 @@
> +#define _GNU_SOURCE
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include "lapi/fcntl.h"
> +
> +#define TST_NO_DEFAULT_MAIN
> +#include "tst_test.h"
> +
> +char fd_path[PATH_MAX];
> +
> +char *tst_decode_fd(int fd)
> +{
> +       ssize_t ret;
> +       char proc_path[32];
> +
> +       if (fd < 0)
> +               return "!";
> +
> +       sprintf(proc_path, "/proc/self/fd/%d", fd);
> +       ret = readlink(proc_path, fd_path, sizeof(fd_path));
> +
> +       if (ret < 0)
> +               return "?";
> +
> +       fd_path[ret] = '\0';
> +
> +       return fd_path;
> +}
> +
> +int safe_openat(const char *file, const int lineno,
> +               int dirfd, const char *path, int oflags, ...)
> +{
> +       va_list ap;
> +       int fd;
> +       mode_t mode;
> +
> +       va_start(ap, oflags);
> +       mode = va_arg(ap, int);
> +       va_end(ap);
> +
> +       fd = openat(dirfd, path, oflags, mode);
> +       if (fd > -1)
> +               return fd;
> +
> +       tst_brk_(file, lineno, TBROK | TERRNO,
> +                "openat(%d<%s>, '%s', %o, %o)",
> +                dirfd, tst_decode_fd(dirfd), path, oflags, mode);
> +
> +       return fd;
> +}
> +
> +ssize_t safe_file_readat(const char *file, const int lineno,
> +                        int dirfd, const char *path, char *buf, size_t
> nbyte)
> +{
> +       int fd = safe_openat(file, lineno, dirfd, path, O_RDONLY);
> +       ssize_t rval;
> +
> +       if (fd < 0)
> +               return -1;
> +
> +       rval = safe_read(file, lineno, NULL, 0, fd, buf, nbyte - 1);
> +       if (rval < 0)
> +               return -1;
> +
> +       close(fd);
> +       buf[rval] = '\0';
> +
> +       if (rval >= (ssize_t)nbyte - 1) {
> +               tst_brk_(file, lineno, TBROK,
> +                       "Buffer length %zu too small to read %d<%s>/%s",
> +                       nbyte, dirfd, tst_decode_fd(dirfd), path);
> +       }
> +
> +       return rval;
> +}
> +
> +int tst_file_vprintfat(int dirfd, const char *path, const char *fmt,
> va_list va)
> +{
> +       int fd = openat(dirfd, path, O_WRONLY);
> +
> +       if (fd < 0)
> +               return -1;
> +
> +       TEST(vdprintf(fd, fmt, va));
> +       close(fd);
> +
> +       if (TST_RET < 0) {
> +               errno = TST_ERR;
> +               return -2;
> +       }
> +
> +       return TST_RET;
> +}
> +
> +int tst_file_printfat(int dirfd, const char *path, const char *fmt, ...)
> +{
> +       va_list va;
> +       int rval;
> +
> +       va_start(va, fmt);
> +       rval = tst_file_vprintfat(dirfd, path, fmt, va);
> +       va_end(va);
> +
> +       return rval;
> +}
> +
> +int safe_file_vprintfat(const char *file, const int lineno,
> +                       int dirfd, const char *path,
> +                       const char *fmt, va_list va)
> +{
> +       char buf[16];
> +       va_list vac;
> +       int rval;
> +
> +       va_copy(vac, va);
> +
> +       TEST(tst_file_vprintfat(dirfd, path, fmt, va));
> +
> +       if (TST_RET == -2) {
> +               rval = vsnprintf(buf, sizeof(buf), fmt, vac);
> +               va_end(vac);
> +
> +               if (rval >= (ssize_t)sizeof(buf))
> +                       strcpy(buf + sizeof(buf) - 5, "...");
> +
> +               tst_brk_(file, lineno, TBROK | TTERRNO,
> +                        "vdprintf(%d<%s>, '%s', '%s'<%s>)",
> +                        dirfd, tst_decode_fd(dirfd), path, fmt,
> +                        rval > 0 ? buf : "???");
> +               return -1;
> +       }
> +
> +       va_end(vac);
> +
> +       if (TST_RET == -1) {
> +               tst_brk_(file, lineno, TBROK | TTERRNO,
> +                       "openat(%d<%s>, '%s', O_WRONLY)",
> +                       dirfd, tst_decode_fd(dirfd), path);
> +       }
> +
> +       return TST_RET;
> +}
> +
> +int safe_file_printfat(const char *file, const int lineno,
> +                      int dirfd, const char *path,
> +                      const char *fmt, ...)
> +{
> +       va_list va;
> +       int rval;
> +
> +       va_start(va, fmt);
> +       rval = safe_file_vprintfat(file, lineno, dirfd, path, fmt, va);
> +       va_end(va);
> +
> +       return rval;
> +}
> +
> +int safe_unlinkat(const char *file, const int lineno,
> +                 int dirfd, const char *path, int flags)
> +{
> +       int rval = unlinkat(dirfd, path, flags);
> +
> +       if (rval > -1)
> +               return rval;
> +
> +       tst_brk_(file, lineno, TBROK | TERRNO,
> +                "unlinkat(%d<%s>, '%s', %s)", dirfd,
> tst_decode_fd(dirfd), path,
> +                flags == AT_REMOVEDIR ? "AT_REMOVEDIR" : (flags ? "?" :
> "0"));
> +
> +       return rval;
> +}
> --
> 2.30.0
>
>

-- 
Regards,
Li Wang
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.linux.it/pipermail/ltp/attachments/20210308/2850f86f/attachment-0001.htm>

^ permalink raw reply	[flat|nested] 13+ messages in thread

* [LTP] [RFC PATCH v2 2/7] API: Add macro for the container_of trick
  2021-03-02 15:25 ` [LTP] [RFC PATCH v2 2/7] API: Add macro for the container_of trick Richard Palethorpe
@ 2021-03-08  8:39   ` Li Wang
  0 siblings, 0 replies; 13+ messages in thread
From: Li Wang @ 2021-03-08  8:39 UTC (permalink / raw)
  To: ltp

On Tue, Mar 2, 2021 at 11:28 PM Richard Palethorpe <rpalethorpe@suse.com>
wrote:

> Signed-off-by: Richard Palethorpe <rpalethorpe@suse.com>
> ---
>  include/tst_common.h | 5 +++++
>  1 file changed, 5 insertions(+)
>
> diff --git a/include/tst_common.h b/include/tst_common.h
> index fd7a900d4..317925d1d 100644
> --- a/include/tst_common.h
> +++ b/include/tst_common.h
> @@ -83,4 +83,9 @@
>  #define TST_RES_SUPPORTS_TCONF_TFAIL_TINFO_TPASS_TWARN(condition) \
>         TST_BUILD_BUG_ON(condition)
>
> +#define tst_container_of(ptr, type, member) ({              \
>

What about using uppercase here?  i.e  TST_CONTAINER_OF(...)



> +       const typeof( ((type *)0)->member ) *__mptr = (ptr); \
> +       (type *)( (char *)__mptr - offsetof(type,member) );  \
> +})
> +
>  #endif /* TST_COMMON_H__ */
> --
> 2.30.0
>
>

-- 
Regards,
Li Wang
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.linux.it/pipermail/ltp/attachments/20210308/963b416b/attachment.htm>

^ permalink raw reply	[flat|nested] 13+ messages in thread

* [LTP] [RFC PATCH v2 3/7] Add new CGroups Core and Item APIs
  2021-03-02 15:25 ` [LTP] [RFC PATCH v2 3/7] Add new CGroups Core and Item APIs Richard Palethorpe
@ 2021-03-08  9:04   ` Li Wang
  0 siblings, 0 replies; 13+ messages in thread
From: Li Wang @ 2021-03-08  9:04 UTC (permalink / raw)
  To: ltp

Hi Richard,

Thanks for your refactor, this is much better than what I have written
before:).

But I have to say this version is complicated since there are many
interlaced structures defined, especially for the cgroup-item part.
And my concern is that it might unfriendly to maintainers and users.

Anyway, I still need time to understand the whole part and will
try to reduce something to make it more simplified if I can.

-- 
Regards,
Li Wang
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.linux.it/pipermail/ltp/attachments/20210308/eb9155fd/attachment.htm>

^ permalink raw reply	[flat|nested] 13+ messages in thread

* [LTP] [RFC PATCH v2 5/7] docs: Update CGroups API
  2021-03-02 15:25 ` [LTP] [RFC PATCH v2 5/7] docs: Update CGroups API Richard Palethorpe
@ 2021-03-09 14:54   ` Cyril Hrubis
  2021-03-09 15:46     ` Richard Palethorpe
  0 siblings, 1 reply; 13+ messages in thread
From: Cyril Hrubis @ 2021-03-09 14:54 UTC (permalink / raw)
  To: ltp

Hi!
> +static void setup(void)
> +{
> +	tst_cgroup_require(TST_CGROUP_MEMORY, NULL);
> +	cg = tst_cgroup_get_default();
> +	SAFE_CGROUP_PRINTF(&cg->cgroup.procs, "%d", getpid());
> +	SAFE_CGROUP_PRINTF(&cg->memory.max, "%lu", MEMSIZE);

I've been looking at the API for a while and I do not understand why we
need to create the tst_cgroup_files for each supported controller and
each node in the hierarchy. Why can't we make this more driver-like? All
we need to know is the controller type, cgroup version and file we want
to read/write.

If I were designing the API I would have made these so that they take a
pointer to a cgroup node, controller type and filename, then the
printf-like formatting.

So the end result would look like:

	SAFE_CGROUP_PRINTF(cg, TST_CGROUP_MEMORY, "memory.max", "%lu", MEMSIZE);

Which would be build on the top of:

int tst_cgroup_open(struct tst_cgroup *cg, enum tst_cgroup_ctrl ctrl, const char *fname);

And instead of storing the file structures into the tst_cgroup structure
we would translate the v2 to v1 on the fly. We would have to open and
close the files on each printf/scanf but I do not think that it's
unreasonable given the simplification of the interface.

We would end up with a simple translation tables for different
controllers instead of the structures that are allocated for each node
then, such as:

static const struct cgroup_map cgroup_memory_map[] = {
	{"memory.usage_in_bytes", "memory.current"},
	{"memory.limit_in_bytes", "memory.max"},
	{"memory.memsw.usage_in_bytes", "memory.swap.current"},
	{"memory.memsw.limitin_bytes", "memory.swap.max"},
	{}
};

Or is there a reason why this cannot be done so?

-- 
Cyril Hrubis
chrubis@suse.cz

^ permalink raw reply	[flat|nested] 13+ messages in thread

* [LTP] [RFC PATCH v2 5/7] docs: Update CGroups API
  2021-03-09 14:54   ` Cyril Hrubis
@ 2021-03-09 15:46     ` Richard Palethorpe
  0 siblings, 0 replies; 13+ messages in thread
From: Richard Palethorpe @ 2021-03-09 15:46 UTC (permalink / raw)
  To: ltp

Hello,

Cyril Hrubis <chrubis@suse.cz> writes:

> Hi!
>> +static void setup(void)
>> +{
>> +	tst_cgroup_require(TST_CGROUP_MEMORY, NULL);
>> +	cg = tst_cgroup_get_default();
>> +	SAFE_CGROUP_PRINTF(&cg->cgroup.procs, "%d", getpid());
>> +	SAFE_CGROUP_PRINTF(&cg->memory.max, "%lu", MEMSIZE);
>
> I've been looking at the API for a while and I do not understand why we
> need to create the tst_cgroup_files for each supported controller and
> each node in the hierarchy. Why can't we make this more driver-like? All
> we need to know is the controller type, cgroup version and file we want
> to read/write.
>
> If I were designing the API I would have made these so that they take a
> pointer to a cgroup node, controller type and filename, then the
> printf-like formatting.
>
> So the end result would look like:
>
> 	SAFE_CGROUP_PRINTF(cg, TST_CGROUP_MEMORY, "memory.max", "%lu", MEMSIZE);

If we use V2 naming the controller is always specified in the file
name. So we can just write.

SAFE_CGROUP_PRINTF(cg, "memory.max", ...);

So both you and Li Wang think this is too complex and I wasn't sure, so
I will respin it with a lookup table instead.

>
> Which would be build on the top of:
>
> int tst_cgroup_open(struct tst_cgroup *cg, enum tst_cgroup_ctrl ctrl, const char *fname);
>
> And instead of storing the file structures into the tst_cgroup structure
> we would translate the v2 to v1 on the fly. We would have to open and
> close the files on each printf/scanf but I do not think that it's
> unreasonable given the simplification of the interface.
>
> We would end up with a simple translation tables for different
> controllers instead of the structures that are allocated for each node
> then, such as:
>
> static const struct cgroup_map cgroup_memory_map[] = {
> 	{"memory.usage_in_bytes", "memory.current"},
> 	{"memory.limit_in_bytes", "memory.max"},
> 	{"memory.memsw.usage_in_bytes", "memory.swap.current"},
> 	{"memory.memsw.limitin_bytes", "memory.swap.max"},
> 	{}
> };
>
> Or is there a reason why this cannot be done so?
>
> -- 
> Cyril Hrubis
> chrubis@suse.cz


-- 
Thank you,
Richard.

^ permalink raw reply	[flat|nested] 13+ messages in thread

end of thread, other threads:[~2021-03-09 15:46 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-03-02 15:25 [LTP] [RFC PATCH v2 0/7] CGroup API rewrite Richard Palethorpe
2021-03-02 15:25 ` [LTP] [RFC PATCH v2 1/7] API: Add safe openat, printfat, readat and unlinkat Richard Palethorpe
2021-03-08  8:37   ` Li Wang
2021-03-02 15:25 ` [LTP] [RFC PATCH v2 2/7] API: Add macro for the container_of trick Richard Palethorpe
2021-03-08  8:39   ` Li Wang
2021-03-02 15:25 ` [LTP] [RFC PATCH v2 3/7] Add new CGroups Core and Item APIs Richard Palethorpe
2021-03-08  9:04   ` Li Wang
2021-03-02 15:25 ` [LTP] [RFC PATCH v2 4/7] Add new CGroups API library tests Richard Palethorpe
2021-03-02 15:25 ` [LTP] [RFC PATCH v2 5/7] docs: Update CGroups API Richard Palethorpe
2021-03-09 14:54   ` Cyril Hrubis
2021-03-09 15:46     ` Richard Palethorpe
2021-03-02 15:25 ` [LTP] [RFC PATCH v2 6/7] mem: Convert tests to new " Richard Palethorpe
2021-03-02 15:25 ` [LTP] [RFC PATCH v2 7/7] madvise06: Convert " Richard Palethorpe

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.