crosh: Make dmesg use debugd

Instead of calling dmesg from crosh directly, make a call to the debugd
dmesg tool. In the process, refactor the dmesg command to use crosh
rust.

BUG=chromium:1154528
TEST=Open crosh and run dmesg

Change-Id: I5bcf2aec2b511f9d41792c226e14b7a47f0300d2
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform2/+/2599604
Tested-by: Nicole Anderson-Au <nvaa@google.com>
Reviewed-by: Mike Frysinger <vapier@chromium.org>
Commit-Queue: Nicole Anderson-Au <nvaa@google.com>
diff --git a/crosh/Cargo.toml b/crosh/Cargo.toml
index 0e4a8b1..45663f1 100644
--- a/crosh/Cargo.toml
+++ b/crosh/Cargo.toml
@@ -10,6 +10,7 @@
 
 [dependencies]
 dbus = "0.8"
+getopts = "0.2"
 libc = "0.2.44"
 remain = "*"
 regex = "1.0.6"
diff --git a/crosh/crosh b/crosh/crosh
index 56da92c..794de14 100755
--- a/crosh/crosh
+++ b/crosh/crosh
@@ -1788,22 +1788,6 @@
 EOF
 )
 
-USAGE_dmesg='[-d|-H|-k|-L|-P|-p|-r|-T|-t|-u|-w|-x]'
-HELP_dmesg='
-  Display kernel log buffer
-'
-cmd_dmesg() (
-  # We allow only a few options.
-  local opt
-  for opt in "$@"; do
-    if ! printf '%s' "${opt}" | grep -sqE '^-[dHkLPprTtuwx]+$'; then
-      help "unknown option: $*"
-      return 1
-    fi
-  done
-  dmesg "$@"
-)
-
 USAGE_free='[options]'
 HELP_free='
   Display free/used memory info
diff --git a/crosh/src/base/dmesg.rs b/crosh/src/base/dmesg.rs
new file mode 100644
index 0000000..c6dac01
--- /dev/null
+++ b/crosh/src/base/dmesg.rs
@@ -0,0 +1,108 @@
+// 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.
+
+// Provides the command "dmesg" for crosh through debugd.
+
+use std::collections::HashMap;
+use std::io::Write;
+use std::time::Duration;
+
+use dbus::arg::{self, Variant};
+use dbus::blocking::Connection;
+use getopts::{self, Options};
+use sys_util::error;
+use system_api::client::OrgChromiumDebugd;
+
+use crate::dispatcher::{self, Arguments, Command, Dispatcher};
+use crate::util::TIMEOUT_MILLIS;
+
+/* We keep a reduced set of options here for security */
+const FLAGS: [(&str, &str, &str); 10] = [
+    (
+        "d",
+        "show_delta",
+        "Display the timestamp and the time delta
+            spent between messages",
+    ),
+    ("H", "human", "Enable human-readable output."),
+    ("k", "kernel", "Print kernel messages."),
+    ("L", "color", "Colorize the output."),
+    (
+        "p",
+        "force-prefix",
+        "Add facility, level or timestamp
+            information to each line of a multi-line message.",
+    ),
+    ("r", "raw", "Print the raw message buffer."),
+    ("T", "ctime", "Print human-readable timestamps."),
+    ("t", "notime", "Do not print kernel's timestamps."),
+    ("u", "userspace", "Print userspace messages."),
+    (
+        "x",
+        "decode",
+        "Decode facility and level (priority) numbers
+            to human-readable prefixes.",
+    ),
+];
+
+pub fn register(dispatcher: &mut Dispatcher) {
+    dispatcher.register_command(
+        Command::new(
+            "dmesg".to_string(),
+            "".to_string(),
+            "Run the dmesg command via debugd.".to_string(),
+        )
+        .set_command_callback(Some(execute_dmesg))
+        .set_help_callback(dmesg_help),
+    );
+}
+
+fn dmesg_help(_cmd: &Command, w: &mut dyn Write, _level: usize) {
+    let mut help = "Usage: dmesg [options]\n".to_string();
+    for flag in FLAGS.iter() {
+        help.push_str(&format!("\t-{}, --{}\n\t\t{}\n", flag.0, flag.1, flag.2));
+    }
+    w.write_all(help.as_bytes()).unwrap();
+    w.flush().unwrap();
+}
+
+fn execute_dmesg(_cmd: &Command, args: &Arguments) -> Result<(), dispatcher::Error> {
+    let mut opts = Options::new();
+
+    for flag in FLAGS.iter() {
+        opts.optflag(flag.0, flag.1, flag.2);
+    }
+
+    let matches = opts
+        .parse(args.get_tokens())
+        .map_err(|_| dispatcher::Error::CommandReturnedError)?;
+
+    let mut dbus_options = HashMap::new();
+    for flag in FLAGS.iter() {
+        let name = flag.1;
+        if matches.opt_present(name) {
+            let val_true: Variant<Box<dyn arg::RefArg>> = Variant(Box::new(1));
+            dbus_options.insert(name, val_true);
+        }
+    }
+
+    let connection = Connection::new_system().map_err(|err| {
+        error!("ERROR: Failed to get D-Bus connection: {}", err);
+        dispatcher::Error::CommandReturnedError
+    })?;
+
+    let conn_path = connection.with_proxy(
+        "org.chromium.debugd",
+        "/org/chromium/debugd",
+        Duration::from_millis(TIMEOUT_MILLIS),
+    );
+
+    let output = conn_path.call_dmesg(dbus_options).map_err(|err| {
+        println!("ERROR: Got unexpected result: {}", err);
+        dispatcher::Error::CommandReturnedError
+    })?;
+    // Print the response.
+    print!("{}", output);
+    Ok(())
+}
diff --git a/crosh/src/base/mod.rs b/crosh/src/base/mod.rs
index cb5df1d..b3465a8 100644
--- a/crosh/src/base/mod.rs
+++ b/crosh/src/base/mod.rs
@@ -6,6 +6,7 @@
 
 mod arc;
 mod ccd_pass;
+mod dmesg;
 mod set_time;
 mod verify_ro;
 mod vmc;
@@ -15,6 +16,7 @@
 pub fn register(dispatcher: &mut Dispatcher) {
     arc::register(dispatcher);
     ccd_pass::register(dispatcher);
+    dmesg::register(dispatcher);
     set_time::register(dispatcher);
     verify_ro::register(dispatcher);
     vmc::register(dispatcher);
diff --git a/crosh/src/legacy.rs b/crosh/src/legacy.rs
index c4a44b8..3513cdd 100644
--- a/crosh/src/legacy.rs
+++ b/crosh/src/legacy.rs
@@ -22,7 +22,6 @@
     "cras",
     "cryptohome_status",
     "diag",
-    "dmesg",
     "dump_emk",
     "enroll_status",
     "evtest",
diff --git a/debugd/src/dmesg_tool.cc b/debugd/src/dmesg_tool.cc
index aa70520..413c84f 100644
--- a/debugd/src/dmesg_tool.cc
+++ b/debugd/src/dmesg_tool.cc
@@ -24,7 +24,7 @@
                           std::string* output) {
   ProcessWithOutput process;
 
-  process.SetCapabilities(CAP_TO_MASK(CAP_SYSLOG));
+  process.SetCapabilities(CAP_TO_MASK(CAP_SYS_ADMIN));
   if (!process.Init()) {
     *output = "<process init failed>";
     return false;
@@ -32,15 +32,16 @@
 
   process.AddArg(kDmesgPath);
 
-  if (!AddIntOption(&process, options, "show-delta", "-d", error) ||
-      !AddIntOption(&process, options, "human", "-H", error) ||
-      !AddIntOption(&process, options, "kernel", "-k", error) ||
-      !AddIntOption(&process, options, "force-prefix", "-p", error) ||
-      !AddIntOption(&process, options, "raw", "-r", error) ||
-      !AddIntOption(&process, options, "ctime", "-T", error) ||
-      !AddIntOption(&process, options, "notime", "-t", error) ||
-      !AddIntOption(&process, options, "userspace", "-u", error) ||
-      !AddIntOption(&process, options, "decode", "-x", error)) {
+  if (!AddBoolOption(&process, options, "show-delta", "-d", error) ||
+      !AddBoolOption(&process, options, "human", "--human", error) ||
+      !AddBoolOption(&process, options, "kernel", "-k", error) ||
+      !AddBoolOption(&process, options, "color", "--color=always", error) ||
+      !AddBoolOption(&process, options, "force-prefix", "-p", error) ||
+      !AddBoolOption(&process, options, "raw", "-r", error) ||
+      !AddBoolOption(&process, options, "ctime", "-T", error) ||
+      !AddBoolOption(&process, options, "notime", "-t", error) ||
+      !AddBoolOption(&process, options, "userspace", "-u", error) ||
+      !AddBoolOption(&process, options, "decode", "-x", error)) {
     *output = "<invalid option>";
     return false;
   }
diff --git a/debugd/src/variant_utils.cc b/debugd/src/variant_utils.cc
index 269eebb..5ecc721 100644
--- a/debugd/src/variant_utils.cc
+++ b/debugd/src/variant_utils.cc
@@ -19,4 +19,17 @@
   return result != ParseResult::PARSE_ERROR;
 }
 
+bool AddBoolOption(SandboxedProcess* process,
+                   const brillo::VariantDictionary& options,
+                   const std::string& key,
+                   const std::string& flag_name,
+                   brillo::ErrorPtr* error) {
+  int value;
+  ParseResult result = GetOption(options, key, &value, error);
+  if (result == ParseResult::PARSED && value)
+    process->AddArg(flag_name);
+
+  return result != ParseResult::PARSE_ERROR;
+}
+
 }  // namespace debugd
diff --git a/debugd/src/variant_utils.h b/debugd/src/variant_utils.h
index fbe7e15..025b545 100644
--- a/debugd/src/variant_utils.h
+++ b/debugd/src/variant_utils.h
@@ -64,6 +64,15 @@
                   const std::string& flag_name,
                   brillo::ErrorPtr* error);
 
+// Looks up an option in the |options| dictionary. If it exists and
+// isn't a boolean, returns false. Otherwise, returns true, and if it
+// exists in the dictionary adds it to the command line for |process|.
+bool AddBoolOption(SandboxedProcess* process,
+                   const brillo::VariantDictionary& options,
+                   const std::string& key,
+                   const std::string& flag_name,
+                   brillo::ErrorPtr* error);
+
 }  // namespace debugd
 
 #endif  // DEBUGD_SRC_VARIANT_UTILS_H_