blob: 1940517f73a723e59034d313c8c1e1cbae18f789 [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 <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h>
#include <sys/wait.h>
#include <unistd.h>
#include <string>
#include <vector>
#include <base/command_line.h>
#include <base/strings/string_piece.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/files/scoped_file.h>
#include <base/logging.h>
#include <base/process/process.h>
#include <base/posix/eintr_wrapper.h>
#include <base/strings/stringprintf.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/string_util.h>
namespace {
constexpr int kDefaultSeverityStdout = 6;
constexpr int kDefaultSeverityStderr = 4;
constexpr char kSyslogSocketPath[] = "/run/rsyslogd/stdout";
void ShowUsage() {
fprintf(
stderr,
"Usage: syslog_cat [OPTION] -- target-command arguments...\n"
" options:\n"
" --identifier=IDENTIFIER specify the identifier of log.\n"
" --severity-stdout=PRIORITY specify the severity of log from\n"
" stdout. PRIORITY is a number 0-8.\n"
" --severity-stderr=PRIORITY specify the severity of log from\n"
" stderr. PRIORITY is a number 0-8.\n");
}
base::ScopedFD PrepareSocket(const std::string& identifier,
int severity,
int pid) {
DCHECK(!identifier.empty());
DCHECK_GE(severity, 0);
DCHECK_LE(severity, 7);
// Open the unix socket to write logs.
base::ScopedFD sock(HANDLE_EINTR(socket(AF_UNIX, SOCK_STREAM, 0)));
if (!sock.is_valid()) {
PLOG(ERROR) << "opening stream socket";
return base::ScopedFD();
}
// Connect the syslog unix socket file.
struct sockaddr_un server {};
server.sun_family = AF_UNIX;
std::strncpy(server.sun_path, kSyslogSocketPath, sizeof(kSyslogSocketPath));
if (HANDLE_EINTR(connect(sock.get(), (struct sockaddr*)&server,
sizeof(struct sockaddr_un))) < 0) {
PLOG(ERROR) << "connecting stream socket";
return base::ScopedFD();
}
// Construct the header string to send.
std::string header = base::StringPrintf("TAG=%s[%d]\nPRIORITY=%d\n\n",
identifier.c_str(), pid, severity);
// Send headers (tag and severity).
if (!base::WriteFileDescriptor(sock.get(), header.c_str(), header.size())) {
PLOG(ERROR) << "writing headers on stream socket";
return base::ScopedFD();
}
return sock;
}
int SeverityFromString(base::StringPiece severity_str) {
if (severity_str == "0" ||
base::CompareCaseInsensitiveASCII(severity_str, "emerg") == 0) {
return 0;
}
if (severity_str == "1" ||
base::CompareCaseInsensitiveASCII(severity_str, "alert") == 0) {
return 1;
}
if (severity_str == "2" ||
base::CompareCaseInsensitiveASCII(severity_str, "critical") == 0 ||
base::CompareCaseInsensitiveASCII(severity_str, "crit") == 0) {
return 2;
}
if (severity_str == "3" ||
base::CompareCaseInsensitiveASCII(severity_str, "err") == 0 ||
base::CompareCaseInsensitiveASCII(severity_str, "error") == 0) {
return 3;
}
if (severity_str == "4" ||
base::CompareCaseInsensitiveASCII(severity_str, "warn") == 0 ||
base::CompareCaseInsensitiveASCII(severity_str, "warning") == 0) {
return 4;
}
if (severity_str == "5" ||
base::CompareCaseInsensitiveASCII(severity_str, "notice") == 0) {
return 5;
}
if (severity_str == "6" ||
base::CompareCaseInsensitiveASCII(severity_str, "info") == 0) {
return 6;
}
if (severity_str == "7" ||
base::CompareCaseInsensitiveASCII(severity_str, "debug") == 0) {
return 7;
}
return -1;
}
// Extract a severity from the command line argument.
// Return the default severity if the argument is not specified
// Return "-1" if an invalid value is specified.
int ExtractSeverityFromCommnadLine(const base::CommandLine* command_line,
const char* switch_name,
int default_severity) {
if (!command_line->HasSwitch(switch_name))
return default_severity;
return SeverityFromString(command_line->GetSwitchValueASCII(switch_name));
}
bool CreateSocketAndBindToFD(const std::string& identifier,
int severity,
int pid,
int target_fd) {
base::ScopedFD sock = PrepareSocket(identifier, severity, pid);
if (!sock.is_valid()) {
LOG(ERROR) << "Failed to open the rsyslog socket for stderr.";
return false;
}
// Connect the socket to stderr.
if (HANDLE_EINTR(dup2(sock.get(), target_fd)) == -1) {
PLOG(ERROR) << "duping the stderr";
return false;
}
return true;
}
} // namespace
int main(int argc, char* argv[]) {
base::CommandLine::Init(argc, argv);
const base::CommandLine* command_line =
base::CommandLine::ForCurrentProcess();
const auto& sv = command_line->GetArgs();
if (sv.size() == 0 || command_line->HasSwitch("help")) {
ShowUsage();
return 1;
}
// Prepare a identifier.
constexpr char kIdentifierSwitchName[] = "identifier";
std::string identifier;
if (command_line->HasSwitch(kIdentifierSwitchName)) {
identifier = command_line->GetSwitchValueASCII(kIdentifierSwitchName);
} else {
const std::string& target_command_str = sv[0].c_str();
const base::FilePath& target_command_path =
base::FilePath(target_command_str);
identifier = target_command_path.BaseName().value();
}
if (identifier.empty()) {
LOG(ERROR) << "Failed to extract a identifier string.";
return 1;
}
// Prepare a severity for stdout.
constexpr char kSeverityOutSwitchName[] = "severity-stdout";
int severity_stdout = ExtractSeverityFromCommnadLine(
command_line, kSeverityOutSwitchName, kDefaultSeverityStdout);
if (severity_stdout < 0) {
LOG(ERROR) << "Invalid severity value. It must be a number between "
<< "0 (EMERG) and 7 (DEBUG) or valid severity string.";
return 1;
}
// Prepare a severity for stderr.
constexpr char kSeverityErrSwitchName[] = "severity-stderr";
int severity_stderr = ExtractSeverityFromCommnadLine(
command_line, kSeverityErrSwitchName, kDefaultSeverityStderr);
if (severity_stderr < 0) {
LOG(ERROR) << "Invalid severity value. It must be a number between "
<< "0 (EMERG) and 7 (DEBUG) or valid severity string.";
return 1;
}
// Prepare a pid.
base::ProcessId pid = base::Process::Current().Pid();
// Prepare a command line for the target process.
int target_command_argc = sv.size();
std::vector<const char*> target_command_argv(target_command_argc + 1);
for (int i = 0; i < target_command_argc; i++)
target_command_argv[i] = sv[i].c_str();
target_command_argv[target_command_argc] = nullptr;
// Open the unix socket to redirect logs from stdout (and maybe stderr).
bool ret_stdout =
CreateSocketAndBindToFD(identifier, severity_stdout, pid, STDOUT_FILENO);
CHECK(ret_stdout) << "Failed to bind stdout.";
// Open the unix socket to redirect logs from stderr.
// We prepare a sock for stderr even if the severarities are same, in order to
// prevent interleave of simultaneous lines.
bool ret_stderr =
CreateSocketAndBindToFD(identifier, severity_stderr, pid, STDERR_FILENO);
CHECK(ret_stderr) << "Failed to bind stderr.";
// Execute the target process.
execvp(const_cast<char*>(sv[0].c_str()),
const_cast<char**>(target_command_argv.data()));
/////////////////////////////////////////////////////////////////////////////
// The code below should not be executed if the execvp() above succeeds.
PLOG(ERROR) << "execvp '" << sv[0] << "'";
exit(1);
}