301 lines
7.8 KiB
C
301 lines
7.8 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/* Copyright (c) 2022 Google */
|
|
|
|
#include <test_progs.h>
|
|
#include <bpf/libbpf.h>
|
|
#include <bpf/btf.h>
|
|
#include "cgroup_iter.skel.h"
|
|
#include "cgroup_helpers.h"
|
|
|
|
#define ROOT 0
|
|
#define PARENT 1
|
|
#define CHILD1 2
|
|
#define CHILD2 3
|
|
#define NUM_CGROUPS 4
|
|
|
|
#define PROLOGUE "prologue\n"
|
|
#define EPILOGUE "epilogue\n"
|
|
|
|
static const char *cg_path[] = {
|
|
"/", "/parent", "/parent/child1", "/parent/child2"
|
|
};
|
|
|
|
static int cg_fd[] = {-1, -1, -1, -1};
|
|
static unsigned long long cg_id[] = {0, 0, 0, 0};
|
|
static char expected_output[64];
|
|
|
|
static int setup_cgroups(void)
|
|
{
|
|
int fd, i = 0;
|
|
|
|
for (i = 0; i < NUM_CGROUPS; i++) {
|
|
fd = create_and_get_cgroup(cg_path[i]);
|
|
if (fd < 0)
|
|
return fd;
|
|
|
|
cg_fd[i] = fd;
|
|
cg_id[i] = get_cgroup_id(cg_path[i]);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void cleanup_cgroups(void)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < NUM_CGROUPS; i++)
|
|
close(cg_fd[i]);
|
|
}
|
|
|
|
static void read_from_cgroup_iter(struct bpf_program *prog, int cgroup_fd,
|
|
int order, const char *testname)
|
|
{
|
|
DECLARE_LIBBPF_OPTS(bpf_iter_attach_opts, opts);
|
|
union bpf_iter_link_info linfo;
|
|
struct bpf_link *link;
|
|
int len, iter_fd;
|
|
static char buf[128];
|
|
size_t left;
|
|
char *p;
|
|
|
|
memset(&linfo, 0, sizeof(linfo));
|
|
linfo.cgroup.cgroup_fd = cgroup_fd;
|
|
linfo.cgroup.order = order;
|
|
opts.link_info = &linfo;
|
|
opts.link_info_len = sizeof(linfo);
|
|
|
|
link = bpf_program__attach_iter(prog, &opts);
|
|
if (!ASSERT_OK_PTR(link, "attach_iter"))
|
|
return;
|
|
|
|
iter_fd = bpf_iter_create(bpf_link__fd(link));
|
|
if (iter_fd < 0)
|
|
goto free_link;
|
|
|
|
memset(buf, 0, sizeof(buf));
|
|
left = ARRAY_SIZE(buf);
|
|
p = buf;
|
|
while ((len = read(iter_fd, p, left)) > 0) {
|
|
p += len;
|
|
left -= len;
|
|
}
|
|
|
|
ASSERT_STREQ(buf, expected_output, testname);
|
|
|
|
/* read() after iter finishes should be ok. */
|
|
if (len == 0)
|
|
ASSERT_OK(read(iter_fd, buf, sizeof(buf)), "second_read");
|
|
|
|
close(iter_fd);
|
|
free_link:
|
|
bpf_link__destroy(link);
|
|
}
|
|
|
|
/* Invalid cgroup. */
|
|
static void test_invalid_cgroup(struct cgroup_iter *skel)
|
|
{
|
|
DECLARE_LIBBPF_OPTS(bpf_iter_attach_opts, opts);
|
|
union bpf_iter_link_info linfo;
|
|
struct bpf_link *link;
|
|
|
|
memset(&linfo, 0, sizeof(linfo));
|
|
linfo.cgroup.cgroup_fd = (__u32)-1;
|
|
opts.link_info = &linfo;
|
|
opts.link_info_len = sizeof(linfo);
|
|
|
|
link = bpf_program__attach_iter(skel->progs.cgroup_id_printer, &opts);
|
|
ASSERT_ERR_PTR(link, "attach_iter");
|
|
bpf_link__destroy(link);
|
|
}
|
|
|
|
/* Specifying both cgroup_fd and cgroup_id is invalid. */
|
|
static void test_invalid_cgroup_spec(struct cgroup_iter *skel)
|
|
{
|
|
DECLARE_LIBBPF_OPTS(bpf_iter_attach_opts, opts);
|
|
union bpf_iter_link_info linfo;
|
|
struct bpf_link *link;
|
|
|
|
memset(&linfo, 0, sizeof(linfo));
|
|
linfo.cgroup.cgroup_fd = (__u32)cg_fd[PARENT];
|
|
linfo.cgroup.cgroup_id = (__u64)cg_id[PARENT];
|
|
opts.link_info = &linfo;
|
|
opts.link_info_len = sizeof(linfo);
|
|
|
|
link = bpf_program__attach_iter(skel->progs.cgroup_id_printer, &opts);
|
|
ASSERT_ERR_PTR(link, "attach_iter");
|
|
bpf_link__destroy(link);
|
|
}
|
|
|
|
/* Preorder walk prints parent and child in order. */
|
|
static void test_walk_preorder(struct cgroup_iter *skel)
|
|
{
|
|
snprintf(expected_output, sizeof(expected_output),
|
|
PROLOGUE "%8llu\n%8llu\n%8llu\n" EPILOGUE,
|
|
cg_id[PARENT], cg_id[CHILD1], cg_id[CHILD2]);
|
|
|
|
read_from_cgroup_iter(skel->progs.cgroup_id_printer, cg_fd[PARENT],
|
|
BPF_CGROUP_ITER_DESCENDANTS_PRE, "preorder");
|
|
}
|
|
|
|
/* Postorder walk prints child and parent in order. */
|
|
static void test_walk_postorder(struct cgroup_iter *skel)
|
|
{
|
|
snprintf(expected_output, sizeof(expected_output),
|
|
PROLOGUE "%8llu\n%8llu\n%8llu\n" EPILOGUE,
|
|
cg_id[CHILD1], cg_id[CHILD2], cg_id[PARENT]);
|
|
|
|
read_from_cgroup_iter(skel->progs.cgroup_id_printer, cg_fd[PARENT],
|
|
BPF_CGROUP_ITER_DESCENDANTS_POST, "postorder");
|
|
}
|
|
|
|
/* Walking parents prints parent and then root. */
|
|
static void test_walk_ancestors_up(struct cgroup_iter *skel)
|
|
{
|
|
/* terminate the walk when ROOT is met. */
|
|
skel->bss->terminal_cgroup = cg_id[ROOT];
|
|
|
|
snprintf(expected_output, sizeof(expected_output),
|
|
PROLOGUE "%8llu\n%8llu\n" EPILOGUE,
|
|
cg_id[PARENT], cg_id[ROOT]);
|
|
|
|
read_from_cgroup_iter(skel->progs.cgroup_id_printer, cg_fd[PARENT],
|
|
BPF_CGROUP_ITER_ANCESTORS_UP, "ancestors_up");
|
|
|
|
skel->bss->terminal_cgroup = 0;
|
|
}
|
|
|
|
/* Early termination prints parent only. */
|
|
static void test_early_termination(struct cgroup_iter *skel)
|
|
{
|
|
/* terminate the walk after the first element is processed. */
|
|
skel->bss->terminate_early = 1;
|
|
|
|
snprintf(expected_output, sizeof(expected_output),
|
|
PROLOGUE "%8llu\n" EPILOGUE, cg_id[PARENT]);
|
|
|
|
read_from_cgroup_iter(skel->progs.cgroup_id_printer, cg_fd[PARENT],
|
|
BPF_CGROUP_ITER_DESCENDANTS_PRE, "early_termination");
|
|
|
|
skel->bss->terminate_early = 0;
|
|
}
|
|
|
|
/* Waling self prints self only. */
|
|
static void test_walk_self_only(struct cgroup_iter *skel)
|
|
{
|
|
snprintf(expected_output, sizeof(expected_output),
|
|
PROLOGUE "%8llu\n" EPILOGUE, cg_id[PARENT]);
|
|
|
|
read_from_cgroup_iter(skel->progs.cgroup_id_printer, cg_fd[PARENT],
|
|
BPF_CGROUP_ITER_SELF_ONLY, "self_only");
|
|
}
|
|
|
|
static void test_walk_dead_self_only(struct cgroup_iter *skel)
|
|
{
|
|
DECLARE_LIBBPF_OPTS(bpf_iter_attach_opts, opts);
|
|
char expected_output[128], buf[128];
|
|
const char *cgrp_name = "/dead";
|
|
union bpf_iter_link_info linfo;
|
|
int len, cgrp_fd, iter_fd;
|
|
struct bpf_link *link;
|
|
size_t left;
|
|
char *p;
|
|
|
|
cgrp_fd = create_and_get_cgroup(cgrp_name);
|
|
if (!ASSERT_GE(cgrp_fd, 0, "create cgrp"))
|
|
return;
|
|
|
|
/* The cgroup will be dead during read() iteration, so it only has
|
|
* epilogue in the output
|
|
*/
|
|
snprintf(expected_output, sizeof(expected_output), EPILOGUE);
|
|
|
|
memset(&linfo, 0, sizeof(linfo));
|
|
linfo.cgroup.cgroup_fd = cgrp_fd;
|
|
linfo.cgroup.order = BPF_CGROUP_ITER_SELF_ONLY;
|
|
opts.link_info = &linfo;
|
|
opts.link_info_len = sizeof(linfo);
|
|
|
|
link = bpf_program__attach_iter(skel->progs.cgroup_id_printer, &opts);
|
|
if (!ASSERT_OK_PTR(link, "attach_iter"))
|
|
goto close_cgrp;
|
|
|
|
iter_fd = bpf_iter_create(bpf_link__fd(link));
|
|
if (!ASSERT_GE(iter_fd, 0, "iter_create"))
|
|
goto free_link;
|
|
|
|
/* Close link fd and cgroup fd */
|
|
bpf_link__destroy(link);
|
|
close(cgrp_fd);
|
|
|
|
/* Remove cgroup to mark it as dead */
|
|
remove_cgroup(cgrp_name);
|
|
|
|
/* Two kern_sync_rcu() and usleep() pairs are used to wait for the
|
|
* releases of cgroup css, and the last kern_sync_rcu() and usleep()
|
|
* pair is used to wait for the free of cgroup itself.
|
|
*/
|
|
kern_sync_rcu();
|
|
usleep(8000);
|
|
kern_sync_rcu();
|
|
usleep(8000);
|
|
kern_sync_rcu();
|
|
usleep(1000);
|
|
|
|
memset(buf, 0, sizeof(buf));
|
|
left = ARRAY_SIZE(buf);
|
|
p = buf;
|
|
while ((len = read(iter_fd, p, left)) > 0) {
|
|
p += len;
|
|
left -= len;
|
|
}
|
|
|
|
ASSERT_STREQ(buf, expected_output, "dead cgroup output");
|
|
|
|
/* read() after iter finishes should be ok. */
|
|
if (len == 0)
|
|
ASSERT_OK(read(iter_fd, buf, sizeof(buf)), "second_read");
|
|
|
|
close(iter_fd);
|
|
return;
|
|
free_link:
|
|
bpf_link__destroy(link);
|
|
close_cgrp:
|
|
close(cgrp_fd);
|
|
}
|
|
|
|
void test_cgroup_iter(void)
|
|
{
|
|
struct cgroup_iter *skel = NULL;
|
|
|
|
if (setup_cgroup_environment())
|
|
return;
|
|
|
|
if (setup_cgroups())
|
|
goto out;
|
|
|
|
skel = cgroup_iter__open_and_load();
|
|
if (!ASSERT_OK_PTR(skel, "cgroup_iter__open_and_load"))
|
|
goto out;
|
|
|
|
if (test__start_subtest("cgroup_iter__invalid_cgroup"))
|
|
test_invalid_cgroup(skel);
|
|
if (test__start_subtest("cgroup_iter__invalid_cgroup_spec"))
|
|
test_invalid_cgroup_spec(skel);
|
|
if (test__start_subtest("cgroup_iter__preorder"))
|
|
test_walk_preorder(skel);
|
|
if (test__start_subtest("cgroup_iter__postorder"))
|
|
test_walk_postorder(skel);
|
|
if (test__start_subtest("cgroup_iter__ancestors_up_walk"))
|
|
test_walk_ancestors_up(skel);
|
|
if (test__start_subtest("cgroup_iter__early_termination"))
|
|
test_early_termination(skel);
|
|
if (test__start_subtest("cgroup_iter__self_only"))
|
|
test_walk_self_only(skel);
|
|
if (test__start_subtest("cgroup_iter__dead_self_only"))
|
|
test_walk_dead_self_only(skel);
|
|
out:
|
|
cgroup_iter__destroy(skel);
|
|
cleanup_cgroups();
|
|
cleanup_cgroup_environment();
|
|
}
|