Steve Fung | 6c34c25 | 2015-08-20 00:27:30 -0700 | [diff] [blame] | 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. |
Luigi Semenzato | 6fdc0b4 | 2013-04-11 17:22:13 -0700 | [diff] [blame] | 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 | |
Steve Fung | 8bafb3d | 2015-08-07 13:22:46 -0700 | [diff] [blame] | 25 | %option noyywrap |
| 26 | |
Luigi Semenzato | 6fdc0b4 | 2013-04-11 17:22:13 -0700 | [diff] [blame] | 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); |
Mike Frysinger | 9f3bf88 | 2013-11-10 16:33:21 -0500 | [diff] [blame] | 42 | void WarnInput(char *buf, yy_size_t *result, size_t max_size); |
Luigi Semenzato | 6fdc0b4 | 2013-04-11 17:22:13 -0700 | [diff] [blame] | 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 */ |
mukesh agrawal | 32f827f | 2013-07-02 13:00:01 -0700 | [diff] [blame] | 64 | <PRE_WARN>[^ ]+.[^ ]+\n if (WarnStart()) { |
| 65 | /* yytext is |
| 66 | "file:line func+offset/offset()\n" */ |
Luigi Semenzato | 6fdc0b4 | 2013-04-11 17:22:13 -0700 | [diff] [blame] | 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 | |
Mike Frysinger | a569da0 | 2013-09-24 14:15:36 -0400 | [diff] [blame] | 98 | __attribute__((__format__(__printf__, 1, 2))) |
Luigi Semenzato | 6fdc0b4 | 2013-04-11 17:22:13 -0700 | [diff] [blame] | 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 | |
| 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 | |
| 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 | */ |
| 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 | |
| 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 | |
Steve Fung | 8bafb3d | 2015-08-07 13:22:46 -0700 | [diff] [blame] | 138 | #pragma GCC diagnostic ignored "-Wwrite-strings" |
Luigi Semenzato | 6fdc0b4 | 2013-04-11 17:22:13 -0700 | [diff] [blame] | 139 | int WarnStart(void) { |
| 140 | uint32_t hash; |
Luigi Semenzato | a59b3df | 2013-12-16 13:59:03 -0800 | [diff] [blame] | 141 | char *spacep; |
Luigi Semenzato | 6fdc0b4 | 2013-04-11 17:22:13 -0700 | [diff] [blame] | 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)); |
Steve Fung | 8bafb3d | 2015-08-07 13:22:46 -0700 | [diff] [blame] | 157 | spacep = strchr(yytext, ' '); |
Luigi Semenzato | a59b3df | 2013-12-16 13:59:03 -0800 | [diff] [blame] | 158 | if (spacep == NULL || spacep[1] == '\0') |
| 159 | spacep = "unknown-function"; |
| 160 | fprintf(yyout, "%08x-%s\n", hash, spacep + 1); |
Luigi Semenzato | 6fdc0b4 | 2013-04-11 17:22:13 -0700 | [diff] [blame] | 161 | return 1; |
| 162 | } |
| 163 | |
| 164 | void WarnEnd(void) { |
| 165 | if (filter) |
| 166 | return; |
| 167 | fclose(yyout); |
| 168 | yyout = stdout; /* for debugging */ |
| 169 | RunCrashReporter(); |
| 170 | } |
| 171 | |
| 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) { |
Luigi Semenzato | 325a2e3 | 2014-02-14 10:41:18 -0800 | [diff] [blame] | 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)); |
Luigi Semenzato | 6fdc0b4 | 2013-04-11 17:22:13 -0700 | [diff] [blame] | 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 | */ |
Mike Frysinger | 9f3bf88 | 2013-11-10 16:33:21 -0500 | [diff] [blame] | 202 | void WarnInput(char *buf, yy_size_t *result, size_t max_size) { |
Luigi Semenzato | 6fdc0b4 | 2013-04-11 17:22:13 -0700 | [diff] [blame] | 203 | while (1) { |
Mike Frysinger | 9f3bf88 | 2013-11-10 16:33:21 -0500 | [diff] [blame] | 204 | ssize_t ret = read(yyin_fd, buf, max_size); |
| 205 | if (ret < 0) |
Luigi Semenzato | 6fdc0b4 | 2013-04-11 17:22:13 -0700 | [diff] [blame] | 206 | Die("read: %s", strerror(errno)); |
Mike Frysinger | 9f3bf88 | 2013-11-10 16:33:21 -0500 | [diff] [blame] | 207 | *result = ret; |
Luigi Semenzato | 6fdc0b4 | 2013-04-11 17:22:13 -0700 | [diff] [blame] | 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; |
Mike Frysinger | 77bd156 | 2013-05-22 17:47:49 -0400 | [diff] [blame] | 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 | } |
Luigi Semenzato | 6fdc0b4 | 2013-04-11 17:22:13 -0700 | [diff] [blame] | 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 | |
| 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 | */ |
| 333 | void UnusedFunctionWarningSuppressor(void) { |
| 334 | yyunput(0, 0); |
Luigi Semenzato | 6fdc0b4 | 2013-04-11 17:22:13 -0700 | [diff] [blame] | 335 | } |