debugd: Have log_tool read file logs directly

log_tool aggregates logs from many different sources. The logs are specified
as the output of running a shell command. However, often this shell command is
just running cat on some file. There is no need to start another process just
to read a file from disk.

This change adds two different log types to log_tool: file logs, and command
logs. Command logs are the same as before; the output of a shell command. File
logs instead just specify a path, and that path is read directly from C++. The
process effective uid and effective gid are set before the reads so that file
permissions are respected.

Additionally, this change splits a couple of log entries that were the combined
output of two files into two different log entries.

Performance testing over 5 trials showed a .6 second improvement in response
time for log_tool when calling GetAllLogs.

TEST=load chrome://system
BUG=chromium:657156
BUG=chromium:925969

Change-Id: Iaf5b8cdbd216d4e1c55b2f6155095192aa18ccbe
Reviewed-on: https://chromium-review.googlesource.com/1479152
Commit-Ready: Fletcher Woodruff <fletcherw@chromium.org>
Tested-by: Fletcher Woodruff <fletcherw@chromium.org>
Reviewed-by: Mike Frysinger <vapier@chromium.org>
diff --git a/debugd/src/log_tool.cc b/debugd/src/log_tool.cc
index 7c46f7e..ee60843 100644
--- a/debugd/src/log_tool.cc
+++ b/debugd/src/log_tool.cc
@@ -4,12 +4,21 @@
 
 #include "debugd/src/log_tool.h"
 
+#include <grp.h>
+#include <inttypes.h>
 #include <lzma.h>
+#include <pwd.h>
+#include <stdint.h>
+#include <sys/types.h>
+#include <unistd.h>
 #include <memory>
 #include <string>
+#include <utility>
 #include <vector>
 
 #include <base/base64.h>
+#include <base/files/file.h>
+#include <base/files/file_path.h>
 #include <base/files/file_util.h>
 #include <base/json/json_writer.h>
 #include <base/logging.h>
@@ -44,150 +53,115 @@
 // Minimum time in seconds needed to allow shill to test active connections.
 const int kConnectionTesterTimeoutSeconds = 5;
 
-// Default maximum size of a log entry.
-constexpr const char kDefaultSizeCap[] = "512K";
-
 // Default running perf for 2 seconds.
 constexpr const int kPerfDurationSecs = 2;
 
-struct Log {
-  const char* name;
-  const char* command;
-  const char* user = nullptr;
-  const char* group = nullptr;
-  const char* size_cap = kDefaultSizeCap;  // passed as arg to 'tail -c'
-  LogTool::Encoding encoding = LogTool::Encoding::kAutodetect;
-};
-
 #define CMD_KERNEL_MODULE_PARAMS(module_name) \
     "cd /sys/module/" #module_name "/parameters 2>/dev/null && grep -sH ^ *"
 
-const Log kCommandLogs[] = {
+using Log = LogTool::Log;
+constexpr Log::LogType kCommand = Log::kCommand;
+constexpr Log::LogType kFile = Log::kFile;
+const std::vector<Log> kCommandLogs {
   // We need to enter init's mount namespace because it has /home/chronos
   // mounted which is where the consent knob lives.  We don't have that mount
   // in our own mount namespace (by design).  https://crbug.com/884249
-  { "CLIENT_ID", "/usr/bin/nsenter -t1 -m /usr/bin/metrics_client -i",
-    kRoot,
-    kDebugfsGroup,
-  },
-  { "LOGDATE", "/bin/date" },
-  { "atrus_logs", "cat /var/log/atrus.log 2>/dev/null" },
-  { "authpolicy", "cat /var/log/authpolicy.log" },
-  { "bootstat_summary", "/usr/bin/bootstat_summary"},
-  { "bio_crypto_init.log", "cat /var/log/bio_crypto_init/bio_crypto_init.log" },
-  { "biod.LATEST", "cat /var/log/biod/biod.LATEST" },
-  { "biod.PREVIOUS", "cat /var/log/biod/biod.PREVIOUS" },
-  { "bios_info", "cat /var/log/bios_info.txt" },
-  { "bios_log",
-    "cat /sys/firmware/log "
-    "/proc/device-tree/chosen/ap-console-buffer 2>/dev/null" },
-  { "bios_times", "cat /var/log/bios_times.txt" },
-  { "board-specific",
-    "/usr/share/userfeedback/scripts/get_board_specific_info" },
-  { "buddyinfo", "cat /proc/buddyinfo" },
-  { "cbi_info", "/usr/share/userfeedback/scripts/cbi_info", kRoot, kRoot},
-  { "cheets_log", "cat /var/log/arc.log 2>/dev/null" },
-  { "clobber.log", "cat /var/log/clobber.log 2>/dev/null" },
-  { "clobber-state.log", "cat /var/log/clobber-state.log 2>/dev/null" },
-  { "chrome_system_log", "cat /var/log/chrome/chrome" },
-  { "chrome_system_log.PREVIOUS", "cat /var/log/chrome/chrome.PREVIOUS" },
+  Log(kCommand, "CLIENT_ID",
+      "/usr/bin/nsenter -t1 -m /usr/bin/metrics_client -i", kRoot,
+      kDebugfsGroup),
+  Log(kCommand, "LOGDATE", "/bin/date"),
+  Log(kFile, "atrus_logs", "/var/log/atrus.log"),
+  Log(kFile, "authpolicy", "/var/log/authpolicy.log"),
+  Log(kCommand, "bootstat_summary", "/usr/bin/bootstat_summary"),
+  Log(kFile, "bio_crypto_init.log",
+      "/var/log/bio_crypto_init/bio_crypto_init.log"),
+  Log(kFile, "biod.LATEST", "/var/log/biod/biod.LATEST"),
+  Log(kFile, "biod.PREVIOUS", "/var/log/biod/biod.PREVIOUS"),
+  Log(kFile, "bios_info", "/var/log/bios_info.txt"),
+  Log(kCommand, "bios_log",
+      "cat /sys/firmware/log "
+      "/proc/device-tree/chosen/ap-console-buffer 2>/dev/null"),
+  Log(kFile, "bios_times", "/var/log/bios_times.txt"),
+  Log(kCommand, "board-specific",
+      "/usr/share/userfeedback/scripts/get_board_specific_info"),
+  Log(kFile, "buddyinfo", "/proc/buddyinfo"),
+  Log(kCommand, "cbi_info", "/usr/share/userfeedback/scripts/cbi_info", kRoot,
+      kRoot),
+  Log(kFile, "cheets_log", "/var/log/arc.log"),
+  Log(kFile, "clobber.log", "/var/log/clobber.log"),
+  Log(kFile, "clobber-state.log", "/var/log/clobber-state.log"),
+  Log(kFile, "chrome_system_log", "/var/log/chrome/chrome"),
+  Log(kFile, "chrome_system_log.PREVIOUS", "/var/log/chrome/chrome.PREVIOUS"),
   // There might be more than one record, so grab them all.
   // Plus, for <linux-3.19, it's named "console-ramoops", but for newer
   // versions, it's named "console-ramoops-#".
-  { "console-ramoops", "cat /sys/fs/pstore/console-ramoops* 2>/dev/null" },
-  { "cpuinfo", "cat /proc/cpuinfo" },
-  { "cr50_version", "cat /var/cache/cr50-version" },
-  { "cros_ec",
-    "cat /var/log/cros_ec.previous /var/log/cros_ec.log 2>/dev/null" },
-  { "cros_ec_panicinfo",
-    "cat /sys/kernel/debug/cros_ec/panicinfo 2>/dev/null",
-    SandboxedProcess::kDefaultUser,
-    kDebugfsGroup
-  },
-  { "cros_ec_pdinfo",
-    "cat /sys/kernel/debug/cros_ec/pdinfo 2>/dev/null",
-    SandboxedProcess::kDefaultUser,
-    kDebugfsGroup
-  },
-  { "cros_fp",
-    "cat /var/log/cros_fp.previous /var/log/cros_fp.log 2>/dev/null" },
-  { "dmesg", "/bin/dmesg" },
-  { "ec_info", "cat /var/log/ec_info.txt" },
+  Log(kCommand, "console-ramoops",
+      "cat /sys/fs/pstore/console-ramoops* 2>/dev/null"),
+  Log(kFile, "cpuinfo", "/proc/cpuinfo"),
+  Log(kFile, "cr50_version", "/var/cache/cr50-version"),
+  Log(kFile, "cros_ec.log", "/var/log/cros_ec.log"),
+  Log(kFile, "cros_ec.previous", "/var/log/cros_ec.previous"),
+  Log(kFile, "cros_ec_panicinfo", "/sys/kernel/debug/cros_ec/panicinfo",
+      SandboxedProcess::kDefaultUser, kDebugfsGroup),
+  Log(kFile, "cros_ec_pdinfo", "/sys/kernel/debug/cros_ec/pdinfo",
+      SandboxedProcess::kDefaultUser, kDebugfsGroup),
+  Log(kFile, "cros_fp.previous", "/var/log/cros_fp.previous"),
+  Log(kFile, "cros_fp.log", "/var/log/cros_fp.log"),
+  Log(kCommand, "dmesg", "/bin/dmesg"),
+  Log(kFile, "ec_info", "/var/log/ec_info.txt"),
   // The sed command replaces the EDID serial number (4 bytes at position 12)
   // with zeroes. See https://en.wikipedia.org/wiki/EDID#EDID_1.4_data_format.
-  { "edid-decode",
-    "for f in /sys/class/drm/card0-*/edid; do "
+  Log(kCommand, "edid-decode",
+      "for f in /sys/class/drm/card0-*/edid; do "
         "echo \"----------- ${f}\"; "
         "sed -E 's/^(.{11}).{4}/\\1\\x0\\x0\\x0\\x0/' \"${f}\" | "
         // edid-decode's stderr output is redundant, so silence it.
         "edid-decode 2>/dev/null; "
-        "done"
-  },
-  { "eventlog", "cat /var/log/eventlog.txt" },
-  {
-    "exynos_gem_objects",
-    "cat /sys/kernel/debug/dri/0/exynos_gem_objects 2>/dev/null",
-    SandboxedProcess::kDefaultUser,
-    kDebugfsGroup
-  },
-  { "font_info", "/usr/share/userfeedback/scripts/font_info" },
-  { "sensor_info", "/usr/share/userfeedback/scripts/sensor_info" },
-  { "hammerd", "cat /var/log/hammerd.log 2>/dev/null" },
-  { "hardware_class", "/usr/bin/crossystem hwid" },
-  { "hostname", "/bin/hostname" },
-  {
-    "i915_gem_gtt",
-    "cat /sys/kernel/debug/dri/0/i915_gem_gtt 2>/dev/null",
-    SandboxedProcess::kDefaultUser,
-    kDebugfsGroup
-  },
-  {
-    "i915_gem_objects",
-    "cat /sys/kernel/debug/dri/0/i915_gem_objects 2>/dev/null",
-    SandboxedProcess::kDefaultUser,
-    kDebugfsGroup
-  },
-  {
-    "i915_error_state",
-    "/usr/bin/xz -c /sys/kernel/debug/dri/0/i915_error_state 2>/dev/null",
-    SandboxedProcess::kDefaultUser,
-    kDebugfsGroup,
-    kDefaultSizeCap,
-    LogTool::Encoding::kBinary,
-  },
-  { "ifconfig", "/bin/ifconfig -a" },
-  { "input_devices", "cat /proc/bus/input/devices" },
+      "done"),
+  Log(kFile, "eventlog", "/var/log/eventlog.txt"),
+  Log(kFile, "exynos_gem_objects", "/sys/kernel/debug/dri/0/exynos_gem_objects",
+      SandboxedProcess::kDefaultUser, kDebugfsGroup),
+  Log(kCommand, "font_info", "/usr/share/userfeedback/scripts/font_info"),
+  Log(kCommand, "sensor_info", "/usr/share/userfeedback/scripts/sensor_info"),
+  Log(kFile, "hammerd", "/var/log/hammerd.log"),
+  Log(kCommand, "hardware_class", "/usr/bin/crossystem hwid"),
+  Log(kCommand, "hostname", "/bin/hostname"),
+  Log(kFile, "i915_gem_gtt", "/sys/kernel/debug/dri/0/i915_gem_gtt",
+      SandboxedProcess::kDefaultUser, kDebugfsGroup),
+  Log(kFile, "i915_gem_objects", "/sys/kernel/debug/dri/0/i915_gem_objects",
+      SandboxedProcess::kDefaultUser, kDebugfsGroup),
+  Log(kFile, "i915_gem_objects", "/sys/kernel/debug/dri/0/i915_gem_objects",
+      SandboxedProcess::kDefaultUser, kDebugfsGroup),
+  Log(kCommand, "i915_error_state",
+      "/usr/bin/xz -c /sys/kernel/debug/dri/0/i915_error_state 2>/dev/null",
+      SandboxedProcess::kDefaultUser, kDebugfsGroup, Log::kDefaultMaxBytes,
+      LogTool::Encoding::kBinary),
+  Log(kCommand, "ifconfig", "/bin/ifconfig -a"),
+  Log(kFile, "input_devices", "/proc/bus/input/devices"),
   // Information about the wiphy device, such as current channel.
-  { "iw_dev", "/usr/sbin/iw dev" },
+  Log(kCommand, "iw_dev", "/usr/sbin/iw dev"),
   // Hardware capabilities of the wiphy device.
-  { "iw_list", "/usr/sbin/iw list" },
+  Log(kCommand, "iw_list", "/usr/sbin/iw list"),
 #if USE_IWLWIFI_DUMP
-  { "iwlmvm_module_params", CMD_KERNEL_MODULE_PARAMS(iwlmvm) },
-  { "iwlwifi_module_params", CMD_KERNEL_MODULE_PARAMS(iwlwifi) },
+  Log(kCommand, "iwlmvm_module_params", CMD_KERNEL_MODULE_PARAMS(iwlmvm)),
+  Log(kCommand, "iwlwifi_module_params", CMD_KERNEL_MODULE_PARAMS(iwlwifi)),
 #endif  // USE_IWLWIFI_DUMP
-  { "kernel-crashes", "cat /var/spool/crash/kernel.*.kcrash 2>/dev/null" },
-  { "logcat",
-    "/usr/sbin/android-sh -c '/system/bin/logcat -d'",
-    kRoot,
-    kRoot,
-    kDefaultSizeCap,
-    LogTool::Encoding::kUtf8,
-  },
-  { "lsmod", "lsmod" },
-  { "lspci", "/usr/sbin/lspci" },
-  { "lsusb", "lsusb && lsusb -t" },
-  { "mali_memory", "cat /sys/class/misc/mali0/device/memory 2>/dev/null" },
-  { "memd.parameters", "cat /var/log/memd/memd.parameters 2>/dev/null" },
-  { "memd clips", "cat /var/log/memd/memd.clip* 2>/dev/null" },
-  { "meminfo", "cat /proc/meminfo" },
-  {
-    "memory_spd_info",
-    // mosys may use 'i2c-dev', which may not be loaded yet.
-    "modprobe i2c-dev 2>/dev/null && "
-        "mosys -l memory spd print all 2>/dev/null",
-    kRoot,
-    kDebugfsGroup,
-  },
+  Log(kCommand, "kernel-crashes",
+      "cat /var/spool/crash/kernel.*.kcrash 2>/dev/null"),
+  Log(kCommand, "logcat", "/usr/sbin/android-sh -c '/system/bin/logcat -d'",
+      kRoot, kRoot, Log::kDefaultMaxBytes, LogTool::Encoding::kUtf8),
+  Log(kCommand, "lsmod", "lsmod"),
+  Log(kCommand, "lspci", "/usr/sbin/lspci"),
+  Log(kCommand, "lsusb", "lsusb && lsusb -t"),
+  Log(kFile, "mali_memory", "/sys/class/misc/mali0/device/memory"),
+  Log(kFile, "memd.parameters", "/var/log/memd/memd.parameters"),
+  Log(kCommand, "memd clips", "cat /var/log/memd/memd.clip* 2>/dev/null"),
+  Log(kFile, "meminfo", "/proc/meminfo"),
+  Log(kCommand, "memory_spd_info",
+      // mosys may use 'i2c-dev', which may not be loaded yet.
+      "modprobe i2c-dev 2>/dev/null && "
+      "mosys -l memory spd print all 2>/dev/null",
+      kRoot, kDebugfsGroup),
   // The sed command finds the EDID blob (starting the line after "value:") and
   // replaces the serial number with all zeroes.
   //
@@ -205,141 +179,128 @@
   //
   // TODO(crbug.com/731133): Remove the sed command once modetest itself can
   // remove serial numbers.
-  {
-    "modetest",
-    "(modetest; modetest -M evdi; modetest -M udl) | "
-        "sed -E '/EDID/ {:a;n;/value:/!ba;n;"
-        "s/(00f{12}00)([0-9a-f]{8})([0-9a-f]{8})/\\1\\200000000/}'",
-    kRoot,
-    kRoot,
-  },
-  { "mount-encrypted", "cat /var/log/mount-encrypted.log" },
-  { "mountinfo", "cat /proc/1/mountinfo" },
-  { "netlog", "/usr/share/userfeedback/scripts/getmsgs /var/log/net.log" },
+  Log(kCommand, "modetest",
+      "(modetest; modetest -M evdi; modetest -M udl) | "
+      "sed -E '/EDID/ {:a;n;/value:/!ba;n;"
+      "s/(00f{12}00)([0-9a-f]{8})([0-9a-f]{8})/\\1\\200000000/}'",
+      kRoot, kRoot),
+  Log(kFile, "mount-encrypted", "/var/log/mount-encrypted.log"),
+  Log(kFile, "mountinfo", "/proc/1/mountinfo"),
+  Log(kCommand, "netlog",
+      "/usr/share/userfeedback/scripts/getmsgs /var/log/net.log"),
   // --processes requires root.
-  { "netstat", "/sbin/ss --all --query inet --numeric --processes",
-    kRoot,
-    kRoot,
-  },
-  {
-    "nvmap_iovmm",
-    "cat /sys/kernel/debug/nvmap/iovmm/allocations 2>/dev/null",
-    SandboxedProcess::kDefaultUser,
-    kDebugfsGroup,
-  },
-  { "oemdata", "/usr/share/cros/oemdata.sh", kRoot, kRoot, },
-  { "pagetypeinfo", "cat /proc/pagetypeinfo" },
-  {
-    "platform_info",
-    // mosys may use 'i2c-dev', which may not be loaded yet.
-    "modprobe i2c-dev 2>/dev/null && "
-        "for param in "
-          "vendor "
-          "name "
-          "version "
-          "family "
-          "model "
-          "sku "
-          "customization "
-        "; do "
-          "mosys -l platform \"${param}\" 2>/dev/null; "
-        "done",
-    kRoot,
-    kDebugfsGroup,
-  },
-  { "power_supply_info", "/usr/bin/power_supply_info" },
-  { "power_supply_sysfs", "/usr/bin/print_sysfs_power_supply_data" },
-  { "powerd.LATEST", "cat /var/log/power_manager/powerd.LATEST" },
-  { "powerd.PREVIOUS", "cat /var/log/power_manager/powerd.PREVIOUS" },
-  { "powerd.out", "cat /var/log/powerd.out" },
-  { "powerwash_count", "cat /var/log/powerwash_count 2>/dev/null" },
+  Log(kCommand, "netstat",
+      "/sbin/ss --all --query inet --numeric --processes", kRoot, kRoot),
+  Log(kFile, "nvmap_iovmm", "/sys/kernel/debug/nvmap/iovmm/allocations",
+      SandboxedProcess::kDefaultUser, kDebugfsGroup),
+  Log(kCommand, "oemdata", "/usr/share/cros/oemdata.sh", kRoot, kRoot),
+  Log(kFile, "pagetypeinfo", "/proc/pagetypeinfo"),
+  Log(kCommand, "platform_info",
+      // mosys may use 'i2c-dev', which may not be loaded yet.
+      "modprobe i2c-dev 2>/dev/null && "
+      "for param in "
+        "vendor "
+        "name "
+        "version "
+        "family "
+        "model "
+
+        "sku "
+        "customization "
+      "; do "
+        "mosys -l platform \"${param}\" 2>/dev/null; "
+      "done",
+      kRoot, kDebugfsGroup),
+  Log(kCommand, "power_supply_info", "/usr/bin/power_supply_info"),
+  Log(kCommand, "power_supply_sysfs",
+      "/usr/bin/print_sysfs_power_supply_data"),
+  Log(kFile, "powerd.LATEST", "/var/log/power_manager/powerd.LATEST"),
+  Log(kFile, "powerd.PREVIOUS", "/var/log/power_manager/powerd.PREVIOUS"),
+  Log(kFile, "powerd.out", "/var/log/powerd.out"),
+  Log(kFile, "powerwash_count", "/var/log/powerwash_count"),
   // Changed from 'ps ux' to 'ps aux' since we're running as debugd,
   // not chronos.
-  { "ps", "/bin/ps aux" },
+  Log(kCommand, "ps", "/bin/ps aux"),
   // /proc/slabinfo is owned by root and has 0400 permission.
-  { "slabinfo", "cat /proc/slabinfo", kRoot, kRoot, },
-  { "storage_info", "cat /var/log/storage_info.txt" },
-  { "swap_info",
-    "/usr/share/cros/init/swap.sh status 2>/dev/null",
-    SandboxedProcess::kDefaultUser,
-    kDebugfsGroup
-  },
-  { "syslog", "/usr/share/userfeedback/scripts/getmsgs /var/log/messages" },
-  { "system_log_stats", "echo 'BLOCK_SIZE=1024'; "
-    "find /var/log/ -type f -exec du --block-size=1024 {} + | sort -n -r",
-    kRoot, kRoot},
-  { "threads", "/bin/ps -T axo pid,ppid,spid,pcpu,ni,stat,time,comm" },
-  { "tlsdate", "cat /var/log/tlsdate.log" },
-  { "top thread", "/usr/bin/top -Hb -n 1 | head -n 40"},
-  { "top memory", "/usr/bin/top -o \"+%MEM\" -bn 1 | head -n 57"},
-  { "touch_fw_version", "grep -E"
-              " -e 'synaptics: Touchpad model'"
-              " -e 'chromeos-[a-z]*-touch-[a-z]*-update'"
-              " /var/log/messages | tail -n 20" },
-  {
-    "tpm-firmware-updater",
-    "/usr/share/userfeedback/scripts/getmsgs /var/log/tpm-firmware-updater.log"
-  },
+  Log(kFile, "slabinfo", "/proc/slabinfo", kRoot, kRoot),
+  Log(kFile, "storage_info", "/var/log/storage_info.txt"),
+  Log(kCommand, "swap_info",
+      "/usr/share/cros/init/swap.sh status 2>/dev/null",
+      SandboxedProcess::kDefaultUser, kDebugfsGroup),
+  Log(kCommand, "syslog",
+      "/usr/share/userfeedback/scripts/getmsgs /var/log/messages"),
+  Log(kCommand, "system_log_stats",
+      "echo 'BLOCK_SIZE=1024'; "
+      "find /var/log/ -type f -exec du --block-size=1024 {} + | sort -n -r",
+      kRoot, kRoot),
+  Log(kCommand, "threads",
+      "/bin/ps -T axo pid,ppid,spid,pcpu,ni,stat,time,comm"),
+  Log(kFile, "tlsdate", "/var/log/tlsdate.log"),
+  Log(kCommand, "top thread", "/usr/bin/top -Hb -n 1 | head -n 40"),
+  Log(kCommand, "top memory", "/usr/bin/top -o \"+%MEM\" -bn 1 | head -n 57"),
+  Log(kCommand, "touch_fw_version",
+      "grep -E"
+      " -e 'synaptics: Touchpad model'"
+      " -e 'chromeos-[a-z]*-touch-[a-z]*-update'"
+      " /var/log/messages | tail -n 20"),
+  Log(kCommand, "tpm-firmware-updater",
+      "/usr/share/userfeedback/scripts/getmsgs "
+      "/var/log/tpm-firmware-updater.log"),
   // TODO(jorgelo,mnissler): Don't run this as root.
   // On TPM 1.2 devices this will likely require adding a new user to the 'tss'
   // group.
   // On TPM 2.0 devices 'get_version_info' uses D-Bus and therefore can run as
   // any user.
-  { "tpm_version", "/usr/sbin/tpm-manager get_version_info", kRoot, kRoot },
-  { "atmel_ts_refs", "/opt/google/touch/scripts/atmel_tools.sh ts r",
-    kRoot, kRoot},
-  { "atmel_tp_refs", "/opt/google/touch/scripts/atmel_tools.sh tp r",
-    kRoot, kRoot},
-  { "atmel_ts_deltas", "/opt/google/touch/scripts/atmel_tools.sh ts d",
-    kRoot, kRoot},
-  { "atmel_tp_deltas", "/opt/google/touch/scripts/atmel_tools.sh tp d",
-    kRoot, kRoot},
-  {
-    "trim",
-    "cat /var/lib/trim/stateful_trim_state /var/lib/trim/stateful_trim_data"
-  },
-  { "ui_log", "/usr/share/userfeedback/scripts/get_log /var/log/ui/ui.LATEST" },
-  { "uname", "/bin/uname -a" },
-  { "update_engine.log", "cat $(ls -1tr /var/log/update_engine | tail -5 | sed"
-                         " s.^./var/log/update_engine/.)" },
-  { "uptime", "/usr/bin/cut -d' ' -f1 /proc/uptime" },
-  { "verified boot", "cat /var/log/debug_vboot_noisy.log" },
-  { "vmlog.1.LATEST", "cat /var/log/vmlog/vmlog.1.LATEST" },
-  { "vmlog.1.PREVIOUS", "cat /var/log/vmlog/vmlog.1.PREVIOUS" },
-  { "vmlog.LATEST", "cat /var/log/vmlog/vmlog.LATEST" },
-  { "vmlog.PREVIOUS", "cat /var/log/vmlog/vmlog.PREVIOUS" },
-  { "vmstat", "cat /proc/vmstat" },
-  { "vpd_2.0", "cat /var/log/vpd_2.0.txt" },
-  { "wifi_status", "/usr/bin/network_diag --wifi-internal --no-log" },
-  { "zram compressed data size",
-    "cat /sys/block/zram0/compr_data_size 2>/dev/null" },
-  { "zram original data size",
-    "cat /sys/block/zram0/orig_data_size 2>/dev/null" },
-  { "zram total memory used",
-    "cat /sys/block/zram0/mem_used_total 2>/dev/null" },
-  { "zram total reads",
-    "cat /sys/block/zram0/num_reads 2>/dev/null" },
-  { "zram total writes",
-    "cat /sys/block/zram0/num_writes 2>/dev/null" },
-  { "zram new stats names",
-    "echo orig_size compr_size used_total limit "
-    "used_max zero_pages migrated" },
-  { "zram new stats values", "cat /sys/block/zram0/mm_stat 2>/dev/null" },
-  { "cros_tp version", "cat /sys/class/chromeos/cros_tp/version" },
-  { "cros_tp console", "/usr/sbin/ectool --name=cros_tp console",
-    kRoot, kRoot },
-  { "cros_tp frame", "/usr/sbin/ectool --name=cros_tp tpframeget",
-    kRoot, kRoot },
-  { "crostini", "/usr/bin/cicerone_client --get_info" },
-
+  Log(kCommand, "tpm_version", "/usr/sbin/tpm-manager get_version_info", kRoot,
+      kRoot),
+  Log(kCommand, "atmel_ts_refs",
+      "/opt/google/touch/scripts/atmel_tools.sh ts r", kRoot, kRoot),
+  Log(kCommand, "atmel_tp_refs",
+      "/opt/google/touch/scripts/atmel_tools.sh tp r", kRoot, kRoot),
+  Log(kCommand, "atmel_ts_deltas",
+      "/opt/google/touch/scripts/atmel_tools.sh ts d", kRoot, kRoot),
+  Log(kCommand, "atmel_tp_deltas",
+      "/opt/google/touch/scripts/atmel_tools.sh tp d", kRoot, kRoot),
+  Log(kFile, "stateful_trim_state", "/var/lib/trim/stateful_trim_state"),
+  Log(kFile, "stateful_trim_data", "/var/lib/trim/stateful_trim_data"),
+  Log(kCommand, "ui_log",
+      "/usr/share/userfeedback/scripts/get_log /var/log/ui/ui.LATEST"),
+  Log(kCommand, "uname", "/bin/uname -a"),
+  Log(kCommand, "update_engine.log",
+      "cat $(ls -1tr /var/log/update_engine | tail -5 | sed"
+      " s.^./var/log/update_engine/.)"),
+  Log(kCommand, "uptime", "/usr/bin/cut -d' ' -f1 /proc/uptime"),
+  Log(kFile, "verified boot", "/var/log/debug_vboot_noisy.log"),
+  Log(kFile, "vmlog.1.LATEST", "/var/log/vmlog/vmlog.1.LATEST"),
+  Log(kFile, "vmlog.1.PREVIOUS", "/var/log/vmlog/vmlog.1.PREVIOUS"),
+  Log(kFile, "vmlog.LATEST", "/var/log/vmlog/vmlog.LATEST"),
+  Log(kFile, "vmlog.PREVIOUS", "/var/log/vmlog/vmlog.PREVIOUS"),
+  Log(kFile, "vmstat", "/proc/vmstat"),
+  Log(kFile, "vpd_2.0", "/var/log/vpd_2.0.txt"),
+  Log(kCommand, "wifi_status",
+      "/usr/bin/network_diag --wifi-internal --no-log"),
+  Log(kFile, "zram compressed data size", "/sys/block/zram0/compr_data_size"),
+  Log(kFile, "zram original data size", "/sys/block/zram0/orig_data_size"),
+  Log(kFile, "zram total memory used", "/sys/block/zram0/mem_used_total"),
+  Log(kFile, "zram total reads", "/sys/block/zram0/num_reads"),
+  Log(kFile, "zram total writes", "/sys/block/zram0/num_writes"),
+  Log(kCommand, "zram new stats names",
+      "echo orig_size compr_size used_total limit used_max zero_pages "
+      "migrated"),
+  Log(kFile, "zram new stats values", "/sys/block/zram0/mm_stat"),
+  Log(kFile, "cros_tp version", "/sys/class/chromeos/cros_tp/version"),
+  Log(kCommand, "cros_tp console", "/usr/sbin/ectool --name=cros_tp console",
+      kRoot, kRoot),
+  Log(kCommand, "cros_tp frame", "/usr/sbin/ectool --name=cros_tp tpframeget",
+      kRoot, kRoot),
+  Log(kCommand, "crostini", "/usr/bin/cicerone_client --get_info"),
   // Stuff pulled out of the original list. These need access to the running X
   // session, which we'd rather not give to debugd, or return info specific to
   // the current session (in the setsid(2) sense), which is not useful for
   // debugd
-  // { "env", "set" },
-  // { "setxkbmap", "/usr/bin/setxkbmap -print -query" },
-  // { "xrandr", "/usr/bin/xrandr --verbose" }
-  { nullptr, nullptr }
+  // Log(kCommand, "env", "set"),
+  // Log(kCommand, "setxkbmap", "/usr/bin/setxkbmap -print -query"),
+  // Log(kCommand, "xrandr", "/usr/bin/xrandr --verbose)
 };
 
 // netstat and logcat should appear in chrome://system but not in feedback
@@ -347,78 +308,52 @@
 // already incorporated via arc-bugreport.
 const std::vector<string> kCommandLogsExclude = {"netstat", "logcat"};
 
-const Log kExtraLogs[] = {
+const std::vector<Log> kExtraLogs {
 #if USE_CELLULAR
-  { "mm-status", "/usr/bin/modem status" },
+  Log(kCommand, "mm-status", "/usr/bin/modem status"),
 #endif  // USE_CELLULAR
-  { "network-devices", "/usr/bin/connectivity show devices" },
-  { "network-services", "/usr/bin/connectivity show services" },
-  { nullptr, nullptr }
+  Log(kCommand, "network-devices", "/usr/bin/connectivity show devices"),
+  Log(kCommand, "network-services", "/usr/bin/connectivity show services"),
 };
 
-const Log kFeedbackLogs[] = {
+const std::vector<Log> kFeedbackLogs {
 #if USE_CELLULAR
-  { "mm-status", "/usr/bin/modem status-feedback" },
+  Log(kCommand, "mm-status", "/usr/bin/modem status-feedback"),
 #endif  // USE_CELLULAR
-  { "network-devices", "/usr/bin/connectivity show-feedback devices" },
-  { "network-services", "/usr/bin/connectivity show-feedback services" },
-  { nullptr, nullptr }
+  Log(kCommand, "network-devices",
+      "/usr/bin/connectivity show-feedback devices"),
+  Log(kCommand, "network-services",
+          "/usr/bin/connectivity show-feedback services"),
 };
 
 // List of log files needed to be part of the feedback report that are huge and
 // must be sent back to the client via the file descriptor using
 // LogTool::GetBigFeedbackLogs().
-const Log kBigFeedbackLogs[] = {
-  { "arc-bugreport",
-    "cat /run/arc/bugreport/pipe 2>/dev/null",
-    // ARC bugreport permissions are weird. Since we're just running cat, this
-    // shouldn't cause any issues.
-    kRoot,
-    kRoot,
-    "10M",
-    LogTool::Encoding::kUtf8,
-  },
-  { nullptr, nullptr }
+const std::vector<Log> kBigFeedbackLogs{
+  // ARC bugreport permissions are weird. Since we're just reading a file,
+  // this shouldn't cause any issues.
+  Log(kFile, "arc-bugreport", "/run/arc/bugreport/pipe", kRoot, kRoot,
+      10 * 1024 * 1024, LogTool::Encoding::kUtf8),
 };
 
 // List of log files that must directly be collected by Chrome. This is because
 // debugd is running under a VFS namespace and does not have access to later
 // cryptohome mounts.
-const Log kUserLogs[] = {
-  {"chrome_user_log", "log/chrome"},
-  {"libassistant_user_log", "log/libassistant.log"},
-  {"login-times", "login-times"},
-  {"logout-times", "logout-times"},
-  { nullptr, nullptr}
+using UserLog = std::pair<std::string, std::string>;
+const std::vector<UserLog> kUserLogs = {
+  UserLog("chrome_user_log", "log/chrome"),
+  UserLog("libassistant_user_log", "log/libassistant.log"),
+  UserLog("login-times", "login-times"),
+  UserLog("logout-times", "logout-times"),
 };
 
-// TODO(ellyjones): sandbox. crosbug.com/35122
-string Run(const Log& log) {
-  string output;
-  ProcessWithOutput p;
-  string tailed_cmdline =
-      base::StringPrintf("%s | tail -c %s", log.command, log.size_cap);
-  if (log.user && log.group)
-    p.SandboxAs(log.user, log.group);
-  if (!p.Init())
-    return "<not available>";
-  p.AddArg(kShell);
-  p.AddStringOption("-c", tailed_cmdline);
-  if (p.Run())
-    return "<not available>";
-  p.GetOutput(&output);
-  if (!output.size())
-    return "<empty>";
-  return LogTool::EnsureUTF8String(output, log.encoding);
-}
-
 // Fills |dictionary| with the anonymized contents of the logs in |logs|.
-void GetLogsInDictionary(const struct Log* logs,
+void GetLogsInDictionary(const std::vector<Log>& logs,
                          AnonymizerTool* anonymizer,
                          base::DictionaryValue* dictionary) {
-  for (size_t i = 0; logs[i].name; ++i) {
+  for (const Log& log : logs) {
     dictionary->SetStringWithoutPathExpansion(
-        logs[i].name, anonymizer->Anonymize(Run(logs[i])));
+        log.GetName(), anonymizer->Anonymize(log.GetLogData()));
   }
 }
 
@@ -433,11 +368,12 @@
   base::WriteFileDescriptor(fd.get(), logs_json.c_str(), logs_json.size());
 }
 
-bool GetNamedLogFrom(const string& name, const struct Log* logs,
+bool GetNamedLogFrom(const string& name,
+                     const std::vector<Log>& logs,
                      string* result) {
-  for (size_t i = 0; logs[i].name; i++) {
-    if (name == logs[i].name) {
-      *result = Run(logs[i]);
+  for (const Log& log : logs) {
+    if (name == log.GetName()) {
+      *result = log.GetLogData();
       return true;
     }
   }
@@ -445,9 +381,9 @@
   return false;
 }
 
-void GetLogsFrom(const struct Log* logs, LogTool::LogMap* map) {
-  for (size_t i = 0; logs[i].name; i++)
-    (*map)[logs[i].name] = Run(logs[i]);
+void GetLogsFrom(const std::vector<Log>& logs, LogTool::LogMap* map) {
+  for (const Log& log : logs)
+    (*map)[log.GetName()] = log.GetLogData();
 }
 
 void GetLsbReleaseInfo(LogTool::LogMap* map) {
@@ -533,6 +469,164 @@
 
 }  // namespace
 
+Log::Log(Log::LogType type,
+         std::string name,
+         std::string data,
+         std::string user,
+         std::string group,
+         int64_t max_bytes,
+         LogTool::Encoding encoding)
+    : type_(type),
+      name_(name),
+      data_(data),
+      user_(user),
+      group_(group),
+      max_bytes_(max_bytes),
+      encoding_(encoding) {}
+
+std::string Log::GetName() const {
+  return name_;
+}
+
+std::string Log::GetLogData() const {
+  // The reason this code uses a switch statement on a type enum rather than
+  // using inheritance/virtual dispatch is so that all of the Log objects can
+  // be constructed statically. Switching to heap allocated subclasses of Log
+  // makes the code that declares all of the log entries much more verbose
+  // and harder to understand.
+  switch (type_) {
+    case kCommand:
+      return GetCommandLogData();
+    case kFile:
+      return GetFileLogData();
+    default:
+      return "<unknown log type>";
+  }
+}
+
+// TODO(ellyjones): sandbox. crosbug.com/35122
+std::string Log::GetCommandLogData() const {
+  if (type_ != kCommand)
+    return "<log type mismatch>";
+  std::string tailed_cmdline =
+      base::StringPrintf("%s | tail -c %" PRId64, data_.c_str(), max_bytes_);
+  ProcessWithOutput p;
+  if (minijail_disabled_for_test_)
+    p.set_use_minijail(false);
+  if (!user_.empty() && !group_.empty())
+    p.SandboxAs(user_, group_);
+  if (!p.Init())
+    return "<not available>";
+  p.AddArg(kShell);
+  p.AddStringOption("-c", tailed_cmdline);
+  if (p.Run())
+    return "<not available>";
+  std::string output;
+  p.GetOutput(&output);
+  if (output.empty())
+    return "<empty>";
+  return LogTool::EnsureUTF8String(output, encoding_);
+}
+
+std::string Log::GetFileLogData() const {
+  if (type_ != kFile)
+    return "<log type mismatch>";
+
+  uid_t old_euid = geteuid();
+  uid_t new_euid = UidForUser(user_);
+  gid_t old_egid = getegid();
+  gid_t new_egid = GidForGroup(group_);
+
+  if (new_euid == -1 || new_egid == -1) {
+    return "<not available>";
+  }
+
+  // Make sure to set group first, since if we set user first we lose root
+  // and therefore the ability to set our effective gid to arbitrary gids.
+  if (setegid(new_egid)) {
+    PLOG(ERROR) << "Failed to set effective group id to " << new_egid;
+    return "<not available>";
+  }
+  if (seteuid(new_euid)) {
+    PLOG(ERROR) << "Failed to set effective user id to " << new_euid;
+    if (setegid(old_egid))
+      PLOG(ERROR) << "Failed to restore effective group id to " << old_egid;
+    return "<not available>";
+  }
+
+  std::string contents;
+  const base::FilePath path(data_);
+  // Handle special files that don't properly report length/allow lseek.
+  if (base::FilePath("/dev").IsParent(path) ||
+      base::FilePath("/proc").IsParent(path) ||
+      base::FilePath("/sys").IsParent(path)) {
+    if (!base::ReadFileToString(path, &contents))
+      contents = "<not available>";
+    if (contents.size() > max_bytes_)
+      contents.erase(0, contents.size() - max_bytes_);
+  } else {
+    base::File file(path, base::File::FLAG_OPEN | base::File::FLAG_READ);
+    if (!file.IsValid()) {
+      contents = "<not available>";
+    } else {
+      int64_t length = file.GetLength();
+      if (length > max_bytes_) {
+        file.Seek(base::File::FROM_END, -max_bytes_);
+        length = max_bytes_;
+      }
+      std::vector<char> buf(length);
+      int read = file.ReadAtCurrentPos(buf.data(), buf.size());
+      if (read < 0) {
+        PLOG(ERROR) << "Could not read from file " << path.value();
+      } else {
+        contents = std::string(buf.begin(), buf.begin() + read);
+      }
+    }
+  }
+
+  if (contents.empty())
+    contents = "<empty>";
+
+  // Make sure we restore our old euid/egid before returning.
+  if (seteuid(old_euid))
+    PLOG(ERROR) << "Failed to restore effective user id to " << old_euid;
+
+  if (setegid(old_egid))
+    PLOG(ERROR) << "Failed to restore effective group id to " << old_egid;
+
+  return LogTool::EnsureUTF8String(contents, encoding_);
+}
+
+void Log::DisableMinijailForTest() {
+  minijail_disabled_for_test_ = true;
+}
+
+// static
+uid_t Log::UidForUser(const std::string& user) {
+  struct passwd entry;
+  struct passwd* result;
+  std::vector<char> buf(1024);
+  getpwnam_r(user.c_str(), &entry, &buf[0], buf.size(), &result);
+  if (!result) {
+    LOG(ERROR) << "User not found: " << user;
+    return -1;
+  }
+  return entry.pw_uid;
+}
+
+// static
+gid_t Log::GidForGroup(const std::string& group) {
+  struct group entry;
+  struct group* result;
+  std::vector<char> buf(1024);
+  getgrnam_r(group.c_str(), &entry, &buf[0], buf.size(), &result);
+  if (!result) {
+    LOG(ERROR) << "Group not found: " << group;
+    return -1;
+  }
+  return entry.gr_gid;
+}
+
 void LogTool::CreateConnectivityReport(bool wait_for_results) {
   // Perform ConnectivityTrial to report connection state in feedback log.
   auto shill = std::make_unique<org::chromium::flimflam::ManagerProxy>(bus_);
@@ -606,8 +700,8 @@
 
 LogTool::LogMap LogTool::GetUserLogFiles() {
   LogMap result;
-  for (size_t i = 0; kUserLogs[i].name; ++i)
-    result[kUserLogs[i].name] = kUserLogs[i].command;
+  for (const UserLog& ul : kUserLogs)
+    result[ul.first] = ul.second;
   return result;
 }
 
diff --git a/debugd/src/log_tool.h b/debugd/src/log_tool.h
index 8e686ab..2c17f52 100644
--- a/debugd/src/log_tool.h
+++ b/debugd/src/log_tool.h
@@ -5,6 +5,7 @@
 #ifndef DEBUGD_SRC_LOG_TOOL_H_
 #define DEBUGD_SRC_LOG_TOOL_H_
 
+#include <sys/types.h>
 #include <map>
 #include <string>
 
@@ -14,6 +15,7 @@
 #include <dbus/bus.h>
 
 #include "debugd/src/anonymizer_tool.h"
+#include "debugd/src/sandboxed_process.h"
 
 namespace debugd {
 
@@ -33,6 +35,45 @@
     kBinary
   };
 
+  class Log {
+   public:
+    enum LogType { kCommand, kFile };
+
+    static constexpr int64_t kDefaultMaxBytes = 512 * 1024;
+
+    Log(LogType type,
+        std::string name,
+        std::string data,
+        std::string user = SandboxedProcess::kDefaultUser,
+        std::string group = SandboxedProcess::kDefaultGroup,
+        int64_t max_bytes = kDefaultMaxBytes,
+        LogTool::Encoding encoding = LogTool::Encoding::kAutodetect);
+
+    std::string GetName() const;
+    std::string GetLogData() const;
+
+    std::string GetCommandLogData() const;
+    std::string GetFileLogData() const;
+
+    void DisableMinijailForTest();
+
+   private:
+    static uid_t UidForUser(const std::string& name);
+    static gid_t GidForGroup(const std::string& group);
+
+    LogType type_;
+    std::string name_;
+    // For kCommand logs, this is the command to run.
+    // For kFile logs, this is the file path to read.
+    std::string data_;
+    std::string user_;
+    std::string group_;
+    int64_t max_bytes_;  // passed as arg to 'tail -c'
+    LogTool::Encoding encoding_;
+
+    bool minijail_disabled_for_test_ = false;
+  };
+
   explicit LogTool(scoped_refptr<dbus::Bus> bus) : bus_(bus) {}
   ~LogTool() = default;
 
diff --git a/debugd/src/log_tool_test.cc b/debugd/src/log_tool_test.cc
index 17184c3..1f50d49 100644
--- a/debugd/src/log_tool_test.cc
+++ b/debugd/src/log_tool_test.cc
@@ -2,17 +2,34 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <grp.h>
+#include <pwd.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <string>
+#include <vector>
+
+#include <base/files/file_path.h>
+#include <base/files/file_util.h>
+#include <base/files/scoped_temp_dir.h>
 #include <dbus/mock_bus.h>
 #include <gtest/gtest.h>
 
 #include "debugd/src/log_tool.h"
 
+namespace {
+bool WriteFile(const base::FilePath& path, const std::string& contents) {
+  return base::CreateDirectory(path.DirName()) &&
+         base::WriteFile(path, contents.c_str(), contents.length()) ==
+             contents.length();
+}
+}  // namespace
+
 namespace debugd {
 
 class LogToolTest : public testing::Test {
  protected:
-  LogToolTest()
-      : log_tool_(new dbus::MockBus(dbus::Bus::Options())) {}
+  LogToolTest() : log_tool_(new dbus::MockBus(dbus::Bus::Options())) {}
 
   void AnonymizeLogMap(LogTool::LogMap* log_map) {
     log_tool_.AnonymizeLogMap(log_map);
@@ -68,4 +85,68 @@
                                       LogTool::Encoding::kBinary));
 }
 
+class LogTest : public testing::Test {
+ protected:
+  void SetUp() override {
+    std::vector<char> buf(1024);
+
+    uid_t uid = getuid();
+    struct passwd pw_entry;
+    struct passwd* pw_result;
+    ASSERT_EQ(getpwuid_r(uid, &pw_entry, &buf[0], buf.size(), &pw_result), 0);
+    ASSERT_NE(pw_result, nullptr);
+    user_name_ = pw_entry.pw_name;
+
+    gid_t gid = getgid();
+    struct group gr_entry;
+    struct group* gr_result;
+    ASSERT_EQ(getgrgid_r(gid, &gr_entry, &buf[0], buf.size(), &gr_result), 0);
+    ASSERT_NE(gr_result, nullptr);
+    group_name_ = gr_entry.gr_name;
+  }
+
+  std::string user_name_;
+  std::string group_name_;
+};
+
+TEST_F(LogTest, GetFileLogData) {
+  base::ScopedTempDir temp;
+  ASSERT_TRUE(temp.CreateUniqueTempDir());
+
+  base::FilePath file_one = temp.GetPath().Append("test/file_one");
+  ASSERT_TRUE(WriteFile(file_one, "test_one_contents"));
+  const LogTool::Log log_one(LogTool::Log::kFile, "test_log_one",
+                             file_one.value(), user_name_, group_name_);
+  EXPECT_EQ(log_one.GetFileLogData(), "test_one_contents");
+
+  base::FilePath file_two = temp.GetPath().Append("test/file_two");
+  ASSERT_TRUE(WriteFile(file_two, ""));
+  const LogTool::Log log_two(LogTool::Log::kFile, "test_log_two",
+                             file_two.value(), user_name_, group_name_);
+  EXPECT_EQ(log_two.GetFileLogData(), "<empty>");
+
+  base::FilePath file_three = temp.GetPath().Append("test/file_three");
+  ASSERT_TRUE(WriteFile(file_three, "long input value"));
+  const LogTool::Log log_three(LogTool::Log::kFile, "test_log_three",
+                               file_three.value(), user_name_, group_name_, 5);
+  EXPECT_EQ(log_three.GetFileLogData(), "value");
+}
+
+TEST_F(LogTest, GetCommandLogData) {
+  LogTool::Log log_one(LogTool::Log::kCommand, "test_log_one", "printf ''",
+                       user_name_, group_name_);
+  log_one.DisableMinijailForTest();
+  EXPECT_EQ(log_one.GetCommandLogData(), "<empty>");
+
+  LogTool::Log log_two(LogTool::Log::kCommand, "test_log_two",
+                       "printf 'test_output'", user_name_, group_name_);
+  log_two.DisableMinijailForTest();
+  EXPECT_EQ(log_two.GetCommandLogData(), "test_output");
+
+  LogTool::Log log_three(LogTool::Log::kCommand, "test_log_three",
+                         "echo a,b,c | cut -d, -f2", user_name_,
+                         group_name_);
+  log_three.DisableMinijailForTest();
+  EXPECT_EQ(log_three.GetCommandLogData(), "b\n");
+}
 }  // namespace debugd