Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Skip to content
Snippets Groups Projects
Commit d4542772 authored by Chenbo Feng's avatar Chenbo Feng Committed by Erick Reyes
Browse files

Add more tests to libdmabufinfo

Add some test to verify the refcount and fd reference is correct when
the dma_buf is shared between processes.

Bug; 63860998
Test: libdmabufinfo_test

Change-Id: Id22e68e7a65820f19847b2faab11c78e6d942d92
parent 055b5494
Branches
No related tags found
No related merge requests found
...@@ -14,14 +14,17 @@ ...@@ -14,14 +14,17 @@
*/ */
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <inttypes.h>
#include <linux/dma-buf.h> #include <linux/dma-buf.h>
#include <poll.h>
#include <sys/mman.h> #include <sys/mman.h>
#include <sys/types.h> #include <sys/types.h>
#include <unistd.h> #include <unistd.h>
#include <fstream>
#include <string> #include <string>
#include <vector>
#include <unordered_map> #include <unordered_map>
#include <vector>
#include <android-base/file.h> #include <android-base/file.h>
#include <android-base/logging.h> #include <android-base/logging.h>
...@@ -50,6 +53,115 @@ struct ion_heap_data { ...@@ -50,6 +53,115 @@ struct ion_heap_data {
#define DMA_BUF_SET_NAME _IOW(DMA_BUF_BASE, 5, const char*) #define DMA_BUF_SET_NAME _IOW(DMA_BUF_BASE, 5, const char*)
#endif #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<int*>(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) \ #define EXPECT_ONE_BUF_EQ(_bufptr, _name, _fdrefs, _maprefs, _expname, _count, _size) \
do { \ do { \
EXPECT_EQ(_bufptr->name(), _name); \ EXPECT_EQ(_bufptr->name(), _name); \
...@@ -140,6 +252,24 @@ class DmaBufTester : public ::testing::Test { ...@@ -140,6 +252,24 @@ class DmaBufTester : public ::testing::Test {
return unique_fd{fd}; return unique_fd{fd};
} }
void readAndCheckDmaBuffer(std::vector<DmaBuffer>* 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: private:
int get_ion_heap_mask() { int get_ion_heap_mask() {
if (ion_fd < 0) { if (ion_fd < 0) {
...@@ -196,7 +326,7 @@ TEST_F(DmaBufTester, TestFdRef) { ...@@ -196,7 +326,7 @@ TEST_F(DmaBufTester, TestFdRef) {
EXPECT_ONE_BUF_EQ(dmabufs.begin(), "dmabuftester-4k", 1UL, 0UL, "ion", 1UL, 4096ULL); EXPECT_ONE_BUF_EQ(dmabufs.begin(), "dmabuftester-4k", 1UL, 0UL, "ion", 1UL, 4096ULL);
// Make sure the buffer has the right pid too. // 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 // Now make sure the buffer has disappeared
...@@ -222,8 +352,8 @@ TEST_F(DmaBufTester, TestMapRef) { ...@@ -222,8 +352,8 @@ TEST_F(DmaBufTester, TestMapRef) {
EXPECT_ONE_BUF_EQ(dmabufs.begin(), "dmabuftester-4k", 1UL, 1UL, "ion", 2UL, 4096ULL); EXPECT_ONE_BUF_EQ(dmabufs.begin(), "dmabuftester-4k", 1UL, 1UL, "ion", 2UL, 4096ULL);
// Make sure the buffer has the right pid too. // 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);
EXPECT_PID_IN_MAPREFS(dmabufs.begin(), pid, false); EXPECT_PID_IN_MAPREFS(dmabufs.begin(), pid, true);
// close the file descriptor and re-read the stats // close the file descriptor and re-read the stats
buf.reset(-1); buf.reset(-1);
...@@ -232,8 +362,8 @@ TEST_F(DmaBufTester, TestMapRef) { ...@@ -232,8 +362,8 @@ TEST_F(DmaBufTester, TestMapRef) {
EXPECT_EQ(dmabufs.size(), 1UL); EXPECT_EQ(dmabufs.size(), 1UL);
EXPECT_ONE_BUF_EQ(dmabufs.begin(), "<unknown>", 0UL, 1UL, "<unknown>", 0UL, 4096ULL); EXPECT_ONE_BUF_EQ(dmabufs.begin(), "<unknown>", 0UL, 1UL, "<unknown>", 0UL, 4096ULL);
EXPECT_PID_IN_FDREFS(dmabufs.begin(), pid, true); EXPECT_PID_IN_FDREFS(dmabufs.begin(), pid, false);
EXPECT_PID_IN_MAPREFS(dmabufs.begin(), pid, false); EXPECT_PID_IN_MAPREFS(dmabufs.begin(), pid, true);
// unmap the bufer and lose all references // unmap the bufer and lose all references
munmap(ptr, 4096); munmap(ptr, 4096);
...@@ -244,6 +374,109 @@ TEST_F(DmaBufTester, TestMapRef) { ...@@ -244,6 +374,109 @@ TEST_F(DmaBufTester, TestMapRef) {
EXPECT_TRUE(dmabufs.empty()); 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<DmaBuffer> 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<DmaBuffer> 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<DmaBuffer> 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) { int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv); ::testing::InitGoogleTest(&argc, argv);
::android::base::InitLogging(argv, android::base::StderrLogger); ::android::base::InitLogging(argv, android::base::StderrLogger);
......
...@@ -44,7 +44,7 @@ struct DmaBuffer { ...@@ -44,7 +44,7 @@ struct DmaBuffer {
} }
// Getters for each property // Getters for each property
uint64_t size() { return size_; } uint64_t size() const { return size_; }
const std::unordered_map<pid_t, int>& fdrefs() const { return fdrefs_; } const std::unordered_map<pid_t, int>& fdrefs() const { return fdrefs_; }
const std::unordered_map<pid_t, int>& maprefs() const { return maprefs_; } const std::unordered_map<pid_t, int>& maprefs() const { return maprefs_; }
ino_t inode() const { return inode_; } ino_t inode() const { return inode_; }
...@@ -56,6 +56,11 @@ struct DmaBuffer { ...@@ -56,6 +56,11 @@ struct DmaBuffer {
void SetExporter(const std::string& exporter) { exporter_ = exporter; } void SetExporter(const std::string& exporter) { exporter_ = exporter; }
void SetCount(uint64_t count) { count_ = count; } 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: private:
ino_t inode_; ino_t inode_;
uint64_t size_; uint64_t size_;
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment