blob: 691ef9913410f944beb0ddaed48b6c832ed462bb [file] [log] [blame]
Luigi Semenzato6fdc0b42013-04-11 17:22:13 -07001/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
2 * Use of this source code is governed by a BSD-style license that can be
3 * found in the LICENSE file.
4 *
5 * This flex program reads /var/log/messages as it grows and saves kernel
6 * warnings to files. It keeps track of warnings it has seen (based on
7 * file/line only, ignoring differences in the stack trace), and reports only
8 * the first warning of each kind, but maintains a count of all warnings by
9 * using their hashes as buckets in a UMA sparse histogram. It also invokes
10 * the crash collector, which collects the warnings and prepares them for later
11 * shipment to the crash server.
12 */
13
14%{
15#include <fcntl.h>
16#include <inttypes.h>
17#include <pwd.h>
18#include <stdarg.h>
19#include <sys/inotify.h>
20#include <sys/select.h>
21#include <sys/stat.h>
22#include <sys/types.h>
23#include <unistd.h>
24
25#include "metrics/c_metrics_library.h"
26
27int WarnStart(void);
28void WarnEnd(void);
Mike Frysinger9f3bf882013-11-10 16:33:21 -050029void WarnInput(char *buf, yy_size_t *result, size_t max_size);
Luigi Semenzato6fdc0b42013-04-11 17:22:13 -070030
31#define YY_INPUT(buf, result, max_size) WarnInput(buf, &result, max_size)
32
33%}
34
35/* Define a few useful regular expressions. */
36
37D [0-9]
38PREFIX .*" kernel: [ "*{D}+"."{D}+"]"
39CUT_HERE {PREFIX}" ------------[ cut here".*
40WARNING {PREFIX}" WARNING: at "
41END_TRACE {PREFIX}" ---[ end trace".*
42
43/* Use exclusive start conditions. */
44%x PRE_WARN WARN
45
46%%
47 /* The scanner itself. */
48
49^{CUT_HERE}\n{WARNING} BEGIN(PRE_WARN);
50.|\n /* ignore all other input in state 0 */
mukesh agrawal32f827f2013-07-02 13:00:01 -070051<PRE_WARN>[^ ]+.[^ ]+\n if (WarnStart()) {
52 /* yytext is
53 "file:line func+offset/offset()\n" */
Luigi Semenzato6fdc0b42013-04-11 17:22:13 -070054 BEGIN(WARN); ECHO;
55 } else {
56 BEGIN(0);
57 }
58
59 /* Assume the warning ends at the "end trace" line */
60<WARN>^{END_TRACE}\n ECHO; BEGIN(0); WarnEnd();
61<WARN>^.*\n ECHO;
62
63%%
64
65#define HASH_BITMAP_SIZE (1 << 15) /* size in bits */
66#define HASH_BITMAP_MASK (HASH_BITMAP_SIZE - 1)
67
68const char warn_hist_name[] = "Platform.KernelWarningHashes";
69uint32_t hash_bitmap[HASH_BITMAP_SIZE / 32];
70CMetricsLibrary metrics_library;
71
72const char *prog_name; /* the name of this program */
73int yyin_fd; /* instead of FILE *yyin to avoid buffering */
74int i_fd; /* for inotify, to detect file changes */
75int testing; /* 1 if running test */
76int filter; /* 1 when using as filter (for development) */
77int fifo; /* 1 when reading from fifo (for devel) */
78int draining; /* 1 when draining renamed log file */
79
80const char *msg_path = "/var/log/messages";
81const char warn_dump_dir[] = "/var/run/kwarn";
82const char *warn_dump_path = "/var/run/kwarn/warning";
83const char *crash_reporter_command;
84
Mike Frysingera569da02013-09-24 14:15:36 -040085__attribute__((__format__(__printf__, 1, 2)))
Luigi Semenzato6fdc0b42013-04-11 17:22:13 -070086static void Die(const char *format, ...) {
87 va_list ap;
88 va_start(ap, format);
89 fprintf(stderr, "%s: ", prog_name);
90 vfprintf(stderr, format, ap);
91 exit(1);
92}
93
94static void RunCrashReporter(void) {
95 int status = system(crash_reporter_command);
96 if (status != 0)
97 Die("%s exited with status %d\n", crash_reporter_command, status);
98}
99
100static uint32_t StringHash(const char *string) {
101 uint32_t hash = 0;
102 while (*string != '\0') {
103 hash = (hash << 5) + hash + *string++;
104 }
105 return hash;
106}
107
108/* We expect only a handful of different warnings per boot session, so the
109 * probability of a collision is very low, and statistically it won't matter
110 * (unless warnings with the same hash also happens in tandem, which is even
111 * rarer).
112 */
113static int HashSeen(uint32_t hash) {
114 int word_index = (hash & HASH_BITMAP_MASK) / 32;
115 int bit_index = (hash & HASH_BITMAP_MASK) % 32;
116 return hash_bitmap[word_index] & 1 << bit_index;
117}
118
119static void SetHashSeen(uint32_t hash) {
120 int word_index = (hash & HASH_BITMAP_MASK) / 32;
121 int bit_index = (hash & HASH_BITMAP_MASK) % 32;
122 hash_bitmap[word_index] |= 1 << bit_index;
123}
124
125int WarnStart(void) {
126 uint32_t hash;
Luigi Semenzatoa59b3df2013-12-16 13:59:03 -0800127 char *spacep;
Luigi Semenzato6fdc0b42013-04-11 17:22:13 -0700128
129 if (filter)
130 return 1;
131
132 hash = StringHash(yytext);
133 if (!(testing || fifo || filter)) {
134 CMetricsLibrarySendSparseToUMA(metrics_library, warn_hist_name, (int) hash);
135 }
136 if (HashSeen(hash))
137 return 0;
138 SetHashSeen(hash);
139
140 yyout = fopen(warn_dump_path, "w");
141 if (yyout == NULL)
142 Die("fopen %s failed: %s\n", warn_dump_path, strerror(errno));
Luigi Semenzatoa59b3df2013-12-16 13:59:03 -0800143 spacep = index(yytext, ' ');
144 if (spacep == NULL || spacep[1] == '\0')
145 spacep = "unknown-function";
146 fprintf(yyout, "%08x-%s\n", hash, spacep + 1);
Luigi Semenzato6fdc0b42013-04-11 17:22:13 -0700147 return 1;
148}
149
150void WarnEnd(void) {
151 if (filter)
152 return;
153 fclose(yyout);
154 yyout = stdout; /* for debugging */
155 RunCrashReporter();
156}
157
158static void WarnOpenInput(const char *path) {
159 yyin_fd = open(path, O_RDONLY);
160 if (yyin_fd < 0)
161 Die("could not open %s: %s\n", path, strerror(errno));
162 if (!fifo) {
Luigi Semenzato325a2e32014-02-14 10:41:18 -0800163 /* Go directly to the end of the file. We don't want to parse the same
164 * warnings multiple times on reboot/restart. We might miss some
165 * warnings, but so be it---it's too hard to keep track reliably of the
166 * last parsed position in the syslog.
167 */
168 if (lseek(yyin_fd, 0, SEEK_END) < 0)
169 Die("could not lseek %s: %s\n", path, strerror(errno));
Luigi Semenzato6fdc0b42013-04-11 17:22:13 -0700170 /* Set up notification of file growth and rename. */
171 i_fd = inotify_init();
172 if (i_fd < 0)
173 Die("inotify_init: %s\n", strerror(errno));
174 if (inotify_add_watch(i_fd, path, IN_MODIFY | IN_MOVE_SELF) < 0)
175 Die("inotify_add_watch: %s\n", strerror(errno));
176 }
177}
178
179/* We replace the default YY_INPUT() for the following reasons:
180 *
181 * 1. We want to read data as soon as it becomes available, but the default
182 * YY_INPUT() uses buffered I/O.
183 *
184 * 2. We want to block on end of input and wait for the file to grow.
185 *
186 * 3. We want to detect log rotation, and reopen the input file as needed.
187 */
Mike Frysinger9f3bf882013-11-10 16:33:21 -0500188void WarnInput(char *buf, yy_size_t *result, size_t max_size) {
Luigi Semenzato6fdc0b42013-04-11 17:22:13 -0700189 while (1) {
Mike Frysinger9f3bf882013-11-10 16:33:21 -0500190 ssize_t ret = read(yyin_fd, buf, max_size);
191 if (ret < 0)
Luigi Semenzato6fdc0b42013-04-11 17:22:13 -0700192 Die("read: %s", strerror(errno));
Mike Frysinger9f3bf882013-11-10 16:33:21 -0500193 *result = ret;
Luigi Semenzato6fdc0b42013-04-11 17:22:13 -0700194 if (*result > 0 || fifo || filter)
195 return;
196 if (draining) {
197 /* Assume we're done with this log, and move to next
198 * log. Rsyslogd may keep writing to the old log file
199 * for a while, but we don't care since we don't have
200 * to be exact.
201 */
202 close(yyin_fd);
203 if (YYSTATE == WARN) {
204 /* Be conservative in case we lose the warn
205 * terminator during the switch---or we may
206 * collect personally identifiable information.
207 */
208 WarnEnd();
209 }
210 BEGIN(0); /* see above comment */
211 sleep(1); /* avoid race with log rotator */
212 WarnOpenInput(msg_path);
213 draining = 0;
214 continue;
215 }
216 /* Nothing left to read, so we must wait. */
217 struct inotify_event event;
Mike Frysinger77bd1562013-05-22 17:47:49 -0400218 while (1) {
219 int n = read(i_fd, &event, sizeof(event));
220 if (n <= 0) {
221 if (errno == EINTR)
222 continue;
223 else
224 Die("inotify: %s\n", strerror(errno));
225 } else
226 break;
227 }
Luigi Semenzato6fdc0b42013-04-11 17:22:13 -0700228 if (event.mask & IN_MOVE_SELF) {
229 /* The file has been renamed. Before switching
230 * to the new one, we process any remaining
231 * content of this file.
232 */
233 draining = 1;
234 }
235 }
236}
237
238int main(int argc, char **argv) {
239 int result;
240 struct passwd *user;
241 prog_name = argv[0];
242
243 if (argc == 2 && strcmp(argv[1], "--test") == 0)
244 testing = 1;
245 else if (argc == 2 && strcmp(argv[1], "--filter") == 0)
246 filter = 1;
247 else if (argc == 2 && strcmp(argv[1], "--fifo") == 0) {
248 fifo = 1;
249 } else if (argc != 1) {
250 fprintf(stderr,
251 "usage: %s [single-flag]\n"
252 "flags (for testing only):\n"
253 "--fifo\tinput is fifo \"fifo\", output is stdout\n"
254 "--filter\tinput is stdin, output is stdout\n"
255 "--test\trun self-test\n",
256 prog_name);
257 exit(1);
258 }
259
260 metrics_library = CMetricsLibraryNew();
261 CMetricsLibraryInit(metrics_library);
262
263 crash_reporter_command = testing ?
264 "./warn_collector_test_reporter.sh" :
265 "/sbin/crash_reporter --kernel_warning";
266
267 /* When filtering with --filter (for development) use stdin for input.
268 * Otherwise read input from a file or a fifo.
269 */
270 yyin_fd = fileno(stdin);
271 if (testing) {
272 msg_path = "messages";
273 warn_dump_path = "warning";
274 }
275 if (fifo) {
276 msg_path = "fifo";
277 }
278 if (!filter) {
279 WarnOpenInput(msg_path);
280 }
281
282 /* Create directory for dump file. Still need to be root here. */
283 unlink(warn_dump_path);
284 if (!testing && !fifo && !filter) {
285 rmdir(warn_dump_dir);
286 result = mkdir(warn_dump_dir, 0755);
287 if (result < 0)
288 Die("could not create %s: %s\n",
289 warn_dump_dir, strerror(errno));
290 }
291
292 if (0) {
293 /* TODO(semenzato): put this back in once we decide it's safe
294 * to make /var/spool/crash rwxrwxrwx root, or use a different
295 * owner and setuid for the crash reporter as well.
296 */
297
298 /* Get low privilege uid, gid. */
299 user = getpwnam("chronos");
300 if (user == NULL)
301 Die("getpwnam failed\n");
302
303 /* Change dump directory ownership. */
304 if (chown(warn_dump_dir, user->pw_uid, user->pw_gid) < 0)
305 Die("chown: %s\n", strerror(errno));
306
307 /* Drop privileges. */
308 if (setuid(user->pw_uid) < 0) {
309 Die("setuid: %s\n", strerror(errno));
310 }
311 }
312
313 /* Go! */
314 return yylex();
315}
316
317/* Flex should really know not to generate these functions.
318 */
319void UnusedFunctionWarningSuppressor(void) {
320 yyunput(0, 0);
321 (void) input();
322}