Add syslog-cat: the replacement of systemd-cat for rsyslog
This CL introduces the replacement of systemd-cat.
Summary:
- syslog-cat is a command to send stdout/stderr from a process to
the rsyslog plugin.
See go/cros-log-syslogcat for the detailed design.
BUG=chromium:1128630
TEST=Confirms the plugin works correctly
Change-Id: I679afda46059010412467cbe8b78c3439ef98f65
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform2/+/2371542
Tested-by: Yoshiki Iguchi <yoshiki@chromium.org>
Commit-Queue: Yoshiki Iguchi <yoshiki@chromium.org>
Auto-Submit: Yoshiki Iguchi <yoshiki@chromium.org>
Reviewed-by: Hidehiko Abe <hidehiko@chromium.org>
diff --git a/README.md b/README.md
index c110bfa..07a3d3a 100644
--- a/README.md
+++ b/README.md
@@ -122,6 +122,7 @@
| [smogcheck](./smogcheck/) | Developer library for working with raw I2C devices |
| [st_flash](./st_flash/) ||
| [storage_info](./storage_info/) | Helper shell functions for retrieving disk information) |
+| [syslog-cat](./syslog-cat/) | Helper command to forward stdout/stderr from process to syslog |
| [system-proxy](./system-proxy/) | Daemon for web proxy authentication support on Chrome OS |
| [system_api](./system_api/) | Headers and .proto files etc. to be shared with chromium |
| [thd](./thd/) | Thermal daemon to help keep systems running cool |
diff --git a/syslog-cat/BUILD.gn b/syslog-cat/BUILD.gn
new file mode 100644
index 0000000..e7e0f3b
--- /dev/null
+++ b/syslog-cat/BUILD.gn
@@ -0,0 +1,20 @@
+# 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.
+
+import("//common-mk/pkg_config.gni")
+
+group("all") {
+ deps = [ ":syslog-cat" ]
+}
+
+pkg_config("target_defaults") {
+ pkg_deps = [ "libchrome-${libbase_ver}" ]
+}
+
+executable("syslog-cat") {
+ configs += [ ":target_defaults" ]
+
+ sources = [ "main.cc" ]
+ install_path = "sbin"
+}
diff --git a/syslog-cat/OWNERS b/syslog-cat/OWNERS
new file mode 100644
index 0000000..407b30d
--- /dev/null
+++ b/syslog-cat/OWNERS
@@ -0,0 +1 @@
+yoshiki@chromium.org
diff --git a/syslog-cat/README.md b/syslog-cat/README.md
new file mode 100644
index 0000000..c523311
--- /dev/null
+++ b/syslog-cat/README.md
@@ -0,0 +1,3 @@
+Helper command to send stdout/stderr from a process to syslog.
+
+See go/cros-log-syslogcat for detail.
diff --git a/syslog-cat/main.cc b/syslog-cat/main.cc
new file mode 100644
index 0000000..1940517
--- /dev/null
+++ b/syslog-cat/main.cc
@@ -0,0 +1,240 @@
+// 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);
+}