--- /dev/null
+/**
+ * libcgroup googletest for cgroup_process_v2_mnt()
+ *
+ * Copyright (c) 2020 Oracle and/or its affiliates.
+ * Author: Tom Hromatka <tom.hromatka@oracle.com>
+ */
+
+/*
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of version 2.1 of the GNU Lesser General Public License as
+ * published by the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses>.
+ */
+
+#include <ftw.h>
+#include <mntent.h>
+
+#include "gtest/gtest.h"
+#include "libcgroup-internal.h"
+
+static const char * const PARENT_DIR = "test008cgroup";
+static const char * const PARENT2_DIR = "test008cgroup2";
+static const mode_t MODE = S_IRWXU | S_IRWXG | S_IRWXO;
+
+static const char * const CONTROLLERS[] = {
+ "cpuset",
+ "cpu",
+ "io",
+ "memory",
+ "pids",
+ "rdma",
+};
+static const int CONTROLLERS_CNT =
+ sizeof(CONTROLLERS) / sizeof(CONTROLLERS[0]);
+
+static int mnt_tbl_idx = 0;
+
+class CgroupProcessV2MntTest : public ::testing::Test {
+ protected:
+
+ void CreateHierarchy(const char * const dir)
+ {
+ char tmp_path[FILENAME_MAX];
+ int i, ret;
+ FILE *f;
+
+ ret = mkdir(dir, MODE);
+ ASSERT_EQ(ret, 0);
+
+ memset(tmp_path, 0, sizeof(tmp_path));
+ snprintf(tmp_path, FILENAME_MAX - 1, "%s/cgroup.controllers",
+ dir);
+
+ f = fopen(tmp_path, "w");
+ ASSERT_NE(f, nullptr);
+
+ for (i = 0; i < CONTROLLERS_CNT; i++)
+ fprintf(f, "%s ", CONTROLLERS[i]);
+
+ fclose(f);
+ }
+
+ void SetUp() override
+ {
+ char tmp_path[FILENAME_MAX];
+ int i, ret;
+ FILE *f;
+
+ CreateHierarchy(PARENT_DIR);
+
+ /* make another directory to test the duplicate logic */
+ CreateHierarchy(PARENT2_DIR);
+ }
+
+ /*
+ * https://stackoverflow.com/questions/5467725/how-to-delete-a-directory-and-its-contents-in-posix-c
+ */
+ static int unlink_cb(const char *fpath, const struct stat *sb, int typeflag,
+ struct FTW *ftwbuf)
+ {
+ return remove(fpath);
+ }
+
+ int rmrf(const char * const path)
+ {
+ return nftw(path, unlink_cb, 64, FTW_DEPTH | FTW_PHYS);
+ }
+
+ void TearDown() override
+ {
+ int ret = 0;
+
+ ret = rmrf(PARENT_DIR);
+ ASSERT_EQ(ret, 0);
+
+ ret = rmrf(PARENT2_DIR);
+ ASSERT_EQ(ret, 0);
+ }
+};
+
+TEST_F(CgroupProcessV2MntTest, AddV2Mount)
+{
+ char *mnt_dir = strdup(PARENT_DIR);
+ struct mntent ent = (struct mntent) {
+ .mnt_fsname = "cgroup2",
+ .mnt_dir = mnt_dir,
+ .mnt_type = "cgroup2",
+ .mnt_opts = "rw,relatime,seclabel",
+ };
+ int ret;
+
+ ret = cgroup_process_v2_mnt(&ent, &mnt_tbl_idx);
+
+ ASSERT_EQ(ret, 0);
+ ASSERT_EQ(mnt_tbl_idx, 6);
+ ASSERT_STREQ(cg_mount_table[0].name, "cpuset");
+ ASSERT_STREQ(cg_mount_table[1].name, "cpu");
+ ASSERT_STREQ(cg_mount_table[2].name, "io");
+ ASSERT_STREQ(cg_mount_table[3].name, "memory");
+ ASSERT_STREQ(cg_mount_table[4].name, "pids");
+ ASSERT_STREQ(cg_mount_table[5].name, "rdma");
+
+ ASSERT_STREQ(cg_mount_table[0].mount.path, ent.mnt_dir);
+ ASSERT_STREQ(cg_mount_table[1].mount.path, ent.mnt_dir);
+ ASSERT_STREQ(cg_mount_table[2].mount.path, ent.mnt_dir);
+ ASSERT_STREQ(cg_mount_table[3].mount.path, ent.mnt_dir);
+ ASSERT_STREQ(cg_mount_table[4].mount.path, ent.mnt_dir);
+ ASSERT_STREQ(cg_mount_table[5].mount.path, ent.mnt_dir);
+}
+
+TEST_F(CgroupProcessV2MntTest, AddV2Mount_Duplicate)
+{
+ char *mnt_dir = strdup(PARENT2_DIR);
+ struct mntent ent = (struct mntent) {
+ .mnt_fsname = "cgroup2",
+ .mnt_dir = mnt_dir,
+ .mnt_type = "cgroup2",
+ .mnt_opts = "rw,relatime,seclabel",
+ };
+ int ret;
+
+ ret = cgroup_process_v2_mnt(&ent, &mnt_tbl_idx);
+
+ ASSERT_EQ(ret, 0);
+ ASSERT_EQ(mnt_tbl_idx, 6);
+ ASSERT_STREQ(cg_mount_table[0].name, "cpuset");
+ ASSERT_STREQ(cg_mount_table[1].name, "cpu");
+ ASSERT_STREQ(cg_mount_table[2].name, "io");
+ ASSERT_STREQ(cg_mount_table[3].name, "memory");
+ ASSERT_STREQ(cg_mount_table[4].name, "pids");
+ ASSERT_STREQ(cg_mount_table[5].name, "rdma");
+
+ ASSERT_STREQ(cg_mount_table[0].mount.next->path, ent.mnt_dir);
+ ASSERT_STREQ(cg_mount_table[1].mount.next->path, ent.mnt_dir);
+ ASSERT_STREQ(cg_mount_table[2].mount.next->path, ent.mnt_dir);
+ ASSERT_STREQ(cg_mount_table[3].mount.next->path, ent.mnt_dir);
+ ASSERT_STREQ(cg_mount_table[4].mount.next->path, ent.mnt_dir);
+ ASSERT_STREQ(cg_mount_table[5].mount.next->path, ent.mnt_dir);
+}
+
+/*
+ * This test must be last because it makes destructive changes to the cgroup hierarchy
+ */
+TEST_F(CgroupProcessV2MntTest, EmptyControllersFile)
+{
+ char tmp_path[FILENAME_MAX];
+ char *mnt_dir = strdup(PARENT_DIR);
+ struct mntent ent = (struct mntent) {
+ .mnt_fsname = "cgroup2",
+ .mnt_dir = mnt_dir,
+ .mnt_type = "cgroup2",
+ .mnt_opts = "rw,relatime,seclabel",
+ };
+ FILE *f;
+ int ret;
+
+ memset(tmp_path, 0, sizeof(tmp_path));
+ snprintf(tmp_path, FILENAME_MAX - 1, "%s/cgroup.controllers",
+ PARENT_DIR);
+
+ /* clear the cgroup.controllers file */
+ f = fopen(tmp_path, "w");
+ ASSERT_NE(f, nullptr);
+ fclose(f);
+
+ /* reset the mount table count */
+ mnt_tbl_idx = 0;
+
+ ret = cgroup_process_v2_mnt(&ent, &mnt_tbl_idx);
+
+ ASSERT_EQ(ret, ECGEOF);
+ ASSERT_EQ(mnt_tbl_idx, 0);
+}