From c2a7efe6a23072acfde99147bd8e81130500bff3 Mon Sep 17 00:00:00 2001 From: Lucas Manning Date: Wed, 30 Aug 2023 13:52:24 -0700 Subject: [PATCH] Clean up mount tests. This change makes it simpler to write mount tests that examine the optional field of mountinfo. It also cleans up the mounts in existing tests so they don't pollute mountinfo after exit. PiperOrigin-RevId: 561439839 --- test/syscalls/linux/mount.cc | 398 ++++++++++++----------------------- test/util/BUILD | 2 + test/util/mount_util.cc | 51 +++++ test/util/mount_util.h | 19 ++ 4 files changed, 209 insertions(+), 261 deletions(-) diff --git a/test/syscalls/linux/mount.cc b/test/syscalls/linux/mount.cc index c95a5f521..145e26ae0 100644 --- a/test/syscalls/linux/mount.cc +++ b/test/syscalls/linux/mount.cc @@ -829,9 +829,9 @@ TEST(MountTest, SimpleBind) { std::string output; ASSERT_NO_ERRNO(GetContents(dir1_filepath, &output)); - ASSERT_EQ(output, contents); + EXPECT_EQ(output, contents); ASSERT_NO_ERRNO(GetContents(dir2_filepath, &output)); - ASSERT_EQ(output, contents); + EXPECT_EQ(output, contents); } TEST(MountTest, BindToSelf) { @@ -909,14 +909,9 @@ TEST(MountTest, MakeShared) { ASSERT_THAT(mount("", dir.path().c_str(), "", MS_SHARED, 0), SyscallSucceeds()); - const std::vector mounts = - ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountInfoEntries()); - for (const auto& e : mounts) { - if (e.mount_point == dir.path()) { - EXPECT_TRUE(absl::StrContains(e.optional, "shared:")); - break; - } - } + auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals()); + ASSERT_FALSE(optionals[dir.path()].empty()); + EXPECT_NE(optionals[dir.path()][0].shared, 0); } // Tests that shared mounts have different group IDs. @@ -935,19 +930,10 @@ TEST(MountTest, MakeMultipleShared) { ASSERT_THAT(mount("", dir2.path().c_str(), "", MS_SHARED, 0), SyscallSucceeds()); - std::string optional1, optional2; - const std::vector mounts = - ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountInfoEntries()); - for (const auto& e : mounts) { - if (e.mount_point == dir1.path()) { - EXPECT_TRUE(absl::StrContains(e.optional, "shared:")); - optional1 = e.optional; - } else if (e.mount_point == dir2.path()) { - EXPECT_TRUE(absl::StrContains(e.optional, "shared:")); - optional2 = e.optional; - } - } - EXPECT_NE(optional1, optional2); + auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals()); + ASSERT_FALSE(optionals[dir1.path()].empty()); + ASSERT_FALSE(optionals[dir2.path()].empty()); + EXPECT_NE(optionals[dir1.path()][0].shared, optionals[dir2.path()][0].shared); } // Tests that shared mounts reused group IDs from deleted groups. @@ -961,21 +947,15 @@ TEST(MountTest, ReuseGroupIDs) { SyscallSucceeds()); auto const dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - std::string reused_optional; + int reused_group_id; { auto const mount2 = ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir2.path(), "tmpfs", 0, "", 0)); ASSERT_THAT(mount("", dir2.path().c_str(), "", MS_SHARED, 0), SyscallSucceeds()); - - const std::vector mounts = - ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountInfoEntries()); - for (const auto& e : mounts) { - if (e.mount_point == dir2.path()) { - EXPECT_TRUE(absl::StrContains(e.optional, "shared:")); - reused_optional = e.optional; - } - } + auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals()); + ASSERT_FALSE(optionals[dir2.path()].empty()); + reused_group_id = optionals[dir2.path()][0].shared; } // Check that created a new shared mount reuses the ID 2. @@ -983,14 +963,9 @@ TEST(MountTest, ReuseGroupIDs) { ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir2.path(), "tmpfs", 0, "", 0)); ASSERT_THAT(mount("", dir2.path().c_str(), "", MS_SHARED, 0), SyscallSucceeds()); - const std::vector mounts = - ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountInfoEntries()); - for (const auto& e : mounts) { - if (e.mount_point == dir2.path()) { - EXPECT_EQ(e.optional, reused_optional); - break; - } - } + auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals()); + ASSERT_FALSE(optionals[dir2.path()].empty()); + EXPECT_EQ(reused_group_id, optionals[dir2.path()][0].shared); } // Tests that a child mount inherits the propagation type of its parent. @@ -1008,14 +983,9 @@ TEST(MountTest, InerheritPropagation) { auto const mount2 = ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir2.path(), "tmpfs", 0, "", 0)); - const std::vector mounts = - ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountInfoEntries()); - for (const auto& e : mounts) { - if (e.mount_point == dir2.path()) { - EXPECT_TRUE(absl::StrContains(e.optional, "shared:")); - break; - } - } + auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals()); + ASSERT_FALSE(optionals[dir2.path()].empty()); + EXPECT_NE(optionals[dir2.path()][0].shared, 0); } // Tests that it is possible to make a mount private again after it is shared. @@ -1030,14 +1000,9 @@ TEST(MountTest, MakePrivate) { ASSERT_THAT(mount("", dir.path().c_str(), "", MS_PRIVATE, 0), SyscallSucceeds()); - const std::vector mounts = - ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountInfoEntries()); - for (const auto& e : mounts) { - if (e.mount_point == dir.path()) { - EXPECT_EQ(e.optional, ""); - break; - } - } + auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals()); + ASSERT_FALSE(optionals[dir.path()].empty()); + EXPECT_EQ(optionals[dir.path()][0].shared, 0); } TEST(MountTest, ArgumentsAreIgnored) { @@ -1045,19 +1010,13 @@ TEST(MountTest, ArgumentsAreIgnored) { auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); // These mounts should not fail even though string arguments are passed as // NULL. - ASSERT_THAT( - mount(dir.path().c_str(), dir.path().c_str(), NULL, MS_BIND, NULL), - SyscallSucceeds()); + auto const mnt = ASSERT_NO_ERRNO_AND_VALUE( + Mount(dir.path(), dir.path(), "", MS_BIND, "", MNT_DETACH)); ASSERT_THAT(mount(NULL, dir.path().c_str(), NULL, MS_SHARED, NULL), SyscallSucceeds()); - const std::vector mounts = - ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountInfoEntries()); - for (const auto& e : mounts) { - if (e.mount_point == dir.path()) { - EXPECT_TRUE(absl::StrContains(e.optional, "shared:")); - break; - } - } + auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals()); + ASSERT_FALSE(optionals[dir.path()].empty()); + EXPECT_NE(optionals[dir.path()][0].shared, 0); } TEST(MountTest, MultiplePropagationFlagsFails) { @@ -1075,29 +1034,16 @@ TEST(MountTest, SetMountPropagationOfStackedMounts) { auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const mnt1 = ASSERT_NO_ERRNO_AND_VALUE( Mount("", dir.path().c_str(), "tmpfs", 0, "", 0)); - - std::vector mounts = - ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountInfoEntries()); - uint64_t parent_mount_id; - for (const auto& e : mounts) { - if (e.mount_point == dir.path()) { - parent_mount_id = e.id; - } - } // Only the topmost mount on the stack should be shared. auto const mnt2 = ASSERT_NO_ERRNO_AND_VALUE( Mount("", dir.path().c_str(), "tmpfs", 0, "", 0)); ASSERT_THAT(mount("", dir.path().c_str(), "", MS_SHARED, 0), SyscallSucceeds()); - mounts = ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountInfoEntries()); - for (const auto& e : mounts) { - if (e.mount_point == dir.path() && e.id != parent_mount_id) { - EXPECT_TRUE(absl::StrContains(e.optional, "shared:")); - } - if (e.mount_point == dir.path() && e.id == parent_mount_id) { - EXPECT_EQ(e.optional, ""); - } - } + + auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals()); + ASSERT_FALSE(optionals[dir.path()].empty()); + EXPECT_EQ(optionals[dir.path()][0].shared, 0); + EXPECT_NE(optionals[dir.path()][1].shared, 0); } TEST(MountTest, MakePeer) { @@ -1109,22 +1055,14 @@ TEST(MountTest, MakePeer) { SyscallSucceeds()); auto const dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - ASSERT_THAT(mount(dir1.path().c_str(), dir2.path().c_str(), "", MS_BIND, 0), - SyscallSucceeds()); - std::vector mounts = - ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountInfoEntries()); - std::string optional1, optional2; - for (const auto& e : mounts) { - if (e.mount_point == dir1.path()) { - EXPECT_TRUE(absl::StrContains(e.optional, "shared:")); - optional1 = e.optional; - } - if (e.mount_point == dir2.path()) { - EXPECT_TRUE(absl::StrContains(e.optional, "shared:")); - optional2 = e.optional; - } - } - ASSERT_EQ(optional1, optional2); + auto const mnt2 = ASSERT_NO_ERRNO_AND_VALUE( + Mount(dir1.path(), dir2.path(), "", MS_BIND, "", MNT_DETACH)); + + auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals()); + ASSERT_FALSE(optionals[dir1.path()].empty()); + ASSERT_FALSE(optionals[dir2.path()].empty()); + EXPECT_EQ(optionals[dir1.path()][0].shared, optionals[dir2.path()][0].shared); + EXPECT_NE(optionals[dir1.path()][0].shared, 0); } TEST(MountTest, PropagateMountEvent) { @@ -1137,8 +1075,8 @@ TEST(MountTest, PropagateMountEvent) { ASSERT_THAT(mount("", dir1.path().c_str(), "", MS_SHARED, 0), SyscallSucceeds()); auto const dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - ASSERT_THAT(mount(dir1.path().c_str(), dir2.path().c_str(), "", MS_BIND, 0), - SyscallSucceeds()); + auto const mnt2 = ASSERT_NO_ERRNO_AND_VALUE( + Mount(dir1.path(), dir2.path(), "", MS_BIND, "", MNT_DETACH)); // This mount should propagate to dir2. auto const child_mnt = ASSERT_NO_ERRNO_AND_VALUE( Mount("", child_dir.path().c_str(), "tmpfs", 0, "", 0)); @@ -1148,26 +1086,15 @@ TEST(MountTest, PropagateMountEvent) { const std::string child_path2 = JoinPath(dir2.path(), Basename(child_dir.path())); - std::string child_opt1, child_opt2, parent_optional; - std::vector mounts = - ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountInfoEntries()); - for (const auto& e : mounts) { - if (e.mount_point == child_path1) { - EXPECT_TRUE(absl::StrContains(e.optional, "shared:")); - child_opt1 = e.optional; - } - if (e.mount_point == child_path2) { - EXPECT_TRUE(absl::StrContains(e.optional, "shared:")); - child_opt2 = e.optional; - } - if (e.mount_point == dir1.path() || e.mount_point == dir2.path()) { - EXPECT_TRUE(absl::StrContains(e.optional, "shared:")); - parent_optional = e.optional; - } - } - // Should be in the same peer group. - ASSERT_EQ(child_opt1, child_opt2); - ASSERT_NE(child_opt1, parent_optional); + auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals()); + ASSERT_FALSE(optionals[dir1.path()].empty()); + ASSERT_FALSE(optionals[dir2.path()].empty()); + ASSERT_FALSE(optionals[child_path1].empty()); + ASSERT_FALSE(optionals[child_path2].empty()); + EXPECT_EQ(optionals[dir1.path()][0].shared, optionals[dir2.path()][0].shared); + EXPECT_NE(optionals[dir1.path()][0].shared, 0); + EXPECT_EQ(optionals[child_path1][0].shared, optionals[child_path2][0].shared); + EXPECT_NE(optionals[child_path1][0].shared, 0); } TEST(MountTest, PropagateUmountEvent) { @@ -1180,8 +1107,8 @@ TEST(MountTest, PropagateUmountEvent) { ASSERT_THAT(mount("", dir1.path().c_str(), "", MS_SHARED, 0), SyscallSucceeds()); auto const dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - ASSERT_THAT(mount(dir1.path().c_str(), dir2.path().c_str(), "", MS_BIND, 0), - SyscallSucceeds()); + auto const mnt2 = ASSERT_NO_ERRNO_AND_VALUE( + Mount(dir1.path(), dir2.path(), "", MS_BIND, "", MNT_DETACH)); // This mount will propagate to dir2. Once the block ends it will be // unmounted, which should also propagate to dir2. { @@ -1197,8 +1124,8 @@ TEST(MountTest, PropagateUmountEvent) { std::vector mounts = ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountInfoEntries()); for (const auto& e : mounts) { - ASSERT_NE(e.mount_point, child_path1); - ASSERT_NE(e.mount_point, child_path2); + EXPECT_NE(e.mount_point, child_path1); + EXPECT_NE(e.mount_point, child_path2); } } @@ -1235,17 +1162,12 @@ TEST(MountTest, UmountIgnoresPeersWithChildren) { // This umount event should not propagate to the peer at dir1 because its // child mount still has its own child mount. ASSERT_THAT(umount2(child_path2.c_str(), MNT_DETACH), SyscallSucceeds()); - std::vector mounts = - ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountInfoEntries()); - bool found = false; - for (const auto& e : mounts) { - ASSERT_NE(e.mount_point, child_path2); - if (e.mount_point == child_path1) { - found = true; - break; - } - } - ASSERT_TRUE(found); + auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals()); + EXPECT_EQ(optionals[child_path2].size(), 0); + EXPECT_EQ(optionals[child_path1].size(), 1); + + ASSERT_THAT(umount2(dir1.path().c_str(), MNT_DETACH), SyscallSucceeds()); + ASSERT_THAT(umount2(dir2.path().c_str(), MNT_DETACH), SyscallSucceeds()); } TEST(MountTest, BindSharedOnShared) { @@ -1256,181 +1178,134 @@ TEST(MountTest, BindSharedOnShared) { auto const dir4 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); // Dir 1 and 2 are part of peer group 'A', dir 3 and 4 are part of peer group // 'B'. - ASSERT_THAT(mount("", dir1.path().c_str(), "tmpfs", 0, ""), - SyscallSucceeds()); + auto const mnt1 = ASSERT_NO_ERRNO_AND_VALUE( + Mount("", dir1.path().c_str(), "tmpfs", 0, "", MNT_DETACH)); auto const dir5 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir1.path())); ASSERT_THAT(mount("", dir1.path().c_str(), "", MS_SHARED, 0), SyscallSucceeds()); - ASSERT_THAT(mount(dir1.path().c_str(), dir2.path().c_str(), "", MS_BIND, 0), - SyscallSucceeds()); - ASSERT_THAT(mount("", dir3.path().c_str(), "tmpfs", 0, ""), - SyscallSucceeds()); + auto const mnt2 = ASSERT_NO_ERRNO_AND_VALUE(Mount( + dir1.path().c_str(), dir2.path().c_str(), "", MS_BIND, "", MNT_DETACH)); + auto const mnt3 = ASSERT_NO_ERRNO_AND_VALUE( + Mount("", dir3.path().c_str(), "tmpfs", 0, "", MNT_DETACH)); ASSERT_THAT(mount("", dir3.path().c_str(), "", MS_SHARED, 0), SyscallSucceeds()); - ASSERT_THAT(mount(dir3.path().c_str(), dir4.path().c_str(), "", MS_BIND, 0), - SyscallSucceeds()); + auto const mnt4 = ASSERT_NO_ERRNO_AND_VALUE(Mount( + dir3.path().c_str(), dir4.path().c_str(), "", MS_BIND, "", MNT_DETACH)); const std::string dir5_path2 = JoinPath(dir2.path(), Basename(dir5.path())); // Bind peer group 'A' to peer group 'B'. - ASSERT_THAT(mount(dir4.path().c_str(), dir5.path().c_str(), "", MS_BIND, 0), - SyscallSucceeds()); + auto const mnt5 = ASSERT_NO_ERRNO_AND_VALUE(Mount( + dir4.path().c_str(), dir5.path().c_str(), "", MS_BIND, "", MNT_DETACH)); - std::vector mounts = - ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountInfoEntries()); - // The new mounts should all be peers with the old ones. - // Optional string should be in the format shared:x. - - std::string opt1, opt2, opt3, opt4; - for (const auto& e : mounts) { - if (e.mount_point == dir3.path()) { - opt1 = e.optional; - } - if (e.mount_point == dir4.path()) { - opt2 = e.optional; - } - if (e.mount_point == dir5.path()) { - opt3 = e.optional; - } - if (e.mount_point == dir5_path2) { - opt4 = e.optional; - } - } - ASSERT_EQ(opt1, opt2); - ASSERT_EQ(opt2, opt3); - ASSERT_EQ(opt3, opt4); - ASSERT_TRUE(absl::StrContains(opt1, "shared:")); + auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals()); + ASSERT_FALSE(optionals[dir1.path()].empty()); + ASSERT_FALSE(optionals[dir2.path()].empty()); + ASSERT_FALSE(optionals[dir3.path()].empty()); + ASSERT_FALSE(optionals[dir4.path()].empty()); + ASSERT_FALSE(optionals[dir5.path()].empty()); + ASSERT_FALSE(optionals[dir5_path2].empty()); + EXPECT_EQ(optionals[dir3.path()][0].shared, optionals[dir4.path()][0].shared); + EXPECT_EQ(optionals[dir4.path()][0].shared, optionals[dir5.path()][0].shared); + EXPECT_EQ(optionals[dir5.path()][0].shared, optionals[dir5_path2][0].shared); + EXPECT_NE(optionals[dir3.path()][0].shared, 0); } TEST(MountTest, BindSharedOnPrivate) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - ASSERT_THAT(mount("", dir1.path().c_str(), "tmpfs", 0, ""), - SyscallSucceeds()); + auto const mnt1 = ASSERT_NO_ERRNO_AND_VALUE( + Mount("", dir1.path().c_str(), "tmpfs", 0, "", MNT_DETACH)); auto const dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir1.path())); auto const dir3 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const dir4 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - ASSERT_THAT(mount("", dir3.path().c_str(), "tmpfs", 0, ""), - SyscallSucceeds()); + auto const mnt3 = ASSERT_NO_ERRNO_AND_VALUE( + Mount("", dir3.path().c_str(), "tmpfs", 0, "", MNT_DETACH)); ASSERT_THAT(mount("", dir3.path().c_str(), "", MS_SHARED, 0), SyscallSucceeds()); - ASSERT_THAT(mount(dir3.path().c_str(), dir4.path().c_str(), "", MS_BIND, 0), - SyscallSucceeds()); + auto const mnt4 = ASSERT_NO_ERRNO_AND_VALUE(Mount( + dir3.path().c_str(), dir4.path().c_str(), "", MS_BIND, "", MNT_DETACH)); // bind to private mount. - ASSERT_THAT(mount(dir3.path().c_str(), dir2.path().c_str(), "", MS_BIND, 0), - SyscallSucceeds()); + auto const mnt2 = ASSERT_NO_ERRNO_AND_VALUE(Mount( + dir3.path().c_str(), dir2.path().c_str(), "", MS_BIND, "", MNT_DETACH)); - std::vector mounts = - ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountInfoEntries()); - std::string opt1, opt2, opt3; - for (const auto& e : mounts) { - if (e.mount_point == dir1.path()) { - ASSERT_EQ(e.optional, ""); - } - if (e.mount_point == dir2.path()) { - opt1 = e.optional; - ASSERT_TRUE(absl::StrContains(e.optional, "shared:")); - } - if (e.mount_point == dir3.path()) { - opt2 = e.optional; - ASSERT_TRUE(absl::StrContains(e.optional, "shared:")); - } - if (e.mount_point == dir4.path()) { - opt3 = e.optional; - ASSERT_TRUE(absl::StrContains(e.optional, "shared:")); - } - } - ASSERT_EQ(opt1, opt2); - ASSERT_EQ(opt2, opt3); + auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals()); + ASSERT_FALSE(optionals[dir1.path()].empty()); + ASSERT_FALSE(optionals[dir2.path()].empty()); + ASSERT_FALSE(optionals[dir3.path()].empty()); + ASSERT_FALSE(optionals[dir4.path()].empty()); + EXPECT_EQ(optionals[dir1.path()][0].shared, 0); + EXPECT_EQ(optionals[dir2.path()][0].shared, optionals[dir3.path()][0].shared); + EXPECT_EQ(optionals[dir3.path()][0].shared, optionals[dir4.path()][0].shared); } TEST(MountTest, BindPeerGroupsWithChildren) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - ASSERT_THAT(mount("", dir1.path().c_str(), "tmpfs", 0, ""), - SyscallSucceeds()); + auto const mnt1 = ASSERT_NO_ERRNO_AND_VALUE( + Mount("", dir1.path().c_str(), "tmpfs", 0, "", MNT_DETACH)); ASSERT_THAT(mount("", dir1.path().c_str(), "", MS_SHARED, 0), SyscallSucceeds()); auto const dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - ASSERT_THAT(mount("", dir2.path().c_str(), "tmpfs", 0, ""), - SyscallSucceeds()); + auto const mnt2 = ASSERT_NO_ERRNO_AND_VALUE( + Mount("", dir2.path().c_str(), "tmpfs", 0, "", MNT_DETACH)); ASSERT_THAT(mount("", dir2.path().c_str(), "", MS_SHARED, 0), SyscallSucceeds()); // dir3 and dir4 are child mounts of dir1. auto const dir3 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir1.path())); - ASSERT_THAT(mount("", dir3.path().c_str(), "tmpfs", 0, ""), - SyscallSucceeds()); + auto const mnt3 = ASSERT_NO_ERRNO_AND_VALUE( + Mount("", dir3.path().c_str(), "tmpfs", 0, "", MNT_DETACH)); auto const dir4 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir1.path())); - ASSERT_THAT(mount("", dir4.path().c_str(), "tmpfs", 0, ""), - SyscallSucceeds()); - ASSERT_THAT(mount(dir1.path().c_str(), dir2.path().c_str(), "", MS_BIND, 0), - SyscallSucceeds()); + auto const mnt4 = ASSERT_NO_ERRNO_AND_VALUE( + Mount("", dir4.path().c_str(), "tmpfs", 0, "", MNT_DETACH)); + auto const mnt5 = ASSERT_NO_ERRNO_AND_VALUE(Mount( + dir1.path().c_str(), dir2.path().c_str(), "", MS_BIND, "", MNT_DETACH)); const std::string dir3_path2 = JoinPath(dir2.path(), Basename(dir3.path())); const std::string dir4_path2 = JoinPath(dir2.path(), Basename(dir4.path())); - std::vector mounts = - ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountInfoEntries()); - std::string opt1, opt2, opt3; - for (const auto& e : mounts) { - if (e.mount_point == dir1.path()) { - ASSERT_TRUE(absl::StrContains(e.optional, "shared:")); - opt1 = e.optional; - } - if (e.mount_point == dir3.path()) { - ASSERT_TRUE(absl::StrContains(e.optional, "shared:")); - opt2 = e.optional; - } - if (e.mount_point == dir4.path()) { - ASSERT_TRUE(absl::StrContains(e.optional, "shared:")); - opt3 = e.optional; - } - ASSERT_NE(e.mount_point, dir3_path2); - ASSERT_NE(e.mount_point, dir4_path2); - } - ASSERT_NE(opt1, opt2); - ASSERT_NE(opt2, opt3); - ASSERT_NE(opt3, opt1); + auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals()); + ASSERT_FALSE(optionals[dir1.path()].empty()); + ASSERT_FALSE(optionals[dir2.path()].empty()); + ASSERT_FALSE(optionals[dir3.path()].empty()); + + EXPECT_NE(optionals[dir1.path()][0].shared, optionals[dir3.path()][0].shared); + EXPECT_NE(optionals[dir3.path()][0].shared, optionals[dir4.path()][0].shared); + EXPECT_NE(optionals[dir4.path()][0].shared, optionals[dir1.path()][0].shared); + EXPECT_EQ(optionals[dir3_path2].size(), 0); + EXPECT_EQ(optionals[dir4_path2].size(), 0); } TEST(MountTest, BindParentToChild) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - ASSERT_THAT(mount(dir1.path().c_str(), dir1.path().c_str(), "", MS_BIND, ""), - SyscallSucceeds()); + auto const mnt1 = ASSERT_NO_ERRNO_AND_VALUE(Mount( + dir1.path().c_str(), dir1.path().c_str(), "", MS_BIND, "", MNT_DETACH)); ASSERT_THAT(mount("", dir1.path().c_str(), "", MS_SHARED, 0), SyscallSucceeds()); auto const dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - ASSERT_THAT(mount(dir1.path().c_str(), dir2.path().c_str(), "", MS_BIND, ""), - SyscallSucceeds()); + auto const mnt2 = ASSERT_NO_ERRNO_AND_VALUE(Mount( + dir1.path().c_str(), dir2.path().c_str(), "", MS_BIND, "", MNT_DETACH)); auto const child_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir1.path())); - ASSERT_THAT( - mount(dir1.path().c_str(), child_dir.path().c_str(), "", MS_BIND, ""), - SyscallSucceeds()); + auto const mnt3 = ASSERT_NO_ERRNO_AND_VALUE( + Mount(dir1.path().c_str(), child_dir.path().c_str(), "", MS_BIND, "", + MNT_DETACH)); - std::vector mounts = - ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountInfoEntries()); - std::string opt1, opt2, opt3; - for (const auto& e : mounts) { - if (e.mount_point == dir1.path()) { - opt1 = e.optional; - } - if (e.mount_point == dir2.path()) { - opt2 = e.optional; - } - if (e.mount_point == child_dir.path()) { - opt3 = e.optional; - } - } - ASSERT_TRUE(absl::StrContains(opt1, "shared:")); - ASSERT_EQ(opt1, opt2); - ASSERT_EQ(opt2, opt3); + auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals()); + ASSERT_FALSE(optionals[dir1.path()].empty()); + ASSERT_FALSE(optionals[dir2.path()].empty()); + ASSERT_FALSE(optionals[child_dir.path()].empty()); + + EXPECT_EQ(optionals[dir1.path()][0].shared, optionals[dir2.path()][0].shared); + EXPECT_EQ(optionals[dir2.path()][0].shared, + optionals[child_dir.path()][0].shared); + EXPECT_NE(optionals[dir1.path()][0].shared, 0); } TEST(MountTest, MountInfoHasRoot) { @@ -1445,10 +1320,11 @@ TEST(MountTest, MountInfoHasRoot) { ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountInfoEntries()); for (const auto& e : mounts) { if (e.mount_point == child.path()) { - ASSERT_EQ(e.root, JoinPath("/", Basename(child.path()))); + EXPECT_EQ(e.root, JoinPath("/", Basename(child.path()))) + << "Offending mount ID is: " << e.id; } if (e.mount_point == parent.path()) { - ASSERT_EQ(e.root, "/"); + EXPECT_EQ(e.root, "/") << "Offending mount ID is: " << e.id; } } } diff --git a/test/util/BUILD b/test/util/BUILD index 26e779f21..2f8cb1f2d 100644 --- a/test/util/BUILD +++ b/test/util/BUILD @@ -167,8 +167,10 @@ cc_library( deps = [ ":cleanup", ":posix_error", + ":temp_path", ":test_util", "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/status:statusor", "@com_google_absl//absl/strings", gtest, "@com_google_absl//absl/types:span", diff --git a/test/util/mount_util.cc b/test/util/mount_util.cc index f37c1e178..63e8684b9 100644 --- a/test/util/mount_util.cc +++ b/test/util/mount_util.cc @@ -17,10 +17,17 @@ #include #include +#include +#include +#include +#include + +#include "absl/container/flat_hash_map.h" #include "absl/strings/numbers.h" #include "absl/strings/str_join.h" #include "absl/strings/str_split.h" #include "absl/types/span.h" +#include "test/util/posix_error.h" namespace gvisor { namespace testing { @@ -176,5 +183,49 @@ absl::flat_hash_map ParseMountOptions( return entries; } +PosixErrorOr>> +MountOptionals() { + absl::flat_hash_map> optionals; + ASSIGN_OR_RETURN_ERRNO(std::vector mounts, + ProcSelfMountInfoEntries()); + for (const auto& e : mounts) { + MountOptional opt; + opt.shared = 0; + opt.master = 0; + opt.propagate_from = 0; + std::vector tags = absl::StrSplit(e.optional, ' '); + + for (std::string_view tag : tags) { + PosixError err = ParseOptionalTag(tag, &opt); + if (!err.ok()) return err; + } + if (optionals.contains(e.mount_point)) { + optionals[e.mount_point].push_back(opt); + } else { + optionals[e.mount_point] = {opt}; + } + } + return optionals; +} + +PosixError ParseOptionalTag(std::string_view tag, MountOptional* opt) { + std::vector key_value = absl::StrSplit(tag, ':'); + if (key_value.size() != 2) return PosixError(0); + if (key_value[0] == "shared") { + if (!absl::SimpleAtoi(key_value[1], &opt->shared)) + return PosixError(EINVAL, + "could not parse shared value in optional string"); + } else if (key_value[0] == "master") { + if (!absl::SimpleAtoi(key_value[1], &opt->master)) + return PosixError(EINVAL, + "could not parse master value in optional string"); + } else if (key_value[0] == "propagate_from") { + if (!absl::SimpleAtoi(key_value[1], &opt->propagate_from)) + return PosixError(EINVAL, + "could not parse propagate_from in optional string"); + } + return PosixError(0); +} + } // namespace testing } // namespace gvisor diff --git a/test/util/mount_util.h b/test/util/mount_util.h index 3f8a1c0f1..6f679cf2d 100644 --- a/test/util/mount_util.h +++ b/test/util/mount_util.h @@ -20,6 +20,8 @@ #include #include +#include +#include #include "gmock/gmock.h" #include "absl/container/flat_hash_map.h" @@ -93,6 +95,23 @@ PosixErrorOr> ProcSelfMountInfoEntriesFrom( absl::flat_hash_map ParseMountOptions( std::string mopts); +struct MountOptional { + int shared; + int master; + int propagate_from; +}; + +// MountOptionals returns a map of mount points to their optional fields as +// found in mountinfo. Duplicate mount points with different mount points will +// map to a vector of optionals. The order of the optionals is determined by +// their order in mountinfo (the order they were mounted in). +PosixErrorOr>> +MountOptionals(); + +// ParseOptionalTag is a helper that parses a single entry in a mount's +// set of optional tags. +PosixError ParseOptionalTag(std::string_view tag, MountOptional* opt); + } // namespace testing } // namespace gvisor