| // Copyright 2020 The Chromium OS 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 "syslog-cat/syslogcat.h" |
| |
| #include <memory> |
| |
| #include <sys/socket.h> |
| #include <sys/un.h> |
| |
| #include <base/check.h> |
| #include <base/check_op.h> |
| #include <base/files/file_path.h> |
| #include <base/files/file_util.h> |
| #include <base/logging.h> |
| #include <base/posix/eintr_wrapper.h> |
| #include <base/strings/stringprintf.h> |
| #include <gtest/gtest.h> |
| |
| namespace { |
| |
| base::ScopedFD CreateDomainSocket(const base::FilePath& path) { |
| base::ScopedFD peer( |
| HANDLE_EINTR(socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0))); |
| |
| struct sockaddr_un addr {}; |
| addr.sun_family = AF_UNIX; |
| CHECK_GT(sizeof(addr.sun_path), path.value().length()); |
| strncpy(addr.sun_path, path.value().c_str(), sizeof(addr.sun_path)); |
| if (bind(peer.get(), (struct sockaddr*)&addr, sizeof(addr)) == -1) { |
| PLOG(ERROR) << "bind"; |
| return base::ScopedFD(); |
| } |
| |
| if (listen(peer.get(), 1) == -1) { |
| PLOG(ERROR) << "listen"; |
| return base::ScopedFD(); |
| } |
| |
| return peer; |
| } |
| |
| base::Optional<std::string> AcceptAndReadFromSocket(int fd, int size) { |
| struct sockaddr_un sun_client = {}; |
| socklen_t socklen = sizeof(sun_client); |
| |
| base::ScopedFD fd_client( |
| HANDLE_EINTR(accept(fd, (struct sockaddr*)&sun_client, &socklen))); |
| if (!fd_client.is_valid()) |
| return base::nullopt; |
| |
| const size_t kBufSize = 1000; |
| char buf[kBufSize]; |
| CHECK_GT(kBufSize, size); |
| EXPECT_TRUE(base::ReadFromFD(fd_client.get(), buf, size)); |
| return std::string(buf, size); |
| } |
| |
| } // anonymous namespace |
| |
| class SyslogCatTest : public ::testing::Test { |
| protected: |
| int GetStdOutFd() const { |
| CHECK(sock_stdout_.is_valid()); |
| return sock_stdout_.get(); |
| } |
| int GetStdErrFd() const { |
| CHECK(sock_stderr_.is_valid()); |
| return sock_stderr_.get(); |
| } |
| |
| pid_t ForkAndExecuteSyslogCat( |
| const std::vector<const char*>& target_command) { |
| const char kIdentifier[] = "IDENT"; |
| const int kSeverityStdout = 6; |
| const int kSeverityStderr = 4; |
| |
| pid_t child_pid = fork(); |
| if (child_pid == 0) { |
| // As a child process. |
| ExecuteCommandWithRedirection( |
| target_command[0], target_command, kIdentifier, kSeverityStdout, |
| kSeverityStderr, sock_path_stdout_, sock_path_stderr_); |
| |
| return -1; |
| } else { |
| // As a parent process. |
| // Wait for the child process to terminate. |
| wait(0); |
| |
| return child_pid; |
| } |
| } |
| |
| private: |
| void SetUp() override { |
| base::FilePath directory; |
| CHECK(base::GetTempDir(&directory)); |
| CHECK(base::CreateTemporaryDirInDir(directory, "syslogcat-test", |
| &temp_directory_)); |
| |
| sock_path_stdout_ = temp_directory_.Append("stdout.sock"); |
| sock_path_stderr_ = temp_directory_.Append("stderr.sock"); |
| |
| sock_stdout_ = CreateDomainSocket(sock_path_stdout_); |
| ASSERT_TRUE(sock_stdout_.is_valid()); |
| sock_stderr_ = CreateDomainSocket(sock_path_stderr_); |
| ASSERT_TRUE(sock_stderr_.is_valid()); |
| } |
| |
| void TearDown() override { base::DeletePathRecursively(temp_directory_); } |
| |
| base::FilePath temp_directory_; |
| base::FilePath sock_path_stdout_; |
| base::FilePath sock_path_stderr_; |
| base::ScopedFD sock_stdout_; |
| base::ScopedFD sock_stderr_; |
| }; |
| |
| TEST_F(SyslogCatTest, Echo) { |
| pid_t child_pid = ForkAndExecuteSyslogCat( |
| std::vector<const char*>({"/bin/echo", "1234567890", NULL})); |
| |
| std::string expected_stdout = |
| base::StringPrintf("TAG=IDENT[%d]\nPRIORITY=6\n\n1234567890", child_pid); |
| base::Optional<std::string> actual_stdout = |
| AcceptAndReadFromSocket(GetStdOutFd(), expected_stdout.length()); |
| EXPECT_TRUE(actual_stdout.has_value()); |
| EXPECT_EQ(expected_stdout, *actual_stdout); |
| |
| std::string expected_stderr = |
| base::StringPrintf("TAG=IDENT[%d]\nPRIORITY=4\n\n", child_pid); |
| base::Optional<std::string> actual_stderr = |
| AcceptAndReadFromSocket(GetStdErrFd(), expected_stderr.length()); |
| EXPECT_TRUE(actual_stderr.has_value()); |
| EXPECT_EQ(expected_stderr, *actual_stderr); |
| } |
| |
| TEST_F(SyslogCatTest, StdErr) { |
| pid_t child_pid = ForkAndExecuteSyslogCat(std::vector<const char*>( |
| {"/bin/bash", "-c", ">&2 echo 1234567890", NULL})); |
| |
| std::string expected_stdout = |
| base::StringPrintf("TAG=IDENT[%d]\nPRIORITY=6\n\n", child_pid); |
| base::Optional<std::string> actual_stdout = |
| AcceptAndReadFromSocket(GetStdOutFd(), expected_stdout.length()); |
| EXPECT_TRUE(actual_stdout.has_value()); |
| EXPECT_EQ(expected_stdout, *actual_stdout); |
| |
| std::string expected_stderr = |
| base::StringPrintf("TAG=IDENT[%d]\nPRIORITY=4\n\n1234567890", child_pid); |
| base::Optional<std::string> actual_stderr = |
| AcceptAndReadFromSocket(GetStdErrFd(), expected_stderr.length()); |
| EXPECT_TRUE(actual_stderr.has_value()); |
| EXPECT_EQ(expected_stderr, *actual_stderr); |
| } |
| |
| TEST_F(SyslogCatTest, StdOutAndErr) { |
| pid_t child_pid = ForkAndExecuteSyslogCat(std::vector<const char*>( |
| {"/bin/bash", "-c", "echo STDOUT; echo STDERR >&2; echo HELLO.", NULL})); |
| |
| std::string expected_stdout = base::StringPrintf( |
| "TAG=IDENT[%d]\nPRIORITY=6\n\nSTDOUT\nHELLO.", child_pid); |
| base::Optional<std::string> actual_stdout = |
| AcceptAndReadFromSocket(GetStdOutFd(), expected_stdout.length()); |
| EXPECT_TRUE(actual_stdout.has_value()); |
| EXPECT_EQ(expected_stdout, *actual_stdout); |
| |
| std::string expected_stderr = |
| base::StringPrintf("TAG=IDENT[%d]\nPRIORITY=4\n\nSTDERR", child_pid); |
| base::Optional<std::string> actual_stderr = |
| AcceptAndReadFromSocket(GetStdErrFd(), expected_stderr.length()); |
| EXPECT_TRUE(actual_stderr.has_value()); |
| EXPECT_EQ(expected_stderr, *actual_stderr); |
| } |