More RAII for system_unittests
This change introduces TemporaryFile and TemporaryDir, which takes care
of cleaning up after themselves.
Bug: None
Test: make tests && git status --porcelain # empty
Change-Id: Ibd429b2ce24c812d87120b269034967e90cd4dd9
diff --git a/system_unittest.cc b/system_unittest.cc
index 60d5c03..36d8dc3 100644
--- a/system_unittest.cc
+++ b/system_unittest.cc
@@ -5,6 +5,7 @@
* Test system.[ch] module code using gtest.
*/
+#include <ftw.h>
#include <limits.h>
#include <linux/securebits.h>
#include <stdio.h>
@@ -23,18 +24,18 @@
namespace {
// A random path that really really should not exist on the host.
-const char kNoSuchDir[] = "/.x/..x/...x/path/should/not/exist/";
+constexpr const char kNoSuchDir[] = "/.x/..x/...x/path/should/not/exist/";
// A random file that should exist on both Linux and Android.
-const char kValidFile[] = "/etc/hosts";
+constexpr const char kValidFile[] = "/etc/hosts";
// A random directory that should exist.
-const char kValidDir[] = "/";
+constexpr const char kValidDir[] = "/";
// A random character device that should exist.
-const char kValidCharDev[] = "/dev/null";
+constexpr const char kValidCharDev[] = "/dev/null";
-inline bool is_android() {
+constexpr bool is_android() {
#if defined(__ANDROID__)
return true;
#else
@@ -42,28 +43,82 @@
#endif
}
-// Return a temp filename that this test can manipulate.
-// It will not exist when it returns.
-std::string get_temp_path() {
- char *path = nullptr;
- if (is_android()) {
- path = strdup("/data/local/tmp/minijail.tests.XXXXXX");
- } else {
- path = strdup("minijail.tests.XXXXXX");
+// Returns a template path that can be used as an argument to mkstemp / mkdtemp.
+constexpr const char* temp_path_pattern() {
+ if (is_android())
+ return "/data/local/tmp/minijail.tests.XXXXXX";
+ else
+ return "minijail.tests.XXXXXX";
+}
+
+// Recursively deletes the subtree rooted at |path|.
+bool rmdir_recursive(const std::string& path) {
+ auto callback = [](const char* child, const struct stat*, int file_type,
+ struct FTW*) -> int {
+ if (file_type == FTW_DP) {
+ if (rmdir(child) == -1) {
+ fprintf(stderr, "rmdir(%s): %s", child, strerror(errno));
+ return -1;
+ }
+ } else if (file_type == FTW_F) {
+ if (unlink(child) == -1) {
+ fprintf(stderr, "unlink(%s): %s", child, strerror(errno));
+ return -1;
+ }
+ }
+ return 0;
+ };
+
+ return nftw(path.c_str(), callback, 128, FTW_DEPTH) == 0;
+}
+
+// Creates a temporary directory that will be cleaned up upon leaving scope.
+class TemporaryDir {
+ public:
+ TemporaryDir() : path(temp_path_pattern()) {
+ if (mkdtemp(const_cast<char*>(path.c_str())) == nullptr)
+ path.clear();
+ }
+ ~TemporaryDir() {
+ if (!is_valid())
+ return;
+ rmdir_recursive(path.c_str());
}
- if (!path)
- return std::string();
+ bool is_valid() const { return !path.empty(); }
- // Just create the temp path.
- int fd = mkstemp(path);
- if (fd < 0)
- return std::string();
- close(fd);
- unlink(path);
+ std::string path;
- return path;
-}
+ private:
+ TemporaryDir(const TemporaryDir&) = delete;
+ TemporaryDir& operator=(const TemporaryDir&) = delete;
+};
+
+// Creates a named temporary file that will be cleaned up upon leaving scope.
+class TemporaryFile {
+ public:
+ TemporaryFile() : path(temp_path_pattern()) {
+ int fd = mkstemp(const_cast<char*>(path.c_str()));
+ if (fd == -1) {
+ path.clear();
+ return;
+ }
+ close(fd);
+ }
+ ~TemporaryFile() {
+ if (!is_valid())
+ return;
+ unlink(path.c_str());
+ }
+
+ bool is_valid() const { return !path.empty(); }
+
+ std::string path;
+
+ private:
+ TemporaryFile(const TemporaryFile&) = delete;
+ TemporaryFile& operator=(const TemporaryFile&) = delete;
+};
} // namespace
@@ -145,12 +200,11 @@
// Make sure we can write a pid to the file.
TEST(write_pid_to_path, basic) {
- std::string path = get_temp_path();
- ASSERT_NE(std::string(), path);
+ TemporaryFile tmp;
+ ASSERT_TRUE(tmp.is_valid());
- EXPECT_EQ(0, write_pid_to_path(1234, path.c_str()));
- FILE *fp = fopen(path.c_str(), "re");
- unlink(path.c_str());
+ EXPECT_EQ(0, write_pid_to_path(1234, tmp.path.c_str()));
+ FILE *fp = fopen(tmp.path.c_str(), "re");
EXPECT_NE(nullptr, fp);
char data[6] = {};
EXPECT_EQ(5u, fread(data, 1, sizeof(data), fp));
@@ -171,40 +225,29 @@
// Create a directory tree that doesn't exist.
TEST(mkdir_p, create_tree) {
- std::string path = get_temp_path();
- ASSERT_NE(std::string(), path);
+ TemporaryDir dir;
+ ASSERT_TRUE(dir.is_valid());
// Run `mkdir -p <path>/a/b/c`.
- char *path_a, *path_a_b, *path_a_b_c;
- ASSERT_NE(-1, asprintf(&path_a, "%s/a", path.c_str()));
- ASSERT_NE(-1, asprintf(&path_a_b, "%s/b", path_a));
- ASSERT_NE(-1, asprintf(&path_a_b_c, "%s/c", path_a_b));
+ std::string path_a = dir.path + "/a";
+ std::string path_a_b = path_a + "/b";
+ std::string path_a_b_c = path_a_b + "/c";
// First try creating it as a file.
- EXPECT_EQ(0, mkdir_p(path_a_b_c, 0700, false));
+ EXPECT_EQ(0, mkdir_p(path_a_b_c.c_str(), 0700, false));
// Make sure the final path doesn't exist yet.
struct stat st;
- EXPECT_EQ(0, stat(path_a_b, &st));
+ EXPECT_EQ(0, stat(path_a_b.c_str(), &st));
EXPECT_EQ(true, S_ISDIR(st.st_mode));
- EXPECT_EQ(-1, stat(path_a_b_c, &st));
+ EXPECT_EQ(-1, stat(path_a_b_c.c_str(), &st));
// Then create it as a complete dir.
- EXPECT_EQ(0, mkdir_p(path_a_b_c, 0700, true));
+ EXPECT_EQ(0, mkdir_p(path_a_b_c.c_str(), 0700, true));
// Make sure the final dir actually exists.
- EXPECT_EQ(0, stat(path_a_b_c, &st));
+ EXPECT_EQ(0, stat(path_a_b_c.c_str(), &st));
EXPECT_EQ(true, S_ISDIR(st.st_mode));
-
- // Clean up.
- ASSERT_EQ(0, rmdir(path_a_b_c));
- ASSERT_EQ(0, rmdir(path_a_b));
- ASSERT_EQ(0, rmdir(path_a));
- ASSERT_EQ(0, rmdir(path.c_str()));
-
- free(path_a_b_c);
- free(path_a_b);
- free(path_a);
}
// If the destination exists, there's nothing to do.
@@ -222,22 +265,24 @@
struct statvfs stvfs_buf;
ASSERT_EQ(0, statvfs("/proc", &stvfs_buf));
- std::string path = get_temp_path();
- ASSERT_NE(std::string(), path);
+ TemporaryDir dir;
+ ASSERT_TRUE(dir.is_valid());
unsigned long mount_flags = -1;
// Passing -1 for user ID/group ID tells chown to make no changes.
- EXPECT_EQ(0, setup_mount_destination("/proc", path.c_str(), -1, -1, true,
+ std::string proc = dir.path + "/proc";
+ EXPECT_EQ(0, setup_mount_destination("/proc", proc.c_str(), -1, -1, true,
&mount_flags));
EXPECT_EQ(stvfs_buf.f_flag, mount_flags);
- EXPECT_EQ(0, rmdir(path.c_str()));
+ EXPECT_EQ(0, rmdir(proc.c_str()));
// Same thing holds for children of a mount.
mount_flags = -1;
- EXPECT_EQ(0, setup_mount_destination("/proc/self", path.c_str(), -1, -1, true,
- &mount_flags));
+ std::string proc_self = dir.path + "/proc_self";
+ EXPECT_EQ(0, setup_mount_destination("/proc/self", proc_self.c_str(), -1, -1,
+ true, &mount_flags));
EXPECT_EQ(stvfs_buf.f_flag, mount_flags);
- EXPECT_EQ(0, rmdir(path.c_str()));
+ EXPECT_EQ(0, rmdir(proc_self.c_str()));
}
// When given a bind mount where the source is relative, reject it.
@@ -248,21 +293,23 @@
// A mount of a pseudo filesystem should make the destination dir.
TEST(setup_mount_destination, create_pseudo_fs) {
- std::string path = get_temp_path();
- ASSERT_NE(std::string(), path);
+ TemporaryDir dir;
+ ASSERT_TRUE(dir.is_valid());
// Passing -1 for user ID/group ID tells chown to make no changes.
- EXPECT_EQ(0, setup_mount_destination("none", path.c_str(), -1, -1, false,
+ std::string no_chmod = dir.path + "/no_chmod";
+ EXPECT_EQ(0, setup_mount_destination("none", no_chmod.c_str(), -1, -1, false,
nullptr));
// We check it's a directory by deleting it as such.
- EXPECT_EQ(0, rmdir(path.c_str()));
+ EXPECT_EQ(0, rmdir(no_chmod.c_str()));
// Confirm that a bad user ID/group ID fails the function as expected.
// On Android, Bionic manages user IDs directly: there is no /etc/passwd file.
// This results in most user IDs being valid. Instead of trying to find an
// invalid user ID, just skip this check.
if (!is_android()) {
- EXPECT_NE(0, setup_mount_destination("none", path.c_str(),
+ std::string with_chmod = dir.path + "/with_chmod";
+ EXPECT_NE(0, setup_mount_destination("none", with_chmod.c_str(),
UINT_MAX / 2, UINT_MAX / 2, false,
nullptr));
}
@@ -279,36 +326,39 @@
// A bind mount of a directory should create the destination dir.
TEST(setup_mount_destination, create_bind_dir) {
- std::string path = get_temp_path();
- ASSERT_NE(std::string(), path);
+ TemporaryDir dir;
+ ASSERT_TRUE(dir.is_valid());
// Passing -1 for user ID/group ID tells chown to make no changes.
- EXPECT_EQ(0, setup_mount_destination(kValidDir, path.c_str(), -1, -1, true,
- nullptr));
+ std::string child_dir = dir.path + "/child_dir";
+ EXPECT_EQ(0, setup_mount_destination(kValidDir, child_dir.c_str(), -1, -1,
+ true, nullptr));
// We check it's a directory by deleting it as such.
- EXPECT_EQ(0, rmdir(path.c_str()));
+ EXPECT_EQ(0, rmdir(child_dir.c_str()));
}
// A bind mount of a file should create the destination file.
TEST(setup_mount_destination, create_bind_file) {
- std::string path = get_temp_path();
- ASSERT_NE(std::string(), path);
+ TemporaryDir dir;
+ ASSERT_TRUE(dir.is_valid());
// Passing -1 for user ID/group ID tells chown to make no changes.
- EXPECT_EQ(0, setup_mount_destination(kValidFile, path.c_str(), -1, -1, true,
- nullptr));
+ std::string child_file = dir.path + "/child_file";
+ EXPECT_EQ(0, setup_mount_destination(kValidFile, child_file.c_str(), -1, -1,
+ true, nullptr));
// We check it's a file by deleting it as such.
- EXPECT_EQ(0, unlink(path.c_str()));
+ EXPECT_EQ(0, unlink(child_file.c_str()));
}
// A mount of a character device should create the destination char.
TEST(setup_mount_destination, create_char_dev) {
- std::string path = get_temp_path();
- ASSERT_NE(std::string(), path);
+ TemporaryDir dir;
+ ASSERT_TRUE(dir.is_valid());
// Passing -1 for user ID/group ID tells chown to make no changes.
- EXPECT_EQ(0, setup_mount_destination(kValidCharDev, path.c_str(), -1, -1,
+ std::string child_dev = dir.path + "/child_dev";
+ EXPECT_EQ(0, setup_mount_destination(kValidCharDev, child_dev.c_str(), -1, -1,
false, nullptr));
// We check it's a directory by deleting it as such.
- EXPECT_EQ(0, rmdir(path.c_str()));
+ EXPECT_EQ(0, rmdir(child_dev.c_str()));
}