blob: 8278883d42ce6da92307124c4ebae05e897f0310 [file] [log] [blame]
// 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);
}