Home | History | Annotate | Download | only in crash_reporter
      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