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))) 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 138 #pragma GCC diagnostic ignored "-Wwrite-strings" 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 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) { 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 */ 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 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); 335 } 336