// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "mojo/edk/embedder/platform_channel_pair.h" #include #include #include #include #include #include #include #include #include #include #include #include "base/files/file_path.h" #include "base/files/file_util.h" #include "base/files/scoped_file.h" #include "base/files/scoped_temp_dir.h" #include "base/logging.h" #include "base/macros.h" #include "mojo/edk/embedder/platform_channel_utils_posix.h" #include "mojo/edk/embedder/platform_handle.h" #include "mojo/edk/embedder/platform_handle_vector.h" #include "mojo/edk/embedder/scoped_platform_handle.h" #include "mojo/edk/test/test_utils.h" #include "testing/gtest/include/gtest/gtest.h" namespace mojo { namespace edk { namespace { void WaitReadable(PlatformHandle h) { struct pollfd pfds = {}; pfds.fd = h.handle; pfds.events = POLLIN; CHECK_EQ(poll(&pfds, 1, -1), 1); } class PlatformChannelPairPosixTest : public testing::Test { public: PlatformChannelPairPosixTest() {} ~PlatformChannelPairPosixTest() override {} void SetUp() override { // Make sure |SIGPIPE| isn't being ignored. struct sigaction action = {}; action.sa_handler = SIG_DFL; ASSERT_EQ(0, sigaction(SIGPIPE, &action, &old_action_)); } void TearDown() override { // Restore the |SIGPIPE| handler. ASSERT_EQ(0, sigaction(SIGPIPE, &old_action_, nullptr)); } private: struct sigaction old_action_; DISALLOW_COPY_AND_ASSIGN(PlatformChannelPairPosixTest); }; TEST_F(PlatformChannelPairPosixTest, NoSigPipe) { PlatformChannelPair channel_pair; ScopedPlatformHandle server_handle = channel_pair.PassServerHandle(); ScopedPlatformHandle client_handle = channel_pair.PassClientHandle(); // Write to the client. static const char kHello[] = "hello"; EXPECT_EQ(static_cast(sizeof(kHello)), write(client_handle.get().handle, kHello, sizeof(kHello))); // Close the client. client_handle.reset(); // Read from the server; this should be okay. char buffer[100] = {}; EXPECT_EQ(static_cast(sizeof(kHello)), read(server_handle.get().handle, buffer, sizeof(buffer))); EXPECT_STREQ(kHello, buffer); // Try reading again. ssize_t result = read(server_handle.get().handle, buffer, sizeof(buffer)); // We should probably get zero (for "end of file"), but -1 would also be okay. EXPECT_TRUE(result == 0 || result == -1); if (result == -1) PLOG(WARNING) << "read (expected 0 for EOF)"; // Test our replacement for |write()|/|send()|. result = PlatformChannelWrite(server_handle.get(), kHello, sizeof(kHello)); EXPECT_EQ(-1, result); if (errno != EPIPE) PLOG(WARNING) << "write (expected EPIPE)"; // Test our replacement for |writev()|/|sendv()|. struct iovec iov[2] = {{const_cast(kHello), sizeof(kHello)}, {const_cast(kHello), sizeof(kHello)}}; result = PlatformChannelWritev(server_handle.get(), iov, 2); EXPECT_EQ(-1, result); if (errno != EPIPE) PLOG(WARNING) << "write (expected EPIPE)"; } TEST_F(PlatformChannelPairPosixTest, SendReceiveData) { PlatformChannelPair channel_pair; ScopedPlatformHandle server_handle = channel_pair.PassServerHandle(); ScopedPlatformHandle client_handle = channel_pair.PassClientHandle(); for (size_t i = 0; i < 10; i++) { std::string send_string(1 << i, 'A' + i); EXPECT_EQ(static_cast(send_string.size()), PlatformChannelWrite(server_handle.get(), send_string.data(), send_string.size())); WaitReadable(client_handle.get()); char buf[10000] = {}; std::deque received_handles; ssize_t result = PlatformChannelRecvmsg(client_handle.get(), buf, sizeof(buf), &received_handles); EXPECT_EQ(static_cast(send_string.size()), result); EXPECT_EQ(send_string, std::string(buf, static_cast(result))); EXPECT_TRUE(received_handles.empty()); } } TEST_F(PlatformChannelPairPosixTest, SendReceiveFDs) { base::ScopedTempDir temp_dir; ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); static const char kHello[] = "hello"; PlatformChannelPair channel_pair; ScopedPlatformHandle server_handle = channel_pair.PassServerHandle(); ScopedPlatformHandle client_handle = channel_pair.PassClientHandle(); // Reduce the number of FDs opened on OS X to avoid test flake. #if defined(OS_MACOSX) const size_t kNumHandlesToSend = kPlatformChannelMaxNumHandles / 2; #else const size_t kNumHandlesToSend = kPlatformChannelMaxNumHandles; #endif for (size_t i = 1; i < kNumHandlesToSend; i++) { // Make |i| files, with the j-th file consisting of j copies of the digit // |c|. const char c = '0' + (i % 10); ScopedPlatformHandleVectorPtr platform_handles(new PlatformHandleVector); for (size_t j = 1; j <= i; j++) { base::FilePath unused; base::ScopedFILE fp( base::CreateAndOpenTemporaryFileInDir(temp_dir.path(), &unused)); ASSERT_TRUE(fp); ASSERT_EQ(j, fwrite(std::string(j, c).data(), 1, j, fp.get())); platform_handles->push_back( test::PlatformHandleFromFILE(std::move(fp)).release()); ASSERT_TRUE(platform_handles->back().is_valid()); } // Send the FDs (+ "hello"). struct iovec iov = {const_cast(kHello), sizeof(kHello)}; // We assume that the |sendmsg()| actually sends all the data. EXPECT_EQ(static_cast(sizeof(kHello)), PlatformChannelSendmsgWithHandles(server_handle.get(), &iov, 1, &platform_handles->at(0), platform_handles->size())); WaitReadable(client_handle.get()); char buf[10000] = {}; std::deque received_handles; // We assume that the |recvmsg()| actually reads all the data. EXPECT_EQ(static_cast(sizeof(kHello)), PlatformChannelRecvmsg(client_handle.get(), buf, sizeof(buf), &received_handles)); EXPECT_STREQ(kHello, buf); EXPECT_EQ(i, received_handles.size()); for (size_t j = 0; !received_handles.empty(); j++) { base::ScopedFILE fp(test::FILEFromPlatformHandle( ScopedPlatformHandle(received_handles.front()), "rb")); received_handles.pop_front(); ASSERT_TRUE(fp); rewind(fp.get()); char read_buf[kNumHandlesToSend]; size_t bytes_read = fread(read_buf, 1, sizeof(read_buf), fp.get()); EXPECT_EQ(j + 1, bytes_read); EXPECT_EQ(std::string(j + 1, c), std::string(read_buf, bytes_read)); } } } TEST_F(PlatformChannelPairPosixTest, AppendReceivedFDs) { base::ScopedTempDir temp_dir; ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); static const char kHello[] = "hello"; PlatformChannelPair channel_pair; ScopedPlatformHandle server_handle = channel_pair.PassServerHandle(); ScopedPlatformHandle client_handle = channel_pair.PassClientHandle(); const std::string file_contents("hello world"); { base::FilePath unused; base::ScopedFILE fp( base::CreateAndOpenTemporaryFileInDir(temp_dir.path(), &unused)); ASSERT_TRUE(fp); ASSERT_EQ(file_contents.size(), fwrite(file_contents.data(), 1, file_contents.size(), fp.get())); ScopedPlatformHandleVectorPtr platform_handles(new PlatformHandleVector); platform_handles->push_back( test::PlatformHandleFromFILE(std::move(fp)).release()); ASSERT_TRUE(platform_handles->back().is_valid()); // Send the FD (+ "hello"). struct iovec iov = {const_cast(kHello), sizeof(kHello)}; // We assume that the |sendmsg()| actually sends all the data. EXPECT_EQ(static_cast(sizeof(kHello)), PlatformChannelSendmsgWithHandles(server_handle.get(), &iov, 1, &platform_handles->at(0), platform_handles->size())); } WaitReadable(client_handle.get()); // Start with an invalid handle in the deque. std::deque received_handles; received_handles.push_back(PlatformHandle()); char buf[100] = {}; // We assume that the |recvmsg()| actually reads all the data. EXPECT_EQ(static_cast(sizeof(kHello)), PlatformChannelRecvmsg(client_handle.get(), buf, sizeof(buf), &received_handles)); EXPECT_STREQ(kHello, buf); ASSERT_EQ(2u, received_handles.size()); EXPECT_FALSE(received_handles[0].is_valid()); EXPECT_TRUE(received_handles[1].is_valid()); { base::ScopedFILE fp(test::FILEFromPlatformHandle( ScopedPlatformHandle(received_handles[1]), "rb")); received_handles[1] = PlatformHandle(); ASSERT_TRUE(fp); rewind(fp.get()); char read_buf[100]; size_t bytes_read = fread(read_buf, 1, sizeof(read_buf), fp.get()); EXPECT_EQ(file_contents.size(), bytes_read); EXPECT_EQ(file_contents, std::string(read_buf, bytes_read)); } } } // namespace } // namespace edk } // namespace mojo