Collect and report kernel warnings.
A flex-based daemon collects kernel warnings by parsing /var/log/messages as lines are added to it. For every warning the daemon sends a sample to a sparse UMA histogram using a hash of the warning as the bucket. Then, if the warning hasn't been seen before, the daemon invokes the crash collector to upload the warning stack trace. BUG=chromium:217382 TEST=manually tested (for now), automated test on its way CQ-DEPEND=Ic8d5773d05d717a275c4a4b5616e0e4c307337b8 CQ-DEPEND=I6a4010acad0ffe20c702bb0fc455e3da7cdf3ac1 Change-Id: I89090e5c2b61ec46b4e740f0895c591728d70e77 Reviewed-on: https://gerrit.chromium.org/gerrit/48277 Commit-Queue: Luigi Semenzato <semenzato@chromium.org> Reviewed-by: Luigi Semenzato <semenzato@chromium.org> Tested-by: Luigi Semenzato <semenzato@chromium.org>
This commit is contained in:
parent
9f90acaa4d
commit
6fdc0b48cb
|
@ -12,6 +12,7 @@ PKG_CONFIG ?= pkg-config
|
|||
CRASH_OBJS = \
|
||||
crash_collector.o \
|
||||
kernel_collector.o \
|
||||
kernel_warning_collector.o \
|
||||
udev_collector.o \
|
||||
unclean_shutdown_collector.o \
|
||||
user_collector.o
|
||||
|
@ -44,6 +45,18 @@ clean: CLEAN(list_proxies)
|
|||
all: CXX_BINARY(list_proxies)
|
||||
|
||||
|
||||
# -- Executable warn_collector --
|
||||
|
||||
CC_BINARY(warn_collector): LDLIBS += -lfl -lmetrics
|
||||
CC_BINARY(warn_collector): lex.yy.o
|
||||
clean: CLEAN(warn_collector)
|
||||
all: CC_BINARY(warn_collector)
|
||||
lex.yy.c: $(SRC)/warn_collector.l
|
||||
flex $<
|
||||
clean: CLEAN(lex.yy.o)
|
||||
$(eval $(call add_object_rules,lex.yy.o,CC,c))
|
||||
|
||||
|
||||
# -- Unit Tests --
|
||||
|
||||
# Uncomment these to just run specific test(s).
|
||||
|
@ -65,8 +78,10 @@ clean: CLEAN($(1))
|
|||
tests: TEST(CXX_BINARY($(1)))
|
||||
endef
|
||||
|
||||
CC_BINARY(warn_collector_test): warn_collector_test.o
|
||||
|
||||
# If $(TEST_BINS) is not already set, assume any filename matching *_test.cc
|
||||
# is a test.
|
||||
TEST_BINS ?= $(patsubst $(SRC)/%.cc,%,$(wildcard $(SRC)/*_test.cc))
|
||||
$(foreach prog,$(TEST_BINS),$(eval $(call TEST_RULES_template,$(prog))))
|
||||
|
||||
tests: TEST(CC_BINARY(warn_collector_test))
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
Apr 31 25:25:25 localhost kernel: [117959.226729] [<ffffffff810e16bf>] do_vfs_ioctl+0x469/0x4b3
|
||||
Apr 31 25:25:25 localhost kernel: [117959.226738] [<ffffffff810d3117>] ? fsnotify_access+0x58/0x60
|
||||
Apr 31 25:25:25 localhost kernel: [117959.226747] [<ffffffff810d3791>] ? vfs_read+0xad/0xd7
|
||||
Apr 31 25:25:25 localhost kernel: [117959.226756] [<ffffffff810e175f>] sys_ioctl+0x56/0x7b
|
||||
Apr 31 25:25:25 localhost kernel: [117959.226765] [<ffffffff810d37fe>] ? sys_read+0x43/0x73
|
||||
Apr 31 25:25:25 localhost kernel: [117959.226774] [<ffffffff8146b7d2>] system_call_fastpath+0x16/0x1b
|
||||
Apr 31 25:25:25 localhost kernel: [117959.226782] ---[ end trace f16822cad7406cec ]---
|
||||
Apr 31 25:25:25 localhost kernel: [117959.231085] ------------[ cut here ]------------
|
||||
Apr 31 25:25:25 localhost kernel: [117959.231100] WARNING: at /mnt/host/source/src/third_party/kernel/files/drivers/gpu/drm/i915/intel_dp.c:351 intel_dp_check_edp+0x6b/0xb9()
|
||||
Apr 31 25:25:25 localhost kernel: [117959.231113] Hardware name: Link
|
||||
Apr 31 25:25:25 localhost kernel: [117959.231117] eDP powered off while attempting aux channel communication.
|
||||
Apr 31 25:25:25 localhost kernel: [117959.231240] Pid: 10508, comm: X Tainted: G WC 3.4.0 #1
|
||||
Apr 31 25:25:25 localhost kernel: [117959.231247] Call Trace:
|
||||
Apr 31 25:25:25 localhost kernel: [117959.231393] [<ffffffff810d3117>] ? fsnotify_access+0x58/0x60
|
||||
Apr 31 25:25:25 localhost kernel: [117959.231402] [<ffffffff810d3791>] ? vfs_read+0xad/0xd7
|
||||
Apr 31 25:25:25 localhost kernel: [117959.231411] [<ffffffff810e175f>] sys_ioctl+0x56/0x7b
|
||||
Apr 31 25:25:25 localhost kernel: [117959.231420] [<ffffffff810d37fe>] ? sys_read+0x43/0x73
|
||||
Apr 31 25:25:25 localhost kernel: [117959.231431] [<ffffffff8146b7d2>] system_call_fastpath+0x16/0x1b
|
||||
Apr 31 25:25:25 localhost kernel: [117959.231439] ---[ end trace f16822cad7406ced ]---
|
||||
Apr 31 25:25:25 localhost kernel: [117959.231450] ------------[ cut here ]------------
|
||||
Apr 31 25:25:25 localhost kernel: [117959.231458] BARNING: at /mnt/host/source/src/third_party/kernel/files/drivers/gpu/drm/i915/intel_dp.c:351 intel_dp_check_edp+0x6b/0xb9()
|
||||
Apr 31 25:25:25 localhost kernel: [117959.231458] ("BARNING" above is intentional)
|
||||
Apr 31 25:25:25 localhost kernel: [117959.231471] Hardware name: Link
|
||||
Apr 31 25:25:25 localhost kernel: [117959.231475] eDP powered off while attempting aux channel communication.
|
||||
Apr 31 25:25:25 localhost kernel: [117959.231482] Modules linked in: nls_iso8859_1 nls_cp437 vfat fat rfcomm i2c_dev ath9k_btcoex snd_hda_codec_hdmi snd_hda_codec_ca0132 mac80211 snd_hda_intel ath9k_common_btcoex snd_hda_codec ath9k_hw_btcoex aesni_intel cryptd snd_hwdep ath snd_pcm aes_x86_64 isl29018(C) memconsole snd_timer snd_page_alloc industrialio(C) cfg80211 rtc_cmos nm10_gpio zram(C) zsmalloc(C) lzo_decompress lzo_compress fuse nf_conntrack_ipv6 nf_defrag_ipv6 ip6table_filter ip6_tables xt_mark option usb_wwan cdc_ether usbnet ath3k btusb bluetooth uvcvideo videobuf2_core videodev videobuf2_vmalloc videobuf2_memops joydev
|
||||
Apr 31 25:25:25 localhost kernel: [117959.231588] Pid: 10508, comm: X Tainted: G WC 3.4.0 #1
|
||||
Apr 31 25:25:25 localhost kernel: [117959.231595] Call Trace:
|
||||
Apr 31 25:25:25 localhost kernel: [117959.231601] [<ffffffff8102a931>] warn_slowpath_common+0x83/0x9c
|
||||
Apr 31 25:25:25 localhost kernel: [117959.231610] [<ffffffff8102a9ed>] warn_slowpath_fmt+0x46/0x48
|
||||
Apr 31 25:25:25 localhost kernel: [117959.231620] [<ffffffff812af495>] intel_dp_check_edp+0x6b/0xb9
|
||||
Apr 31 25:25:25 localhost kernel: [117959.231629] [<ffffffff8102a9ed>] ? warn_slowpath_fmt+
|
|
@ -15,6 +15,7 @@
|
|||
#include "base/stringprintf.h"
|
||||
#include "chromeos/syslog_logging.h"
|
||||
#include "crash-reporter/kernel_collector.h"
|
||||
#include "crash-reporter/kernel_warning_collector.h"
|
||||
#include "crash-reporter/udev_collector.h"
|
||||
#include "crash-reporter/unclean_shutdown_collector.h"
|
||||
#include "crash-reporter/user_collector.h"
|
||||
|
@ -30,6 +31,7 @@ DEFINE_bool(crash_test, false, "Crash test");
|
|||
DEFINE_string(user, "", "User crash info (pid:signal:exec_name)");
|
||||
DEFINE_bool(unclean_check, true, "Check for unclean shutdown");
|
||||
DEFINE_string(udev, "", "Udev event description (type:device:subsystem)");
|
||||
DEFINE_bool(kernel_warning, false, "Report collected kernel warning");
|
||||
#pragma GCC diagnostic error "-Wstrict-aliasing"
|
||||
|
||||
static const char kCrashCounterHistogram[] = "Logging.CrashCounter";
|
||||
|
@ -47,6 +49,7 @@ enum CrashKinds {
|
|||
kCrashKindUser = 2,
|
||||
kCrashKindKernel = 3,
|
||||
kCrashKindUdev = 4,
|
||||
kCrashKindKernelWarning = 5,
|
||||
kCrashKindMax
|
||||
};
|
||||
|
||||
|
@ -176,6 +179,17 @@ static int HandleUdevCrash(UdevCollector *udev_collector) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int HandleKernelWarning(KernelWarningCollector
|
||||
*kernel_warning_collector) {
|
||||
// Accumulate logs to help in diagnosing failures during collection.
|
||||
chromeos::LogToString(true);
|
||||
bool handled = kernel_warning_collector->Collect();
|
||||
chromeos::LogToString(false);
|
||||
if (!handled)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Interactive/diagnostics mode for generating kernel crash signatures.
|
||||
static int GenerateKernelSignature(KernelCollector *kernel_collector) {
|
||||
std::string kcrash_contents;
|
||||
|
@ -238,6 +252,9 @@ int main(int argc, char *argv[]) {
|
|||
UdevCollector udev_collector;
|
||||
udev_collector.Initialize(CountUdevCrash, IsFeedbackAllowed);
|
||||
|
||||
KernelWarningCollector kernel_warning_collector;
|
||||
udev_collector.Initialize(CountUdevCrash, IsFeedbackAllowed);
|
||||
|
||||
if (FLAGS_init) {
|
||||
return Initialize(&kernel_collector,
|
||||
&user_collector,
|
||||
|
@ -258,5 +275,9 @@ int main(int argc, char *argv[]) {
|
|||
return HandleUdevCrash(&udev_collector);
|
||||
}
|
||||
|
||||
if (FLAGS_kernel_warning) {
|
||||
return HandleKernelWarning(&kernel_warning_collector);
|
||||
}
|
||||
|
||||
return HandleUserCrash(&user_collector);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
// Copyright (c) 2012 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 "crash-reporter/kernel_warning_collector.h"
|
||||
|
||||
#include "base/file_util.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/string_number_conversions.h"
|
||||
#include "base/string_util.h"
|
||||
#include "base/stringprintf.h"
|
||||
|
||||
namespace {
|
||||
const char kExecName[] = "kernel-warning";
|
||||
const char kKernelWarningSignatureKey[] = "sig";
|
||||
const char kKernelWarningPath[] = "/var/run/kwarn/warning";
|
||||
const pid_t kKernelPid = 0;
|
||||
const uid_t kRootUid = 0;
|
||||
} // namespace
|
||||
|
||||
KernelWarningCollector::KernelWarningCollector() {
|
||||
}
|
||||
|
||||
KernelWarningCollector::~KernelWarningCollector() {
|
||||
}
|
||||
|
||||
bool KernelWarningCollector::LoadKernelWarning(std::string *content,
|
||||
std::string *hash_string) {
|
||||
FilePath kernel_warning_path(kKernelWarningPath);
|
||||
if (!file_util::ReadFileToString(kernel_warning_path, content)) {
|
||||
LOG(ERROR) << "Could not open " << kKernelWarningPath;
|
||||
return false;
|
||||
}
|
||||
/* Verify that the first line contains an 8-digit hex hash. */
|
||||
*hash_string = content->substr(0, 8);
|
||||
std::vector<uint8> output;
|
||||
if (!base::HexStringToBytes(*hash_string, &output)) {
|
||||
LOG(ERROR) << "Bad hash " << *hash_string << " in " << kKernelWarningPath;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool KernelWarningCollector::Collect() {
|
||||
std::string reason = "normal collection";
|
||||
bool feedback = true;
|
||||
if (IsDeveloperImage()) {
|
||||
reason = "always collect from developer builds";
|
||||
feedback = true;
|
||||
} else if (!is_feedback_allowed_function_()) {
|
||||
reason = "no user consent";
|
||||
feedback = false;
|
||||
}
|
||||
|
||||
LOG(INFO) << "Processing kernel warning: " << reason;
|
||||
|
||||
if (!feedback) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string kernel_warning;
|
||||
std::string warning_hash;
|
||||
if (!LoadKernelWarning(&kernel_warning, &warning_hash)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
FilePath root_crash_directory;
|
||||
if (!GetCreatedCrashDirectoryByEuid(kRootUid, &root_crash_directory, NULL)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string dump_basename =
|
||||
FormatDumpBasename(kExecName, time(NULL), kKernelPid);
|
||||
FilePath kernel_crash_path = root_crash_directory.Append(
|
||||
StringPrintf("%s.kcrash", dump_basename.c_str()));
|
||||
|
||||
// We must use WriteNewFile instead of file_util::WriteFile as we
|
||||
// do not want to write with root access to a symlink that an attacker
|
||||
// might have created.
|
||||
if (WriteNewFile(kernel_crash_path,
|
||||
kernel_warning.data(),
|
||||
kernel_warning.length()) !=
|
||||
static_cast<int>(kernel_warning.length())) {
|
||||
LOG(INFO) << "Failed to write kernel warning to "
|
||||
<< kernel_crash_path.value().c_str();
|
||||
return true;
|
||||
}
|
||||
|
||||
AddCrashMetaData(kKernelWarningSignatureKey, warning_hash);
|
||||
WriteCrashMetaData(
|
||||
root_crash_directory.Append(
|
||||
StringPrintf("%s.meta", dump_basename.c_str())),
|
||||
kExecName, kernel_crash_path.value());
|
||||
|
||||
LOG(INFO) << "Stored kernel warning into " << kernel_crash_path.value();
|
||||
return true;
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
// Copyright (c) 2013 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.
|
||||
|
||||
#ifndef _CRASH_REPORTER_KERNEL_WARNING_COLLECTOR_H_
|
||||
#define _CRASH_REPORTER_KERNEL_WARNING_COLLECTOR_H_
|
||||
|
||||
#include <gtest/gtest_prod.h> // for FRIEND_TEST
|
||||
#include <string>
|
||||
|
||||
#include "crash-reporter/crash_collector.h"
|
||||
|
||||
// Kernel warning collector.
|
||||
class KernelWarningCollector : public CrashCollector {
|
||||
public:
|
||||
KernelWarningCollector();
|
||||
|
||||
virtual ~KernelWarningCollector();
|
||||
|
||||
// Collects warning.
|
||||
bool Collect();
|
||||
|
||||
private:
|
||||
friend class KernelWarningCollectorTest;
|
||||
FRIEND_TEST(KernelWarningCollectorTest, CollectOK);
|
||||
|
||||
// Reads the full content of the kernel warn dump and the warning hash.
|
||||
bool LoadKernelWarning(std::string *hash, std::string *content);
|
||||
};
|
||||
|
||||
#endif // _CRASH_REPORTER_KERNEL_WARNING_COLLECTOR_H_
|
|
@ -0,0 +1,300 @@
|
|||
/* Copyright (c) 2013 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.
|
||||
*
|
||||
* This flex program reads /var/log/messages as it grows and saves kernel
|
||||
* warnings to files. It keeps track of warnings it has seen (based on
|
||||
* file/line only, ignoring differences in the stack trace), and reports only
|
||||
* the first warning of each kind, but maintains a count of all warnings by
|
||||
* using their hashes as buckets in a UMA sparse histogram. It also invokes
|
||||
* the crash collector, which collects the warnings and prepares them for later
|
||||
* shipment to the crash server.
|
||||
*/
|
||||
|
||||
%{
|
||||
#include <fcntl.h>
|
||||
#include <inttypes.h>
|
||||
#include <pwd.h>
|
||||
#include <stdarg.h>
|
||||
#include <sys/inotify.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "metrics/c_metrics_library.h"
|
||||
|
||||
int WarnStart(void);
|
||||
void WarnEnd(void);
|
||||
void WarnInput(char *buf, int *result, size_t max_size);
|
||||
|
||||
#define YY_INPUT(buf, result, max_size) WarnInput(buf, &result, max_size)
|
||||
|
||||
%}
|
||||
|
||||
/* Define a few useful regular expressions. */
|
||||
|
||||
D [0-9]
|
||||
PREFIX .*" kernel: [ "*{D}+"."{D}+"]"
|
||||
CUT_HERE {PREFIX}" ------------[ cut here".*
|
||||
WARNING {PREFIX}" WARNING: at "
|
||||
END_TRACE {PREFIX}" ---[ end trace".*
|
||||
|
||||
/* Use exclusive start conditions. */
|
||||
%x PRE_WARN WARN
|
||||
|
||||
%%
|
||||
/* The scanner itself. */
|
||||
|
||||
^{CUT_HERE}\n{WARNING} BEGIN(PRE_WARN);
|
||||
.|\n /* ignore all other input in state 0 */
|
||||
<PRE_WARN>.*\n if (WarnStart()) {
|
||||
BEGIN(WARN); ECHO;
|
||||
} else {
|
||||
BEGIN(0);
|
||||
}
|
||||
|
||||
/* Assume the warning ends at the "end trace" line */
|
||||
<WARN>^{END_TRACE}\n ECHO; BEGIN(0); WarnEnd();
|
||||
<WARN>^.*\n ECHO;
|
||||
|
||||
%%
|
||||
|
||||
#define HASH_BITMAP_SIZE (1 << 15) /* size in bits */
|
||||
#define HASH_BITMAP_MASK (HASH_BITMAP_SIZE - 1)
|
||||
|
||||
const char warn_hist_name[] = "Platform.KernelWarningHashes";
|
||||
uint32_t hash_bitmap[HASH_BITMAP_SIZE / 32];
|
||||
CMetricsLibrary metrics_library;
|
||||
|
||||
const char *prog_name; /* the name of this program */
|
||||
int yyin_fd; /* instead of FILE *yyin to avoid buffering */
|
||||
int i_fd; /* for inotify, to detect file changes */
|
||||
int testing; /* 1 if running test */
|
||||
int filter; /* 1 when using as filter (for development) */
|
||||
int fifo; /* 1 when reading from fifo (for devel) */
|
||||
int draining; /* 1 when draining renamed log file */
|
||||
|
||||
const char *msg_path = "/var/log/messages";
|
||||
const char warn_dump_dir[] = "/var/run/kwarn";
|
||||
const char *warn_dump_path = "/var/run/kwarn/warning";
|
||||
const char *crash_reporter_command;
|
||||
|
||||
static void Die(const char *format, ...) {
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
fprintf(stderr, "%s: ", prog_name);
|
||||
vfprintf(stderr, format, ap);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
static void RunCrashReporter(void) {
|
||||
int status = system(crash_reporter_command);
|
||||
if (status != 0)
|
||||
Die("%s exited with status %d\n", crash_reporter_command, status);
|
||||
}
|
||||
|
||||
static uint32_t StringHash(const char *string) {
|
||||
uint32_t hash = 0;
|
||||
while (*string != '\0') {
|
||||
hash = (hash << 5) + hash + *string++;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
/* We expect only a handful of different warnings per boot session, so the
|
||||
* probability of a collision is very low, and statistically it won't matter
|
||||
* (unless warnings with the same hash also happens in tandem, which is even
|
||||
* rarer).
|
||||
*/
|
||||
static int HashSeen(uint32_t hash) {
|
||||
int word_index = (hash & HASH_BITMAP_MASK) / 32;
|
||||
int bit_index = (hash & HASH_BITMAP_MASK) % 32;
|
||||
return hash_bitmap[word_index] & 1 << bit_index;
|
||||
}
|
||||
|
||||
static void SetHashSeen(uint32_t hash) {
|
||||
int word_index = (hash & HASH_BITMAP_MASK) / 32;
|
||||
int bit_index = (hash & HASH_BITMAP_MASK) % 32;
|
||||
hash_bitmap[word_index] |= 1 << bit_index;
|
||||
}
|
||||
|
||||
int WarnStart(void) {
|
||||
uint32_t hash;
|
||||
|
||||
if (filter)
|
||||
return 1;
|
||||
|
||||
hash = StringHash(yytext);
|
||||
if (!(testing || fifo || filter)) {
|
||||
CMetricsLibrarySendSparseToUMA(metrics_library, warn_hist_name, (int) hash);
|
||||
}
|
||||
if (HashSeen(hash))
|
||||
return 0;
|
||||
SetHashSeen(hash);
|
||||
|
||||
yyout = fopen(warn_dump_path, "w");
|
||||
if (yyout == NULL)
|
||||
Die("fopen %s failed: %s\n", warn_dump_path, strerror(errno));
|
||||
fprintf(yyout, "%08x\n", hash);
|
||||
return 1;
|
||||
}
|
||||
|
||||
void WarnEnd(void) {
|
||||
if (filter)
|
||||
return;
|
||||
fclose(yyout);
|
||||
yyout = stdout; /* for debugging */
|
||||
RunCrashReporter();
|
||||
}
|
||||
|
||||
static void WarnOpenInput(const char *path) {
|
||||
yyin_fd = open(path, O_RDONLY);
|
||||
if (yyin_fd < 0)
|
||||
Die("could not open %s: %s\n", path, strerror(errno));
|
||||
if (!fifo) {
|
||||
/* Set up notification of file growth and rename. */
|
||||
i_fd = inotify_init();
|
||||
if (i_fd < 0)
|
||||
Die("inotify_init: %s\n", strerror(errno));
|
||||
if (inotify_add_watch(i_fd, path, IN_MODIFY | IN_MOVE_SELF) < 0)
|
||||
Die("inotify_add_watch: %s\n", strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
/* We replace the default YY_INPUT() for the following reasons:
|
||||
*
|
||||
* 1. We want to read data as soon as it becomes available, but the default
|
||||
* YY_INPUT() uses buffered I/O.
|
||||
*
|
||||
* 2. We want to block on end of input and wait for the file to grow.
|
||||
*
|
||||
* 3. We want to detect log rotation, and reopen the input file as needed.
|
||||
*/
|
||||
void WarnInput(char *buf, int *result, size_t max_size) {
|
||||
while (1) {
|
||||
*result = read(yyin_fd, buf, max_size);
|
||||
if (*result < 0)
|
||||
Die("read: %s", strerror(errno));
|
||||
if (*result > 0 || fifo || filter)
|
||||
return;
|
||||
if (draining) {
|
||||
/* Assume we're done with this log, and move to next
|
||||
* log. Rsyslogd may keep writing to the old log file
|
||||
* for a while, but we don't care since we don't have
|
||||
* to be exact.
|
||||
*/
|
||||
close(yyin_fd);
|
||||
if (YYSTATE == WARN) {
|
||||
/* Be conservative in case we lose the warn
|
||||
* terminator during the switch---or we may
|
||||
* collect personally identifiable information.
|
||||
*/
|
||||
WarnEnd();
|
||||
}
|
||||
BEGIN(0); /* see above comment */
|
||||
sleep(1); /* avoid race with log rotator */
|
||||
WarnOpenInput(msg_path);
|
||||
draining = 0;
|
||||
continue;
|
||||
}
|
||||
/* Nothing left to read, so we must wait. */
|
||||
struct inotify_event event;
|
||||
int n = read(i_fd, &event, sizeof(event));
|
||||
if (n <= 0)
|
||||
Die("inotify: %s\n", strerror(errno));
|
||||
if (event.mask & IN_MOVE_SELF) {
|
||||
/* The file has been renamed. Before switching
|
||||
* to the new one, we process any remaining
|
||||
* content of this file.
|
||||
*/
|
||||
draining = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
int result;
|
||||
struct passwd *user;
|
||||
prog_name = argv[0];
|
||||
|
||||
if (argc == 2 && strcmp(argv[1], "--test") == 0)
|
||||
testing = 1;
|
||||
else if (argc == 2 && strcmp(argv[1], "--filter") == 0)
|
||||
filter = 1;
|
||||
else if (argc == 2 && strcmp(argv[1], "--fifo") == 0) {
|
||||
fifo = 1;
|
||||
} else if (argc != 1) {
|
||||
fprintf(stderr,
|
||||
"usage: %s [single-flag]\n"
|
||||
"flags (for testing only):\n"
|
||||
"--fifo\tinput is fifo \"fifo\", output is stdout\n"
|
||||
"--filter\tinput is stdin, output is stdout\n"
|
||||
"--test\trun self-test\n",
|
||||
prog_name);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
metrics_library = CMetricsLibraryNew();
|
||||
CMetricsLibraryInit(metrics_library);
|
||||
|
||||
crash_reporter_command = testing ?
|
||||
"./warn_collector_test_reporter.sh" :
|
||||
"/sbin/crash_reporter --kernel_warning";
|
||||
|
||||
/* When filtering with --filter (for development) use stdin for input.
|
||||
* Otherwise read input from a file or a fifo.
|
||||
*/
|
||||
yyin_fd = fileno(stdin);
|
||||
if (testing) {
|
||||
msg_path = "messages";
|
||||
warn_dump_path = "warning";
|
||||
}
|
||||
if (fifo) {
|
||||
msg_path = "fifo";
|
||||
}
|
||||
if (!filter) {
|
||||
WarnOpenInput(msg_path);
|
||||
}
|
||||
|
||||
/* Create directory for dump file. Still need to be root here. */
|
||||
unlink(warn_dump_path);
|
||||
if (!testing && !fifo && !filter) {
|
||||
rmdir(warn_dump_dir);
|
||||
result = mkdir(warn_dump_dir, 0755);
|
||||
if (result < 0)
|
||||
Die("could not create %s: %s\n",
|
||||
warn_dump_dir, strerror(errno));
|
||||
}
|
||||
|
||||
if (0) {
|
||||
/* TODO(semenzato): put this back in once we decide it's safe
|
||||
* to make /var/spool/crash rwxrwxrwx root, or use a different
|
||||
* owner and setuid for the crash reporter as well.
|
||||
*/
|
||||
|
||||
/* Get low privilege uid, gid. */
|
||||
user = getpwnam("chronos");
|
||||
if (user == NULL)
|
||||
Die("getpwnam failed\n");
|
||||
|
||||
/* Change dump directory ownership. */
|
||||
if (chown(warn_dump_dir, user->pw_uid, user->pw_gid) < 0)
|
||||
Die("chown: %s\n", strerror(errno));
|
||||
|
||||
/* Drop privileges. */
|
||||
if (setuid(user->pw_uid) < 0) {
|
||||
Die("setuid: %s\n", strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
/* Go! */
|
||||
return yylex();
|
||||
}
|
||||
|
||||
/* Flex should really know not to generate these functions.
|
||||
*/
|
||||
void UnusedFunctionWarningSuppressor(void) {
|
||||
yyunput(0, 0);
|
||||
(void) input();
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
/* Copyright (c) 2013 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Test driver for the warn_collector daemon.
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
|
||||
int main(int ac, char **av)
|
||||
{
|
||||
return system("$SRC/warn_collector_test.sh");
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
#! /bin/bash
|
||||
# Copyright (c) 2013 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.
|
||||
|
||||
# Test for warn_collector. Run the warn collector in the background, emulate
|
||||
# the kernel by appending lines to the log file "messages", and observe the log
|
||||
# of the (fake) crash reporter each time is run by the warn collector daemon.
|
||||
|
||||
set -e
|
||||
|
||||
SYSROOT=/build/link
|
||||
LD_LIBRARY_PATH=$SYSROOT/lib64:$SYSROOT/usr/lib64
|
||||
PATH=.:$SYSROOT/bin:$SYSROOT/usr/bin:$PATH
|
||||
TESTLOG=warn-test-log
|
||||
|
||||
trap cleanup EXIT
|
||||
|
||||
cleanup() {
|
||||
# Kill daemon (if started) on exit
|
||||
kill %
|
||||
}
|
||||
|
||||
check_log() {
|
||||
n_expected=$1
|
||||
if [ ! -f $TESTLOG ]; then
|
||||
echo $TESTLOG was not created
|
||||
exit 1
|
||||
fi
|
||||
if [ "$(wc -l < $TESTLOG)" -ne $n_expected ]; then
|
||||
echo expected $n_expected lines in $TESTLOG, found this:
|
||||
cat $TESTLOG
|
||||
exit 1
|
||||
fi
|
||||
if egrep -qv '^[0-9a-f]{8}$' $TESTLOG; then
|
||||
echo found bad lines in $TESTLOG:
|
||||
cat $TESTLOG
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
rm -f warn-test-log
|
||||
cp $SRC/warn_collector_test_reporter.sh .
|
||||
cp $SRC/TEST_WARNING .
|
||||
cp TEST_WARNING messages
|
||||
|
||||
# Start the collector daemon. With the --test option, the daemon reads input
|
||||
# from ./messages, writes the warning into ./warning, and invokes
|
||||
# ./warn_collector_test_reporter.sh to report the warning.
|
||||
warn_collector --test &
|
||||
|
||||
# After a while, check that the first warning has been collected.
|
||||
sleep 1
|
||||
check_log 1
|
||||
|
||||
# Add the same warning to messages, verify that it is NOT collected
|
||||
cat TEST_WARNING >> messages
|
||||
sleep 1
|
||||
check_log 1
|
||||
|
||||
# Add a slightly different warning to messages, check that it is collected.
|
||||
sed s/intel_dp.c/intel_xx.c/ < TEST_WARNING >> messages
|
||||
sleep 1
|
||||
check_log 2
|
||||
|
||||
# Emulate log rotation, add a warning, and check.
|
||||
mv messages messages.1
|
||||
sed s/intel_dp.c/intel_xy.c/ < TEST_WARNING > messages
|
||||
sleep 2
|
||||
check_log 3
|
||||
|
||||
# Success!
|
||||
exit 0
|
|
@ -0,0 +1,16 @@
|
|||
#! /bin/sh
|
||||
# Copyright (c) 2013 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.
|
||||
|
||||
# Replacement for the crash reporter, for testing. Log the first line of the
|
||||
# "warning" file, which by convention contains the warning hash, and remove the
|
||||
# file.
|
||||
|
||||
set -e
|
||||
|
||||
exec 1>> warn-test-log
|
||||
exec 2>> warn-test-log
|
||||
|
||||
head -1 warning
|
||||
rm warning
|
Loading…
Reference in New Issue