diff options
-rw-r--r-- | test/syscalls/linux/shm.cc | 189 |
1 files changed, 123 insertions, 66 deletions
diff --git a/test/syscalls/linux/shm.cc b/test/syscalls/linux/shm.cc index 9f57476c9..9d6d636d5 100644 --- a/test/syscalls/linux/shm.cc +++ b/test/syscalls/linux/shm.cc @@ -33,14 +33,6 @@ using ::testing::_; const uint64_t kAllocSize = kPageSize * 128ULL; -PosixErrorOr<int> Shmget(key_t key, size_t size, int shmflg) { - int id = shmget(key, size, shmflg); - if (id == -1) { - return PosixError(errno, "shmget() failed"); - } - return id; -} - PosixErrorOr<char*> Shmat(int shmid, const void* shmaddr, int shmflg) { const intptr_t addr = reinterpret_cast<intptr_t>(shmat(shmid, shmaddr, shmflg)); @@ -67,57 +59,109 @@ PosixErrorOr<int> Shmctl(int shmid, int cmd, T* buf) { return ret; } +// ShmSegment is a RAII object for automatically cleaning up shm segments. +class ShmSegment { + public: + explicit ShmSegment(int id) : id_(id) {} + + ~ShmSegment() { + if (id_ >= 0) { + EXPECT_NO_ERRNO(Rmid()); + id_ = -1; + } + } + + ShmSegment(ShmSegment&& other) : id_(other.release()) {} + + ShmSegment& operator=(ShmSegment&& other) { + id_ = other.release(); + return *this; + } + + ShmSegment(ShmSegment const& other) = delete; + ShmSegment& operator=(ShmSegment const& other) = delete; + + int id() const { return id_; } + + int release() { + int id = id_; + id_ = -1; + return id; + } + + PosixErrorOr<int> Rmid() { + RETURN_IF_ERRNO(Shmctl<void>(id_, IPC_RMID, nullptr)); + return release(); + } + + private: + int id_ = -1; +}; + +PosixErrorOr<int> ShmgetRaw(key_t key, size_t size, int shmflg) { + int id = shmget(key, size, shmflg); + if (id == -1) { + return PosixError(errno, "shmget() failed"); + } + return id; +} + +PosixErrorOr<ShmSegment> Shmget(key_t key, size_t size, int shmflg) { + ASSIGN_OR_RETURN_ERRNO(int id, ShmgetRaw(key, size, shmflg)); + return ShmSegment(id); +} + TEST(ShmTest, AttachDetach) { - const int id = ASSERT_NO_ERRNO_AND_VALUE( + const ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE( Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777)); struct shmid_ds attr; - ASSERT_NO_ERRNO(Shmctl(id, IPC_STAT, &attr)); + ASSERT_NO_ERRNO(Shmctl(shm.id(), IPC_STAT, &attr)); EXPECT_EQ(attr.shm_segsz, kAllocSize); EXPECT_EQ(attr.shm_nattch, 0); - const char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(id, nullptr, 0)); - ASSERT_NO_ERRNO(Shmctl(id, IPC_STAT, &attr)); + const char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0)); + ASSERT_NO_ERRNO(Shmctl(shm.id(), IPC_STAT, &attr)); EXPECT_EQ(attr.shm_nattch, 1); - const char* addr2 = ASSERT_NO_ERRNO_AND_VALUE(Shmat(id, nullptr, 0)); - ASSERT_NO_ERRNO(Shmctl(id, IPC_STAT, &attr)); + const char* addr2 = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0)); + ASSERT_NO_ERRNO(Shmctl(shm.id(), IPC_STAT, &attr)); EXPECT_EQ(attr.shm_nattch, 2); ASSERT_NO_ERRNO(Shmdt(addr)); - ASSERT_NO_ERRNO(Shmctl(id, IPC_STAT, &attr)); + ASSERT_NO_ERRNO(Shmctl(shm.id(), IPC_STAT, &attr)); EXPECT_EQ(attr.shm_nattch, 1); ASSERT_NO_ERRNO(Shmdt(addr2)); - ASSERT_NO_ERRNO(Shmctl(id, IPC_STAT, &attr)); + ASSERT_NO_ERRNO(Shmctl(shm.id(), IPC_STAT, &attr)); EXPECT_EQ(attr.shm_nattch, 0); } TEST(ShmTest, LookupByKey) { const TempPath keyfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); const key_t key = ftok(keyfile.path().c_str(), 1); - const int id = + const ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE(Shmget(key, kAllocSize, IPC_CREAT | 0777)); - const int id2 = ASSERT_NO_ERRNO_AND_VALUE(Shmget(key, kAllocSize, 0777)); - EXPECT_EQ(id, id2); + const int id2 = ASSERT_NO_ERRNO_AND_VALUE(ShmgetRaw(key, kAllocSize, 0777)); + EXPECT_EQ(shm.id(), id2); } TEST(ShmTest, DetachedSegmentsPersist) { - const int id = ASSERT_NO_ERRNO_AND_VALUE( + const ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE( Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777)); - char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(id, nullptr, 0)); + char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0)); addr[0] = 'x'; ASSERT_NO_ERRNO(Shmdt(addr)); // We should be able to re-attach to the same segment and get our data back. - addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(id, nullptr, 0)); + addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0)); EXPECT_EQ(addr[0], 'x'); ASSERT_NO_ERRNO(Shmdt(addr)); } TEST(ShmTest, MultipleDetachFails) { - const int id = ASSERT_NO_ERRNO_AND_VALUE( + const ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE( Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777)); - const char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(id, nullptr, 0)); + const char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0)); ASSERT_NO_ERRNO(Shmdt(addr)); EXPECT_THAT(Shmdt(addr), PosixErrorIs(EINVAL, _)); } @@ -128,7 +172,7 @@ TEST(ShmTest, IpcStat) { const time_t start = time(nullptr); - const int id = + const ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE(Shmget(key, kAllocSize, IPC_CREAT | 0777)); const uid_t uid = getuid(); @@ -136,7 +180,7 @@ TEST(ShmTest, IpcStat) { const pid_t pid = getpid(); struct shmid_ds attr; - ASSERT_NO_ERRNO(Shmctl(id, IPC_STAT, &attr)); + ASSERT_NO_ERRNO(Shmctl(shm.id(), IPC_STAT, &attr)); EXPECT_EQ(attr.shm_perm.__key, key); EXPECT_EQ(attr.shm_perm.uid, uid); @@ -163,8 +207,8 @@ TEST(ShmTest, IpcStat) { absl::SleepFor(absl::Seconds(1)); const time_t pre_attach = time(nullptr); - const char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(id, nullptr, 0)); - ASSERT_NO_ERRNO(Shmctl(id, IPC_STAT, &attr)); + const char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0)); + ASSERT_NO_ERRNO(Shmctl(shm.id(), IPC_STAT, &attr)); EXPECT_GE(attr.shm_atime, pre_attach); EXPECT_EQ(attr.shm_dtime, 0); @@ -176,7 +220,7 @@ TEST(ShmTest, IpcStat) { const time_t pre_detach = time(nullptr); ASSERT_NO_ERRNO(Shmdt(addr)); - ASSERT_NO_ERRNO(Shmctl(id, IPC_STAT, &attr)); + ASSERT_NO_ERRNO(Shmctl(shm.id(), IPC_STAT, &attr)); EXPECT_LT(attr.shm_atime, pre_detach); EXPECT_GE(attr.shm_dtime, pre_detach); @@ -191,7 +235,8 @@ TEST(ShmTest, ShmStat) { // general Linux host. SKIP_IF(!IsRunningOnGvisor()); - ASSERT_NO_ERRNO(Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777)); + const ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE( + Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777)); struct shmid_ds attr; ASSERT_NO_ERRNO(Shmctl(1, SHM_STAT, &attr)); // This does the same thing as IPC_STAT, so only test that the syscall @@ -225,14 +270,14 @@ TEST(ShmTest, ShmInfo) { EXPECT_EQ(info.shm_swp, 0); } - const int id = ASSERT_NO_ERRNO_AND_VALUE( + const ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE( Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777)); - const char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(id, nullptr, 0)); + const char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0)); ASSERT_NO_ERRNO(Shmctl(1, SHM_INFO, &info)); if (IsRunningOnGvisor()) { - ASSERT_NO_ERRNO(Shmctl(id, SHM_INFO, &info)); + ASSERT_NO_ERRNO(Shmctl(shm.id(), SHM_INFO, &info)); EXPECT_EQ(info.used_ids, 1); EXPECT_EQ(info.shm_tot, kAllocSize / kPageSize); EXPECT_EQ(info.shm_rss, kAllocSize / kPageSize); @@ -243,28 +288,28 @@ TEST(ShmTest, ShmInfo) { } TEST(ShmTest, ShmCtlSet) { - const int id = ASSERT_NO_ERRNO_AND_VALUE( + const ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE( Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777)); - const char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(id, nullptr, 0)); + const char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0)); struct shmid_ds attr; - ASSERT_NO_ERRNO(Shmctl(id, IPC_STAT, &attr)); + ASSERT_NO_ERRNO(Shmctl(shm.id(), IPC_STAT, &attr)); ASSERT_EQ(attr.shm_perm.mode, 0777); attr.shm_perm.mode = 0766; - ASSERT_NO_ERRNO(Shmctl(id, IPC_SET, &attr)); + ASSERT_NO_ERRNO(Shmctl(shm.id(), IPC_SET, &attr)); - ASSERT_NO_ERRNO(Shmctl(id, IPC_STAT, &attr)); + ASSERT_NO_ERRNO(Shmctl(shm.id(), IPC_STAT, &attr)); ASSERT_EQ(attr.shm_perm.mode, 0766); ASSERT_NO_ERRNO(Shmdt(addr)); } TEST(ShmTest, RemovedSegmentsAreMarkedDeleted) { - const int id = ASSERT_NO_ERRNO_AND_VALUE( + ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE( Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777)); - const char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(id, nullptr, 0)); - ASSERT_NO_ERRNO(Shmctl<void>(id, IPC_RMID, nullptr)); + const char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0)); + const int id = ASSERT_NO_ERRNO_AND_VALUE(shm.Rmid()); struct shmid_ds attr; ASSERT_NO_ERRNO(Shmctl(id, IPC_STAT, &attr)); EXPECT_NE(attr.shm_perm.mode & SHM_DEST, 0); @@ -272,29 +317,32 @@ TEST(ShmTest, RemovedSegmentsAreMarkedDeleted) { } TEST(ShmTest, RemovedSegmentsAreDestroyed) { - const int id = ASSERT_NO_ERRNO_AND_VALUE( + ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE( Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777)); - const char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(id, nullptr, 0)); + const char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0)); const uint64_t alloc_pages = kAllocSize / kPageSize; struct shm_info info; - ASSERT_NO_ERRNO(Shmctl(1, SHM_INFO, &info)); + ASSERT_NO_ERRNO(Shmctl(0 /*ignored*/, SHM_INFO, &info)); const uint64_t before = info.shm_tot; - ASSERT_NO_ERRNO(Shmctl<void>(id, IPC_RMID, nullptr)); + ASSERT_NO_ERRNO(shm.Rmid()); ASSERT_NO_ERRNO(Shmdt(addr)); - ASSERT_NO_ERRNO(Shmctl(1, SHM_INFO, &info)); - const uint64_t after = info.shm_tot; - EXPECT_EQ(after, before - alloc_pages); + ASSERT_NO_ERRNO(Shmctl(0 /*ignored*/, SHM_INFO, &info)); + if (IsRunningOnGvisor()) { + // No guarantees on system-wide shm memory usage on a generic linux host. + const uint64_t after = info.shm_tot; + EXPECT_EQ(after, before - alloc_pages); + } } TEST(ShmTest, AllowsAttachToRemovedSegmentWithRefs) { - const int id = ASSERT_NO_ERRNO_AND_VALUE( + ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE( Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777)); - const char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(id, nullptr, 0)); - ASSERT_NO_ERRNO(Shmctl<void>(id, IPC_RMID, nullptr)); + const char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0)); + const int id = ASSERT_NO_ERRNO_AND_VALUE(shm.Rmid()); const char* addr2 = ASSERT_NO_ERRNO_AND_VALUE(Shmat(id, nullptr, 0)); ASSERT_NO_ERRNO(Shmdt(addr)); ASSERT_NO_ERRNO(Shmdt(addr2)); @@ -303,17 +351,17 @@ TEST(ShmTest, AllowsAttachToRemovedSegmentWithRefs) { TEST(ShmTest, RemovedSegmentsAreNotDiscoverable) { const TempPath keyfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); const key_t key = ftok(keyfile.path().c_str(), 1); - const int id = + ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE(Shmget(key, kAllocSize, IPC_CREAT | 0777)); - ASSERT_NO_ERRNO(Shmctl<void>(id, IPC_RMID, nullptr)); + ASSERT_NO_ERRNO(shm.Rmid()); EXPECT_THAT(Shmget(key, kAllocSize, 0777), PosixErrorIs(ENOENT, _)); } TEST(ShmDeathTest, ReadonlySegment) { SetupGvisorDeathTest(); - const int id = ASSERT_NO_ERRNO_AND_VALUE( + const ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE( Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777)); - char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(id, nullptr, SHM_RDONLY)); + char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, SHM_RDONLY)); // Reading succeeds. static_cast<void>(addr[0]); // Writing fails. @@ -329,9 +377,16 @@ TEST(ShmDeathTest, SegmentNotAccessibleAfterDetach) { SetupGvisorDeathTest(); const auto rest = [&] { - const int id = ASSERT_NO_ERRNO_AND_VALUE( + ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE( Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777)); - char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(id, nullptr, 0)); + char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0)); + + // Mark the segment as destroyed so it's automatically cleaned up when we + // crash below. We can't rely on the standard cleanup since the destructor + // will not run after the SIGSEGV. Note that this doesn't destroy the + // segment immediately since we're still attached to it. + ASSERT_NO_ERRNO(shm.Rmid()); + addr[0] = 'x'; ASSERT_NO_ERRNO(Shmdt(addr)); @@ -366,7 +421,7 @@ TEST(ShmTest, RequestingUnalignedSizeSucceeds) { TEST(ShmTest, RequestingDuplicateCreationFails) { const TempPath keyfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); const key_t key = ftok(keyfile.path().c_str(), 1); - ASSERT_NO_ERRNO_AND_VALUE( + const ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE( Shmget(key, kAllocSize, IPC_CREAT | IPC_EXCL | 0777)); EXPECT_THAT(Shmget(key, kAllocSize, IPC_CREAT | IPC_EXCL | 0777), PosixErrorIs(EEXIST, _)); @@ -377,16 +432,17 @@ TEST(ShmTest, SegmentsSizeFixedOnCreation) { const key_t key = ftok(keyfile.path().c_str(), 1); // Base segment. - const int id = + const ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE(Shmget(key, kAllocSize, IPC_CREAT | 0777)); // Ask for the same segment at half size. This succeeds. - const int id2 = ASSERT_NO_ERRNO_AND_VALUE(Shmget(key, kAllocSize / 2, 0777)); + const int id2 = + ASSERT_NO_ERRNO_AND_VALUE(ShmgetRaw(key, kAllocSize / 2, 0777)); // Ask for the same segment at double size. EXPECT_THAT(Shmget(key, kAllocSize * 2, 0777), PosixErrorIs(EINVAL, _)); - char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(id, nullptr, 0)); + char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0)); char* addr2 = ASSERT_NO_ERRNO_AND_VALUE(Shmat(id2, nullptr, 0)); // We have 2 different maps... @@ -402,9 +458,9 @@ TEST(ShmTest, SegmentsSizeFixedOnCreation) { } TEST(ShmTest, PartialUnmap) { - const int id = ASSERT_NO_ERRNO_AND_VALUE( + const ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE( Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777)); - char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(id, nullptr, 0)); + char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0)); EXPECT_THAT(munmap(addr + (kAllocSize / 4), kAllocSize / 2), SyscallSucceeds()); ASSERT_NO_ERRNO(Shmdt(addr)); @@ -417,15 +473,16 @@ TEST(ShmTest, GracefullyFailOnZeroLenSegmentCreation) { } TEST(ShmTest, NoDestructionOfAttachedSegmentWithMultipleRmid) { - const int id = ASSERT_NO_ERRNO_AND_VALUE( + ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE( Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777)); - char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(id, nullptr, 0)); - char* addr2 = ASSERT_NO_ERRNO_AND_VALUE(Shmat(id, nullptr, 0)); + char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0)); + char* addr2 = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0)); // There should be 2 refs to the segment from the 2 attachments, and a single // self-reference. Mark the segment as destroyed more than 3 times through // shmctl(RMID). If there's a bug with the ref counting, this should cause the // count to drop to zero. + int id = shm.release(); for (int i = 0; i < 6; ++i) { ASSERT_NO_ERRNO(Shmctl<void>(id, IPC_RMID, nullptr)); } |