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