1 /*
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.
15 *
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
25 %option noyywrap
26
27 %{
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
40 int WarnStart(void);
41 void WarnEnd(void);
42 void WarnInput(char *buf, yy_size_t *result, size_t max_size);
43
44 #define YY_INPUT(buf, result, max_size) WarnInput(buf, &result, max_size)
45
46 %}
47
48 /* Define a few useful regular expressions. */
49
50 D [0-9]
51 PREFIX .*" kernel: [ "*{D}+"."{D}+"]"
52 CUT_HERE {PREFIX}" ------------[ cut here".*
53 WARNING {PREFIX}" WARNING: at "
54 END_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 */
64 <PRE_WARN>[^ ]+.[^ ]+\n if (WarnStart()) {
65 /* yytext is
66 "file:line func+offset/offset()\n" */
67 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
81 const char warn_hist_name[] = "Platform.KernelWarningHashes";
82 uint32_t hash_bitmap[HASH_BITMAP_SIZE / 32];
83 CMetricsLibrary metrics_library;
84
85 const char *prog_name; /* the name of this program */
86 int yyin_fd; /* instead of FILE *yyin to avoid buffering */
87 int i_fd; /* for inotify, to detect file changes */
88 int testing; /* 1 if running test */
89 int filter; /* 1 when using as filter (for development) */
90 int fifo; /* 1 when reading from fifo (for devel) */
91 int draining; /* 1 when draining renamed log file */
92
93 const char *msg_path = "/var/log/messages";
94 const char warn_dump_dir[] = "/var/run/kwarn";
95 const char *warn_dump_path = "/var/run/kwarn/warning";
96 const char *crash_reporter_command;
97
98 __attribute__((__format__(__printf__, 1, 2)))
Die(const char * format,...)99 static 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
RunCrashReporter(void)107 static 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
StringHash(const char * string)113 static 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 */
HashSeen(uint32_t hash)126 static 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
SetHashSeen(uint32_t hash)132 static 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
138 #pragma GCC diagnostic ignored "-Wwrite-strings"
WarnStart(void)139 int WarnStart(void) {
140 uint32_t hash;
141 char *spacep;
142
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));
157 spacep = strchr(yytext, ' ');
158 if (spacep == NULL || spacep[1] == '\0')
159 spacep = "unknown-function";
160 fprintf(yyout, "%08x-%s\n", hash, spacep + 1);
161 return 1;
162 }
163
WarnEnd(void)164 void WarnEnd(void) {
165 if (filter)
166 return;
167 fclose(yyout);
168 yyout = stdout; /* for debugging */
169 RunCrashReporter();
170 }
171
WarnOpenInput(const char * path)172 static 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) {
177 /* 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));
184 /* 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 */
WarnInput(char * buf,yy_size_t * result,size_t max_size)202 void WarnInput(char *buf, yy_size_t *result, size_t max_size) {
203 while (1) {
204 ssize_t ret = read(yyin_fd, buf, max_size);
205 if (ret < 0)
206 Die("read: %s", strerror(errno));
207 *result = ret;
208 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;
232 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 }
242 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
main(int argc,char ** argv)252 int 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 */
UnusedFunctionWarningSuppressor(void)333 void UnusedFunctionWarningSuppressor(void) {
334 yyunput(0, 0);
335 }
336