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