Add unittest for IsEquivalentNode

PiperOrigin-RevId: 433172902
Change-Id: Ie6fb44e682be947fb9f8b856c5e804aa91647a6d
This commit is contained in:
Oliver Kunz 2022-03-08 04:04:15 -08:00 committed by Copybara-Service
parent 8a5740fbb1
commit 2650834d7c
5 changed files with 107 additions and 40 deletions

View File

@ -495,6 +495,7 @@ cc_test(
data = ["//sandboxed_api/sandbox2/testcases:minimal_dynamic"], data = ["//sandboxed_api/sandbox2/testcases:minimal_dynamic"],
deps = [ deps = [
":mounts", ":mounts",
":mounttree_cc_proto",
"//sandboxed_api:testing", "//sandboxed_api:testing",
"//sandboxed_api/util:file_base", "//sandboxed_api/util:file_base",
"//sandboxed_api/util:file_helpers", "//sandboxed_api/util:file_helpers",

View File

@ -667,6 +667,7 @@ if(SAPI_ENABLE_TESTS)
absl::strings absl::strings
sapi::file_base sapi::file_base
sandbox2::mounts sandbox2::mounts
sandbox2::mounttree_proto
sapi::temp_file sapi::temp_file
sapi::testing sapi::testing
sapi::status_matchers sapi::status_matchers

View File

@ -55,43 +55,6 @@ bool PathContainsNullByte(absl::string_view path) {
return absl::StrContains(path, '\0'); return absl::StrContains(path, '\0');
} }
bool IsSameFile(const std::string& path1, const std::string& path2) {
struct stat stat1, stat2;
if (stat(path1.c_str(), &stat1) == -1) {
return false;
}
if (stat(path2.c_str(), &stat2) == -1) {
return false;
}
return stat1.st_dev == stat2.st_dev && stat1.st_ino == stat2.st_ino;
}
bool IsEquivalentNode(const sandbox2::MountTree::Node& n1,
const sandbox2::MountTree::Node& n2) {
// Node equals 1:1
if (google::protobuf::util::MessageDifferencer::Equals(n1, n2)) {
return true;
}
if (n1.node_case() != n2.node_case()) {
return false;
}
// Check whether files/dirs are the same (e.g symlinks / hardlinks)
switch (n1.node_case()) {
case sandbox2::MountTree::Node::kFileNode:
return n1.file_node().is_ro() == n2.file_node().is_ro() &&
IsSameFile(n1.file_node().outside(), n2.file_node().outside());
case sandbox2::MountTree::Node::kDirNode:
return n1.dir_node().is_ro() == n2.dir_node().is_ro() &&
IsSameFile(n1.dir_node().outside(), n2.dir_node().outside());
default:
return false;
}
}
absl::string_view GetOutsidePath(const MountTree::Node& node) { absl::string_view GetOutsidePath(const MountTree::Node& node) {
switch (node.node_case()) { switch (node.node_case()) {
case MountTree::Node::kFileNode: case MountTree::Node::kFileNode:
@ -162,6 +125,47 @@ std::string GetPlatform(absl::string_view interpreter) {
} // namespace } // namespace
namespace internal {
bool IsSameFile(const std::string& path1, const std::string& path2) {
struct stat stat1, stat2;
if (stat(path1.c_str(), &stat1) == -1) {
return false;
}
if (stat(path2.c_str(), &stat2) == -1) {
return false;
}
return stat1.st_dev == stat2.st_dev && stat1.st_ino == stat2.st_ino;
}
bool IsEquivalentNode(const sandbox2::MountTree::Node& n1,
const sandbox2::MountTree::Node& n2) {
// Node equals 1:1
if (google::protobuf::util::MessageDifferencer::Equals(n1, n2)) {
return true;
}
if (n1.node_case() != n2.node_case()) {
return false;
}
// Check whether files/dirs are the same (e.g symlinks / hardlinks)
switch (n1.node_case()) {
case sandbox2::MountTree::Node::kFileNode:
return n1.file_node().is_ro() == n2.file_node().is_ro() &&
IsSameFile(n1.file_node().outside(), n2.file_node().outside());
case sandbox2::MountTree::Node::kDirNode:
return n1.dir_node().is_ro() == n2.dir_node().is_ro() &&
IsSameFile(n1.dir_node().outside(), n2.dir_node().outside());
default:
return false;
}
}
} // namespace internal
absl::Status Mounts::Insert(absl::string_view path, absl::Status Mounts::Insert(absl::string_view path,
const MountTree::Node& new_node) { const MountTree::Node& new_node) {
// Some sandboxes allow the inside/outside paths to be partially // Some sandboxes allow the inside/outside paths to be partially
@ -225,7 +229,7 @@ absl::Status Mounts::Insert(absl::string_view path,
.first->second); .first->second);
if (curtree->has_node()) { if (curtree->has_node()) {
if (IsEquivalentNode(curtree->node(), new_node)) { if (internal::IsEquivalentNode(curtree->node(), new_node)) {
SAPI_RAW_LOG(INFO, "Inserting %s with the same value twice", SAPI_RAW_LOG(INFO, "Inserting %s with the same value twice",
std::string(path).c_str()); std::string(path).c_str());
return absl::OkStatus(); return absl::OkStatus();

View File

@ -26,6 +26,12 @@
namespace sandbox2 { namespace sandbox2 {
namespace internal {
bool IsSameFile(const std::string& path1, const std::string& path2);
bool IsEquivalentNode(const MountTree::Node& n1, const MountTree::Node& n2);
} // namespace internal
class Mounts { class Mounts {
public: public:
Mounts() { Mounts() {

View File

@ -178,7 +178,7 @@ TEST(MountTreeTest, TestMinimalDynamicBinary) {
TEST(MountTreeTest, TestList) { TEST(MountTreeTest, TestList) {
struct TestCase { struct TestCase {
const char *path; const char* path;
const bool is_ro; const bool is_ro;
}; };
// clang-format off // clang-format off
@ -196,7 +196,7 @@ TEST(MountTreeTest, TestList) {
Mounts mounts; Mounts mounts;
// Create actual directories and files on disk and selectively add // Create actual directories and files on disk and selectively add
for (const auto &test_case : kTestCases) { for (const auto& test_case : kTestCases) {
const auto inside_path = test_case.path; const auto inside_path = test_case.path;
const std::string outside_path = absl::StrCat("/some/dir/", inside_path); const std::string outside_path = absl::StrCat("/some/dir/", inside_path);
if (absl::EndsWith(outside_path, "/")) { if (absl::EndsWith(outside_path, "/")) {
@ -244,6 +244,61 @@ TEST(MountTreeTest, TestList) {
// clang-format on // clang-format on
} }
TEST(MountTreeTest, TestNodeEquivalence) {
MountTree::Node nodes[8];
MountTree::FileNode* fn0 = nodes[0].mutable_file_node();
fn0->set_is_ro(true);
fn0->set_outside("foo");
MountTree::FileNode* fn1 = nodes[1].mutable_file_node();
fn1->set_is_ro(true);
fn1->set_outside("bar");
MountTree::DirNode* dn0 = nodes[2].mutable_dir_node();
dn0->set_is_ro(true);
dn0->set_outside("foo");
MountTree::DirNode* dn1 = nodes[3].mutable_dir_node();
dn1->set_is_ro(true);
dn1->set_outside("bar");
MountTree::TmpfsNode* tn0 = nodes[4].mutable_tmpfs_node();
tn0->set_tmpfs_options("option1");
MountTree::TmpfsNode* tn1 = nodes[5].mutable_tmpfs_node();
tn1->set_tmpfs_options("option2");
MountTree::RootNode* rn0 = nodes[6].mutable_root_node();
rn0->set_is_ro(true);
MountTree::RootNode* rn1 = nodes[7].mutable_root_node();
rn1->set_is_ro(false);
for (const MountTree::Node n : nodes) {
ASSERT_TRUE(n.IsInitialized());
}
// Compare same file nodes
EXPECT_TRUE(internal::IsEquivalentNode(nodes[0], nodes[0]));
// Compare with different file node
EXPECT_FALSE(internal::IsEquivalentNode(nodes[0], nodes[1]));
// compare file node with dir node
EXPECT_FALSE(internal::IsEquivalentNode(nodes[0], nodes[2]));
// Compare same dir nodes
EXPECT_TRUE(internal::IsEquivalentNode(nodes[2], nodes[2]));
// Compare with different dir node
EXPECT_FALSE(internal::IsEquivalentNode(nodes[2], nodes[3]));
// Compare dir node with tmpfs node
EXPECT_FALSE(internal::IsEquivalentNode(nodes[2], nodes[4]));
// Compare same tmpfs nodes
EXPECT_TRUE(internal::IsEquivalentNode(nodes[4], nodes[4]));
// Compare with different tmpfs nodes
EXPECT_FALSE(internal::IsEquivalentNode(nodes[4], nodes[5]));
// Compare tmpfs node with root node
EXPECT_FALSE(internal::IsEquivalentNode(nodes[4], nodes[6]));
// Compare same root nodes
EXPECT_TRUE(internal::IsEquivalentNode(nodes[6], nodes[6]));
// Compare different root node
EXPECT_FALSE(internal::IsEquivalentNode(nodes[6], nodes[7]));
// Compare root node with file node
EXPECT_FALSE(internal::IsEquivalentNode(nodes[6], nodes[0]));
}
TEST(MountsResolvePathTest, Files) { TEST(MountsResolvePathTest, Files) {
Mounts mounts; Mounts mounts;
ASSERT_THAT(mounts.AddFileAt("/A", "/a"), IsOk()); ASSERT_THAT(mounts.AddFileAt("/A", "/a"), IsOk());