blob: d4c93db798173464110359dc3b5b4593845f73a0 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* 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 CGroups API tries to present a consistent interface to the
* many possible CGroup configurations a system could have.
*
* 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 similar 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.
*
* Often 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
#include <sys/types.h>
/* CGroups Kernel API version */
enum tst_cgroup_ver {
TST_CGROUP_V1 = 1,
TST_CGROUP_V2 = 2,
};
/* 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;
};
/* A Control Group in LTP's aggregated hierarchy */
struct tst_cgroup_group;
/* 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(const char *const ctrl_name,
const struct tst_cgroup_opts *const options)
__attribute__ ((nonnull (1)));
/* Tear down any CGroups created by calls to tst_cgroup_require */
void tst_cgroup_cleanup(void);
/* 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_group *tst_cgroup_get_test_group(void)
__attribute__ ((warn_unused_result));
/* Get the shared drain group. Also should be called from setup */
const struct tst_cgroup_group *tst_cgroup_get_drain_group(void)
__attribute__ ((warn_unused_result));
/* Create a descendant CGroup */
struct tst_cgroup_group *
tst_cgroup_group_mk(const struct tst_cgroup_group *const parent,
const char *const group_name)
__attribute__ ((nonnull, warn_unused_result));
/* Remove a descendant CGroup */
struct tst_cgroup_group *
tst_cgroup_group_rm(struct tst_cgroup_group *const cg)
__attribute__ ((nonnull, warn_unused_result));
#define TST_CGROUP_VER(cg, ctrl_name) \
tst_cgroup_ver(__FILE__, __LINE__, (cg), (ctrl_name))
enum tst_cgroup_ver tst_cgroup_ver(const char *const file, const int lineno,
const struct tst_cgroup_group *const cg,
const char *const ctrl_name)
__attribute__ ((nonnull, warn_unused_result));
#define SAFE_CGROUP_HAS(cg, file_name) \
safe_cgroup_has(__FILE__, __LINE__, (cg), (file_name))
int safe_cgroup_has(const char *const file, const int lineno,
const struct tst_cgroup_group *const cg,
const char *const file_name)
__attribute__ ((nonnull, warn_unused_result));
#define SAFE_CGROUP_READ(cg, file_name, out, len) \
safe_cgroup_read(__FILE__, __LINE__, \
(cg), (file_name), (out), (len))
ssize_t safe_cgroup_read(const char *const file, const int lineno,
const struct tst_cgroup_group *const cg,
const char *const file_name,
char *const out, const size_t len)
__attribute__ ((nonnull));
#define SAFE_CGROUP_PRINTF(cg, file_name, fmt, ...) \
safe_cgroup_printf(__FILE__, __LINE__, \
(cg), (file_name), (fmt), __VA_ARGS__)
#define SAFE_CGROUP_PRINT(cg, file_name, str) \
safe_cgroup_printf(__FILE__, __LINE__, (cg), (file_name), "%s", (str))
void safe_cgroup_printf(const char *const file, const int lineno,
const struct tst_cgroup_group *const cg,
const char *const file_name,
const char *const fmt, ...)
__attribute__ ((format (printf, 5, 6), nonnull));
#define SAFE_CGROUP_SCANF(cg, file_name, fmt, ...) \
safe_cgroup_scanf(__FILE__, __LINE__, \
(cg), (file_name), (fmt), __VA_ARGS__)
void safe_cgroup_scanf(const char *file, const int lineno,
const struct tst_cgroup_group *const cg,
const char *const file_name,
const char *fmt, ...)
__attribute__ ((format (scanf, 5, 6), nonnull));
#endif /* TST_CGROUP_H */