diff --git a/libmeminfo/libdmabufinfo/dmabufinfo_test.cpp b/libmeminfo/libdmabufinfo/dmabufinfo_test.cpp index 95aa2c79938aa0326aaed264cdc5107a38577c47..eb53e577f274cda16a969114bcb9db308e1f98d7 100644 --- a/libmeminfo/libdmabufinfo/dmabufinfo_test.cpp +++ b/libmeminfo/libdmabufinfo/dmabufinfo_test.cpp @@ -14,14 +14,17 @@ */ #include +#include #include +#include #include #include #include +#include #include -#include #include +#include #include #include @@ -50,6 +53,115 @@ struct ion_heap_data { #define DMA_BUF_SET_NAME _IOW(DMA_BUF_BASE, 5, const char*) #endif +class fd_sharer { + public: + fd_sharer(); + ~fd_sharer() { kill(); } + + bool ok() const { return child_pid > 0; } + bool sendfd(int fd); + bool kill(); + pid_t pid() const { return child_pid; } + + private: + unique_fd parent_fd, child_fd; + pid_t child_pid; + + void run(); +}; + +fd_sharer::fd_sharer() : parent_fd{}, child_fd{}, child_pid{-1} { + bool sp_ok = android::base::Socketpair(SOCK_STREAM, &parent_fd, &child_fd); + if (!sp_ok) return; + + child_pid = fork(); + if (child_pid < 0) return; + + if (child_pid == 0) run(); +} + +bool fd_sharer::kill() { + int err = ::kill(child_pid, SIGKILL); + if (err < 0) return false; + + return ::waitpid(child_pid, nullptr, 0) == child_pid; +} + +void fd_sharer::run() { + while (true) { + int fd; + char unused = 0; + + iovec iov{}; + iov.iov_base = &unused; + iov.iov_len = sizeof(unused); + + msghdr msg{}; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + char cmsg_buf[CMSG_SPACE(sizeof(fd))]; + msg.msg_control = cmsg_buf; + msg.msg_controllen = sizeof(cmsg_buf); + + cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(fd)); + + ssize_t s = TEMP_FAILURE_RETRY(recvmsg(child_fd, &msg, 0)); + if (s == -1) break; + + s = TEMP_FAILURE_RETRY(write(child_fd, &unused, sizeof(unused))); + if (s == -1) break; + } +} + +bool fd_sharer::sendfd(int fd) { + char unused = 0; + + iovec iov{}; + iov.iov_base = &unused; + iov.iov_len = sizeof(unused); + + msghdr msg{}; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + char cmsg_buf[CMSG_SPACE(sizeof(fd))]; + msg.msg_control = cmsg_buf; + msg.msg_controllen = sizeof(cmsg_buf); + + cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(fd)); + + int* fd_buf = reinterpret_cast(CMSG_DATA(cmsg)); + *fd_buf = fd; + + ssize_t s = TEMP_FAILURE_RETRY(sendmsg(parent_fd, &msg, 0)); + if (s == -1) return false; + + // The target process installs the fd into its fd table during recvmsg(). + // So if we return now, there's a brief window between sendfd() finishing + // and libmemoryinfo actually seeing that the buffer has been shared. This + // window is just large enough to break tests. + // + // To work around this, wait for the target process to respond with a dummy + // byte, with a timeout of 1 s. + pollfd p{}; + p.fd = parent_fd; + p.events = POLL_IN; + int ready = poll(&p, 1, 1000); + if (ready != 1) return false; + + s = TEMP_FAILURE_RETRY(read(parent_fd, &unused, sizeof(unused))); + if (s == -1) return false; + + return true; +} + #define EXPECT_ONE_BUF_EQ(_bufptr, _name, _fdrefs, _maprefs, _expname, _count, _size) \ do { \ EXPECT_EQ(_bufptr->name(), _name); \ @@ -140,6 +252,24 @@ class DmaBufTester : public ::testing::Test { return unique_fd{fd}; } + void readAndCheckDmaBuffer(std::vector* dmabufs, pid_t pid, const std::string name, + size_t fdrefs_size, size_t maprefs_size, const std::string exporter, + size_t refcount, uint64_t buf_size, bool expectFdrefs, + bool expectMapRefs) { + EXPECT_TRUE(ReadDmaBufInfo(pid, dmabufs)); + EXPECT_EQ(dmabufs->size(), 1UL); + EXPECT_ONE_BUF_EQ(dmabufs->begin(), name, fdrefs_size, maprefs_size, exporter, refcount, + buf_size); + // Make sure the buffer has the right pid too. + EXPECT_PID_IN_FDREFS(dmabufs->begin(), pid, expectFdrefs); + EXPECT_PID_IN_MAPREFS(dmabufs->begin(), pid, expectMapRefs); + } + + bool checkPidRef(DmaBuffer& dmabuf, pid_t pid, int expectFdrefs) { + int fdrefs = dmabuf.fdrefs().find(pid)->second; + return fdrefs == expectFdrefs; + } + private: int get_ion_heap_mask() { if (ion_fd < 0) { @@ -196,7 +326,7 @@ TEST_F(DmaBufTester, TestFdRef) { EXPECT_ONE_BUF_EQ(dmabufs.begin(), "dmabuftester-4k", 1UL, 0UL, "ion", 1UL, 4096ULL); // Make sure the buffer has the right pid too. - EXPECT_PID_IN_FDREFS(dmabufs.begin(), pid, false); + EXPECT_PID_IN_FDREFS(dmabufs.begin(), pid, true); } // Now make sure the buffer has disappeared @@ -222,8 +352,8 @@ TEST_F(DmaBufTester, TestMapRef) { EXPECT_ONE_BUF_EQ(dmabufs.begin(), "dmabuftester-4k", 1UL, 1UL, "ion", 2UL, 4096ULL); // Make sure the buffer has the right pid too. - EXPECT_PID_IN_FDREFS(dmabufs.begin(), pid, false); - EXPECT_PID_IN_MAPREFS(dmabufs.begin(), pid, false); + EXPECT_PID_IN_FDREFS(dmabufs.begin(), pid, true); + EXPECT_PID_IN_MAPREFS(dmabufs.begin(), pid, true); // close the file descriptor and re-read the stats buf.reset(-1); @@ -232,8 +362,8 @@ TEST_F(DmaBufTester, TestMapRef) { EXPECT_EQ(dmabufs.size(), 1UL); EXPECT_ONE_BUF_EQ(dmabufs.begin(), "", 0UL, 1UL, "", 0UL, 4096ULL); - EXPECT_PID_IN_FDREFS(dmabufs.begin(), pid, true); - EXPECT_PID_IN_MAPREFS(dmabufs.begin(), pid, false); + EXPECT_PID_IN_FDREFS(dmabufs.begin(), pid, false); + EXPECT_PID_IN_MAPREFS(dmabufs.begin(), pid, true); // unmap the bufer and lose all references munmap(ptr, 4096); @@ -244,6 +374,109 @@ TEST_F(DmaBufTester, TestMapRef) { EXPECT_TRUE(dmabufs.empty()); } +TEST_F(DmaBufTester, TestSharedfd) { + // Each time a shared buffer is received over a socket, the remote process + // will take an extra reference on it. + + ASSERT_TRUE(is_valid()); + + pid_t pid = getpid(); + std::vector dmabufs; + { + fd_sharer sharer{}; + ASSERT_TRUE(sharer.ok()); + // Allocate one buffer and make sure the library can see it + unique_fd buf = allocate(4096, "dmabuftester-4k"); + ASSERT_GT(buf, 0) << "Allocated buffer is invalid"; + readAndCheckDmaBuffer(&dmabufs, pid, "dmabuftester-4k", 1UL, 0UL, "ion", 1UL, 4096ULL, true, + false); + + ASSERT_TRUE(sharer.sendfd(buf)); + readAndCheckDmaBuffer(&dmabufs, pid, "dmabuftester-4k", 1UL, 0UL, "ion", 2UL, 4096ULL, true, + false); + EXPECT_TRUE(checkPidRef(dmabufs[0], pid, 1)); + readAndCheckDmaBuffer(&dmabufs, sharer.pid(), "dmabuftester-4k", 1UL, 0UL, "ion", 2UL, + 4096ULL, true, false); + EXPECT_TRUE(checkPidRef(dmabufs[0], sharer.pid(), 1)); + + ASSERT_TRUE(sharer.sendfd(buf)); + readAndCheckDmaBuffer(&dmabufs, pid, "dmabuftester-4k", 1UL, 0UL, "ion", 3UL, 4096ULL, true, + false); + EXPECT_TRUE(checkPidRef(dmabufs[0], pid, 1)); + readAndCheckDmaBuffer(&dmabufs, sharer.pid(), "dmabuftester-4k", 1UL, 0UL, "ion", 3UL, + 4096ULL, true, false); + EXPECT_TRUE(checkPidRef(dmabufs[0], sharer.pid(), 2)); + + ASSERT_TRUE(sharer.kill()); + readAndCheckDmaBuffer(&dmabufs, pid, "dmabuftester-4k", 1UL, 0UL, "ion", 1UL, 4096ULL, true, + false); + } + + // Now make sure the buffer has disappeared + ASSERT_TRUE(ReadDmaBufInfo(pid, &dmabufs)); + EXPECT_TRUE(dmabufs.empty()); +} + +TEST_F(DmaBufTester, DupFdTest) { + // dup()ing an fd will make this process take an extra reference on the + // shared buffer. + + ASSERT_TRUE(is_valid()); + + pid_t pid = getpid(); + std::vector dmabufs; + { + // Allocate one buffer and make sure the library can see it + unique_fd buf = allocate(4096, "dmabuftester-4k"); + ASSERT_GT(buf, 0) << "Allocated buffer is invalid"; + readAndCheckDmaBuffer(&dmabufs, pid, "dmabuftester-4k", 1UL, 0UL, "ion", 1UL, 4096ULL, true, + false); + + unique_fd buf2{dup(buf)}; + readAndCheckDmaBuffer(&dmabufs, pid, "dmabuftester-4k", 1UL, 0UL, "ion", 2UL, 4096ULL, true, + false); + EXPECT_TRUE(checkPidRef(dmabufs[0], pid, 2)); + + close(buf2.release()); + readAndCheckDmaBuffer(&dmabufs, pid, "dmabuftester-4k", 1UL, 0UL, "ion", 1UL, 4096ULL, true, + false); + EXPECT_TRUE(checkPidRef(dmabufs[0], pid, 1)); + } + + // Now make sure the buffer has disappeared + ASSERT_TRUE(ReadDmaBufInfo(pid, &dmabufs)); + EXPECT_TRUE(dmabufs.empty()); +} + +TEST_F(DmaBufTester, ForkTest) { + // fork()ing a child will cause the child to automatically take a reference + // on any existing shared buffers. + ASSERT_TRUE(is_valid()); + + pid_t pid = getpid(); + std::vector dmabufs; + { + // Allocate one buffer and make sure the library can see it + unique_fd buf = allocate(4096, "dmabuftester-4k"); + ASSERT_GT(buf, 0) << "Allocated buffer is invalid"; + readAndCheckDmaBuffer(&dmabufs, pid, "dmabuftester-4k", 1UL, 0UL, "ion", 1UL, 4096ULL, true, + false); + fd_sharer sharer{}; + ASSERT_TRUE(sharer.ok()); + readAndCheckDmaBuffer(&dmabufs, pid, "dmabuftester-4k", 1UL, 0UL, "ion", 2UL, 4096ULL, true, + false); + readAndCheckDmaBuffer(&dmabufs, sharer.pid(), "dmabuftester-4k", 1UL, 0UL, "ion", 2UL, + 4096ULL, true, false); + ASSERT_TRUE(sharer.kill()); + readAndCheckDmaBuffer(&dmabufs, pid, "dmabuftester-4k", 1UL, 0UL, "ion", 1UL, 4096ULL, true, + false); + } + + // Now make sure the buffer has disappeared + ASSERT_TRUE(ReadDmaBufInfo(pid, &dmabufs)); + EXPECT_TRUE(dmabufs.empty()); +} + int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); ::android::base::InitLogging(argv, android::base::StderrLogger); diff --git a/libmeminfo/libdmabufinfo/include/dmabufinfo/dmabufinfo.h b/libmeminfo/libdmabufinfo/include/dmabufinfo/dmabufinfo.h index 74eff3ccac711fe8964f1b5e8073bb219ebf79b0..e3be320b48f7d6edf2e8940b63b8efabe9032bec 100644 --- a/libmeminfo/libdmabufinfo/include/dmabufinfo/dmabufinfo.h +++ b/libmeminfo/libdmabufinfo/include/dmabufinfo/dmabufinfo.h @@ -44,7 +44,7 @@ struct DmaBuffer { } // Getters for each property - uint64_t size() { return size_; } + uint64_t size() const { return size_; } const std::unordered_map& fdrefs() const { return fdrefs_; } const std::unordered_map& maprefs() const { return maprefs_; } ino_t inode() const { return inode_; } @@ -56,6 +56,11 @@ struct DmaBuffer { void SetExporter(const std::string& exporter) { exporter_ = exporter; } void SetCount(uint64_t count) { count_ = count; } + bool operator==(const DmaBuffer& rhs) { + return (inode_ == rhs.inode()) && (size_ == rhs.size()) && (name_ == rhs.name()) && + (exporter_ == rhs.exporter()); + } + private: ino_t inode_; uint64_t size_;