File: warn_collector.l

package info (click to toggle)
android-platform-system-core 1%3A7.0.0%2Br33-1
  • links: PTS, VCS
  • area: main
  • in suites: stretch
  • size: 10,464 kB
  • sloc: cpp: 96,742; ansic: 39,563; asm: 3,482; python: 1,571; sh: 666; lex: 311; java: 169; makefile: 65; xml: 19
file content (335 lines) | stat: -rw-r--r-- 10,269 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
/*
 * Copyright (C) 2013 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * This flex program reads /var/log/messages as it grows and saves kernel
 * warnings to files.  It keeps track of warnings it has seen (based on
 * file/line only, ignoring differences in the stack trace), and reports only
 * the first warning of each kind, but maintains a count of all warnings by
 * using their hashes as buckets in a UMA sparse histogram.  It also invokes
 * the crash collector, which collects the warnings and prepares them for later
 * shipment to the crash server.
 */

%option noyywrap

%{
#include <fcntl.h>
#include <inttypes.h>
#include <pwd.h>
#include <stdarg.h>
#include <sys/inotify.h>
#include <sys/select.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#include "metrics/c_metrics_library.h"

int WarnStart(void);
void WarnEnd(void);
void WarnInput(char *buf, yy_size_t *result, size_t max_size);

#define YY_INPUT(buf, result, max_size) WarnInput(buf, &result, max_size)

%}

/* Define a few useful regular expressions. */

D               [0-9]
PREFIX          .*" kernel: [ "*{D}+"."{D}+"]"
CUT_HERE        {PREFIX}" ------------[ cut here".*
WARNING         {PREFIX}" WARNING: at "
END_TRACE       {PREFIX}" ---[ end trace".*

/* Use exclusive start conditions. */
%x PRE_WARN WARN

%%
 /* The scanner itself. */

^{CUT_HERE}\n{WARNING}          BEGIN(PRE_WARN);
.|\n                            /* ignore all other input in state 0 */
<PRE_WARN>[^ ]+.[^ ]+\n         if (WarnStart()) {
                                  /* yytext is
                                     "file:line func+offset/offset()\n" */
                                  BEGIN(WARN); ECHO;
                                } else {
                                  BEGIN(0);
                                }

 /* Assume the warning ends at the "end trace" line */
<WARN>^{END_TRACE}\n            ECHO; BEGIN(0); WarnEnd();
<WARN>^.*\n                     ECHO;

%%

#define HASH_BITMAP_SIZE        (1 << 15)  /* size in bits */
#define HASH_BITMAP_MASK        (HASH_BITMAP_SIZE - 1)

const char warn_hist_name[] = "Platform.KernelWarningHashes";
uint32_t hash_bitmap[HASH_BITMAP_SIZE / 32];
CMetricsLibrary metrics_library;

const char *prog_name;          /* the name of this program */
int yyin_fd;                    /* instead of FILE *yyin to avoid buffering */
int i_fd;                       /* for inotify, to detect file changes */
int testing;                    /* 1 if running test */
int filter;                     /* 1 when using as filter (for development) */
int fifo;                       /* 1 when reading from fifo (for devel) */
int draining;                   /* 1 when draining renamed log file */

const char *msg_path = "/var/log/messages";
const char warn_dump_dir[]  = "/var/run/kwarn";
const char *warn_dump_path = "/var/run/kwarn/warning";
const char *crash_reporter_command;

__attribute__((__format__(__printf__, 1, 2)))
static void Die(const char *format, ...) {
  va_list ap;
  va_start(ap, format);
  fprintf(stderr, "%s: ", prog_name);
  vfprintf(stderr, format, ap);
  exit(1);
}

static void RunCrashReporter(void) {
  int status = system(crash_reporter_command);
  if (status != 0)
    Die("%s exited with status %d\n", crash_reporter_command, status);
}

static uint32_t StringHash(const char *string) {
  uint32_t hash = 0;
  while (*string != '\0') {
    hash = (hash << 5) + hash + *string++;
  }
  return hash;
}

/* We expect only a handful of different warnings per boot session, so the
 * probability of a collision is very low, and statistically it won't matter
 * (unless warnings with the same hash also happens in tandem, which is even
 * rarer).
 */
static int HashSeen(uint32_t hash) {
  int word_index = (hash & HASH_BITMAP_MASK) / 32;
  int bit_index = (hash & HASH_BITMAP_MASK) % 32;
  return hash_bitmap[word_index] & 1 << bit_index;
}

static void SetHashSeen(uint32_t hash) {
  int word_index = (hash & HASH_BITMAP_MASK) / 32;
  int bit_index = (hash & HASH_BITMAP_MASK) % 32;
  hash_bitmap[word_index] |= 1 << bit_index;
}

#pragma GCC diagnostic ignored "-Wwrite-strings"
int WarnStart(void) {
  uint32_t hash;
  char *spacep;

  if (filter)
    return 1;

  hash = StringHash(yytext);
  if (!(testing || fifo || filter)) {
    CMetricsLibrarySendSparseToUMA(metrics_library, warn_hist_name, (int) hash);
  }
  if (HashSeen(hash))
    return 0;
  SetHashSeen(hash);

  yyout = fopen(warn_dump_path, "w");
  if (yyout == NULL)
    Die("fopen %s failed: %s\n", warn_dump_path, strerror(errno));
  spacep = strchr(yytext, ' ');
  if (spacep == NULL || spacep[1] == '\0')
    spacep = "unknown-function";
  fprintf(yyout, "%08x-%s\n", hash, spacep + 1);
  return 1;
}

void WarnEnd(void) {
  if (filter)
    return;
  fclose(yyout);
  yyout = stdout;               /* for debugging */
  RunCrashReporter();
}

static void WarnOpenInput(const char *path) {
  yyin_fd = open(path, O_RDONLY);
  if (yyin_fd < 0)
    Die("could not open %s: %s\n", path, strerror(errno));
  if (!fifo) {
    /* Go directly to the end of the file.  We don't want to parse the same
     * warnings multiple times on reboot/restart.  We might miss some
     * warnings, but so be it---it's too hard to keep track reliably of the
     * last parsed position in the syslog.
     */
    if (lseek(yyin_fd, 0, SEEK_END) < 0)
      Die("could not lseek %s: %s\n", path, strerror(errno));
    /* Set up notification of file growth and rename. */
    i_fd = inotify_init();
    if (i_fd < 0)
      Die("inotify_init: %s\n", strerror(errno));
    if (inotify_add_watch(i_fd, path, IN_MODIFY | IN_MOVE_SELF) < 0)
      Die("inotify_add_watch: %s\n", strerror(errno));
  }
}

/* We replace the default YY_INPUT() for the following reasons:
 *
 * 1.  We want to read data as soon as it becomes available, but the default
 * YY_INPUT() uses buffered I/O.
 *
 * 2.  We want to block on end of input and wait for the file to grow.
 *
 * 3.  We want to detect log rotation, and reopen the input file as needed.
 */
void WarnInput(char *buf, yy_size_t *result, size_t max_size) {
  while (1) {
    ssize_t ret = read(yyin_fd, buf, max_size);
    if (ret < 0)
      Die("read: %s", strerror(errno));
    *result = ret;
    if (*result > 0 || fifo || filter)
      return;
    if (draining) {
      /* Assume we're done with this log, and move to next
       * log.  Rsyslogd may keep writing to the old log file
       * for a while, but we don't care since we don't have
       * to be exact.
       */
      close(yyin_fd);
      if (YYSTATE == WARN) {
        /* Be conservative in case we lose the warn
         * terminator during the switch---or we may
         * collect personally identifiable information.
         */
        WarnEnd();
      }
      BEGIN(0);        /* see above comment */
      sleep(1);        /* avoid race with log rotator */
      WarnOpenInput(msg_path);
      draining = 0;
      continue;
    }
    /* Nothing left to read, so we must wait. */
    struct inotify_event event;
    while (1) {
      int n = read(i_fd, &event, sizeof(event));
      if (n <= 0) {
        if (errno == EINTR)
          continue;
        else
          Die("inotify: %s\n", strerror(errno));
      } else
        break;
    }
    if (event.mask & IN_MOVE_SELF) {
      /* The file has been renamed.  Before switching
       * to the new one, we process any remaining
       * content of this file.
       */
      draining = 1;
    }
  }
}

int main(int argc, char **argv) {
  int result;
  struct passwd *user;
  prog_name = argv[0];

  if (argc == 2 && strcmp(argv[1], "--test") == 0)
    testing = 1;
  else if (argc == 2 && strcmp(argv[1], "--filter") == 0)
    filter = 1;
  else if (argc == 2 && strcmp(argv[1], "--fifo") == 0) {
    fifo = 1;
  } else if (argc != 1) {
    fprintf(stderr,
            "usage: %s [single-flag]\n"
            "flags (for testing only):\n"
            "--fifo\tinput is fifo \"fifo\", output is stdout\n"
            "--filter\tinput is stdin, output is stdout\n"
            "--test\trun self-test\n",
            prog_name);
    exit(1);
  }

  metrics_library = CMetricsLibraryNew();
  CMetricsLibraryInit(metrics_library);

  crash_reporter_command = testing ?
    "./warn_collector_test_reporter.sh" :
    "/sbin/crash_reporter --kernel_warning";

  /* When filtering with --filter (for development) use stdin for input.
   * Otherwise read input from a file or a fifo.
   */
  yyin_fd = fileno(stdin);
  if (testing) {
    msg_path = "messages";
    warn_dump_path = "warning";
  }
  if (fifo) {
    msg_path = "fifo";
  }
  if (!filter) {
    WarnOpenInput(msg_path);
  }

  /* Create directory for dump file.  Still need to be root here. */
  unlink(warn_dump_path);
  if (!testing && !fifo && !filter) {
    rmdir(warn_dump_dir);
    result = mkdir(warn_dump_dir, 0755);
    if (result < 0)
      Die("could not create %s: %s\n",
          warn_dump_dir, strerror(errno));
  }

  if (0) {
    /* TODO(semenzato): put this back in once we decide it's safe
     * to make /var/spool/crash rwxrwxrwx root, or use a different
     * owner and setuid for the crash reporter as well.
     */

    /* Get low privilege uid, gid. */
    user = getpwnam("chronos");
    if (user == NULL)
      Die("getpwnam failed\n");

    /* Change dump directory ownership. */
    if (chown(warn_dump_dir, user->pw_uid, user->pw_gid) < 0)
      Die("chown: %s\n", strerror(errno));

    /* Drop privileges. */
    if (setuid(user->pw_uid) < 0) {
      Die("setuid: %s\n", strerror(errno));
    }
  }

  /* Go! */
  return yylex();
}

/* Flex should really know not to generate these functions.
 */
void UnusedFunctionWarningSuppressor(void) {
  yyunput(0, 0);
}