blob: 39da87c3866cabfc67316c51ef8a8e02ba3c8280 [file] [log] [blame]
Mark Salyzyna1aacb72014-10-15 08:49:39 -07001/*
2 * Copyright (C) 2014 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
17#include <ctype.h>
18#include <errno.h>
19#include <inttypes.h>
20#include <limits.h>
21#include <stdarg.h>
22#include <stdlib.h>
23#include <sys/prctl.h>
24#include <sys/uio.h>
25#include <syslog.h>
26
27#include <log/logger.h>
28
29#include "LogKlog.h"
30
31#define KMSG_PRIORITY(PRI) \
32 '<', \
33 '0' + (LOG_SYSLOG | (PRI)) / 10, \
34 '0' + (LOG_SYSLOG | (PRI)) % 10, \
35 '>'
36
37static const char priority_message[] = { KMSG_PRIORITY(LOG_INFO), '\0' };
38
Mark Salyzyndf5902c2015-05-22 15:23:44 -070039// Parsing is hard
40
41// called if we see a '<', s is the next character, returns pointer after '>'
42static char *is_prio(char *s) {
43 if (!isdigit(*s++)) {
44 return NULL;
45 }
Mark Salyzyncb19b082015-07-13 10:19:34 -070046 static const size_t max_prio_len = 4;
47 size_t len = 0;
Mark Salyzyndf5902c2015-05-22 15:23:44 -070048 char c;
Mark Salyzyncb19b082015-07-13 10:19:34 -070049 while (((c = *s++)) && (++len <= max_prio_len)) {
Mark Salyzynd5600fd2015-06-12 14:59:42 -070050 if (!isdigit(c)) {
51 return (c == '>') ? s : NULL;
Mark Salyzyndf5902c2015-05-22 15:23:44 -070052 }
53 }
54 return NULL;
55}
56
57// called if we see a '[', s is the next character, returns pointer after ']'
58static char *is_timestamp(char *s) {
59 while (*s == ' ') {
60 ++s;
61 }
62 if (!isdigit(*s++)) {
63 return NULL;
64 }
65 bool first_period = true;
66 char c;
67 while ((c = *s++)) {
68 if ((c == '.') && first_period) {
69 first_period = false;
Mark Salyzynd5600fd2015-06-12 14:59:42 -070070 } else if (!isdigit(c)) {
71 return ((c == ']') && !first_period && (*s == ' ')) ? s : NULL;
Mark Salyzyndf5902c2015-05-22 15:23:44 -070072 }
73 }
74 return NULL;
75}
76
77// Like strtok_r with "\r\n" except that we look for log signatures (regex)
Mark Salyzyncb19b082015-07-13 10:19:34 -070078// \(\(<[0-9]\{1,4\}>\)\([[] *[0-9]+[.][0-9]+[]] \)\{0,1\}\|[[] *[0-9]+[.][0-9]+[]] \)
Mark Salyzyndf5902c2015-05-22 15:23:44 -070079// and split if we see a second one without a newline.
80
81#define SIGNATURE_MASK 0xF0
82// <digit> following ('0' to '9' masked with ~SIGNATURE_MASK) added to signature
83#define LESS_THAN_SIG SIGNATURE_MASK
84#define OPEN_BRACKET_SIG ((SIGNATURE_MASK << 1) & SIGNATURE_MASK)
85// space is one more than <digit> of 9
Mark Salyzyn03196c92015-06-08 14:51:30 -070086#define OPEN_BRACKET_SPACE ((char)(OPEN_BRACKET_SIG | 10))
Mark Salyzyndf5902c2015-05-22 15:23:44 -070087
88char *log_strtok_r(char *s, char **last) {
89 if (!s) {
90 if (!(s = *last)) {
91 return NULL;
92 }
93 // fixup for log signature split <,
94 // LESS_THAN_SIG + <digit>
95 if ((*s & SIGNATURE_MASK) == LESS_THAN_SIG) {
96 *s = (*s & ~SIGNATURE_MASK) + '0';
97 *--s = '<';
98 }
99 // fixup for log signature split [,
100 // OPEN_BRACKET_SPACE is space, OPEN_BRACKET_SIG + <digit>
101 if ((*s & SIGNATURE_MASK) == OPEN_BRACKET_SIG) {
102 if (*s == OPEN_BRACKET_SPACE) {
103 *s = ' ';
104 } else {
105 *s = (*s & ~SIGNATURE_MASK) + '0';
106 }
107 *--s = '[';
108 }
109 }
110
111 s += strspn(s, "\r\n");
112
113 if (!*s) { // no non-delimiter characters
114 *last = NULL;
115 return NULL;
116 }
117 char *peek, *tok = s;
118
119 for (;;) {
120 char c = *s++;
121 switch (c) {
122 case '\0':
123 *last = NULL;
124 return tok;
125
126 case '\r':
127 case '\n':
128 s[-1] = '\0';
129 *last = s;
130 return tok;
131
132 case '<':
133 peek = is_prio(s);
134 if (!peek) {
135 break;
136 }
137 if (s != (tok + 1)) { // not first?
138 s[-1] = '\0';
139 *s &= ~SIGNATURE_MASK;
140 *s |= LESS_THAN_SIG; // signature for '<'
141 *last = s;
142 return tok;
143 }
144 s = peek;
145 if ((*s == '[') && ((peek = is_timestamp(s + 1)))) {
146 s = peek;
147 }
148 break;
149
150 case '[':
151 peek = is_timestamp(s);
152 if (!peek) {
153 break;
154 }
155 if (s != (tok + 1)) { // not first?
156 s[-1] = '\0';
157 if (*s == ' ') {
158 *s = OPEN_BRACKET_SPACE;
159 } else {
160 *s &= ~SIGNATURE_MASK;
161 *s |= OPEN_BRACKET_SIG; // signature for '['
162 }
163 *last = s;
164 return tok;
165 }
166 s = peek;
167 break;
168 }
169 }
Mark Salyzyncb19b082015-07-13 10:19:34 -0700170 // NOTREACHED
Mark Salyzyndf5902c2015-05-22 15:23:44 -0700171}
172
Mark Salyzyna1aacb72014-10-15 08:49:39 -0700173log_time LogKlog::correction = log_time(CLOCK_REALTIME) - log_time(CLOCK_MONOTONIC);
174
Mark Salyzyn66091f12015-05-12 15:21:31 -0700175LogKlog::LogKlog(LogBuffer *buf, LogReader *reader, int fdWrite, int fdRead, bool auditd) :
176 SocketListener(fdRead, false),
177 logbuf(buf),
178 reader(reader),
179 signature(CLOCK_MONOTONIC),
180 fdWrite(fdWrite),
181 fdRead(fdRead),
182 initialized(false),
183 enableLogging(true),
184 auditd(auditd) {
Mark Salyzyna1aacb72014-10-15 08:49:39 -0700185 static const char klogd_message[] = "%slogd.klogd: %" PRIu64 "\n";
186 char buffer[sizeof(priority_message) + sizeof(klogd_message) + 20 - 4];
187 snprintf(buffer, sizeof(buffer), klogd_message, priority_message,
188 signature.nsec());
189 write(fdWrite, buffer, strlen(buffer));
190}
191
192bool LogKlog::onDataAvailable(SocketClient *cli) {
193 if (!initialized) {
194 prctl(PR_SET_NAME, "logd.klogd");
195 initialized = true;
196 enableLogging = false;
197 }
198
199 char buffer[LOGGER_ENTRY_MAX_PAYLOAD];
200 size_t len = 0;
201
202 for(;;) {
203 ssize_t retval = 0;
204 if ((sizeof(buffer) - 1 - len) > 0) {
205 retval = read(cli->getSocket(), buffer + len, sizeof(buffer) - 1 - len);
206 }
207 if ((retval == 0) && (len == 0)) {
208 break;
209 }
210 if (retval < 0) {
211 return false;
212 }
213 len += retval;
214 bool full = len == (sizeof(buffer) - 1);
215 char *ep = buffer + len;
216 *ep = '\0';
217 len = 0;
Mark Salyzyndf5902c2015-05-22 15:23:44 -0700218 for(char *ptr = NULL, *tok = buffer;
219 ((tok = log_strtok_r(tok, &ptr)));
Mark Salyzyna1aacb72014-10-15 08:49:39 -0700220 tok = NULL) {
221 if (((tok + strlen(tok)) == ep) && (retval != 0) && full) {
222 len = strlen(tok);
223 memmove(buffer, tok, len);
224 break;
225 }
226 if (*tok) {
227 log(tok);
228 }
229 }
230 }
231
232 return true;
233}
234
235
236void LogKlog::calculateCorrection(const log_time &monotonic,
237 const char *real_string) {
238 log_time real;
239 if (!real.strptime(real_string, "%Y-%m-%d %H:%M:%S.%09q UTC")) {
240 return;
241 }
242 // kernel report UTC, log_time::strptime is localtime from calendar.
243 // Bionic and liblog strptime does not support %z or %Z to pick up
244 // timezone so we are calculating our own correction.
245 time_t now = real.tv_sec;
246 struct tm tm;
247 memset(&tm, 0, sizeof(tm));
248 tm.tm_isdst = -1;
249 localtime_r(&now, &tm);
250 real.tv_sec += tm.tm_gmtoff;
251 correction = real - monotonic;
252}
253
254void LogKlog::sniffTime(log_time &now, const char **buf, bool reverse) {
255 const char *cp;
256 if ((cp = now.strptime(*buf, "[ %s.%q]"))) {
257 static const char suspend[] = "PM: suspend entry ";
258 static const char resume[] = "PM: suspend exit ";
Mark Salyzynbc193c52015-07-23 09:22:50 -0700259 static const char healthd[] = "healthd: battery ";
Mark Salyzyna1aacb72014-10-15 08:49:39 -0700260 static const char suspended[] = "Suspended for ";
261
262 if (isspace(*cp)) {
263 ++cp;
264 }
265 if (!strncmp(cp, suspend, sizeof(suspend) - 1)) {
266 calculateCorrection(now, cp + sizeof(suspend) - 1);
267 } else if (!strncmp(cp, resume, sizeof(resume) - 1)) {
268 calculateCorrection(now, cp + sizeof(resume) - 1);
Mark Salyzynbc193c52015-07-23 09:22:50 -0700269 } else if (!strncmp(cp, healthd, sizeof(healthd) - 1)) {
270 // look for " 2???-??-?? ??:??:??.????????? ???"
271 const char *tp;
272 for (tp = cp + sizeof(healthd) - 1; *tp && (*tp != '\n'); ++tp) {
273 if ((tp[0] == ' ') && (tp[1] == '2') && (tp[5] == '-')) {
274 calculateCorrection(now, tp + 1);
275 break;
276 }
277 }
Mark Salyzyna1aacb72014-10-15 08:49:39 -0700278 } else if (!strncmp(cp, suspended, sizeof(suspended) - 1)) {
279 log_time real;
280 char *endp;
281 real.tv_sec = strtol(cp + sizeof(suspended) - 1, &endp, 10);
282 if (*endp == '.') {
283 real.tv_nsec = strtol(endp + 1, &endp, 10) * 1000000L;
284 if (reverse) {
285 correction -= real;
286 } else {
287 correction += real;
288 }
289 }
290 }
291
292 convertMonotonicToReal(now);
293 *buf = cp;
294 } else {
295 now = log_time(CLOCK_REALTIME);
296 }
297}
298
299// Passed the entire SYSLOG_ACTION_READ_ALL buffer and interpret a
300// compensated start time.
301void LogKlog::synchronize(const char *buf) {
302 const char *cp = strstr(buf, "] PM: suspend e");
303 if (!cp) {
304 return;
305 }
306
307 do {
308 --cp;
309 } while ((cp > buf) && (isdigit(*cp) || isspace(*cp) || (*cp == '.')));
310
311 log_time now;
312 sniffTime(now, &cp, true);
313
314 char *suspended = strstr(buf, "] Suspended for ");
315 if (!suspended || (suspended > cp)) {
316 return;
317 }
318 cp = suspended;
319
320 do {
321 --cp;
322 } while ((cp > buf) && (isdigit(*cp) || isspace(*cp) || (*cp == '.')));
323
324 sniffTime(now, &cp, true);
325}
326
327// kernel log prefix, convert to a kernel log priority number
328static int parseKernelPrio(const char **buf) {
329 int pri = LOG_USER | LOG_INFO;
330 const char *cp = *buf;
331 if (*cp == '<') {
332 pri = 0;
333 while(isdigit(*++cp)) {
334 pri = (pri * 10) + *cp - '0';
335 }
336 if (*cp == '>') {
337 ++cp;
338 } else {
339 cp = *buf;
340 pri = LOG_USER | LOG_INFO;
341 }
342 *buf = cp;
343 }
344 return pri;
345}
346
347// Convert kernel log priority number into an Android Logger priority number
348static int convertKernelPrioToAndroidPrio(int pri) {
349 switch(pri & LOG_PRIMASK) {
350 case LOG_EMERG:
351 // FALLTHRU
352 case LOG_ALERT:
353 // FALLTHRU
354 case LOG_CRIT:
355 return ANDROID_LOG_FATAL;
356
357 case LOG_ERR:
358 return ANDROID_LOG_ERROR;
359
360 case LOG_WARNING:
361 return ANDROID_LOG_WARN;
362
363 default:
364 // FALLTHRU
365 case LOG_NOTICE:
366 // FALLTHRU
367 case LOG_INFO:
368 break;
369
370 case LOG_DEBUG:
371 return ANDROID_LOG_DEBUG;
372 }
373
374 return ANDROID_LOG_INFO;
375}
376
377//
378// log a message into the kernel log buffer
379//
380// Filter rules to parse <PRI> <TIME> <tag> and <message> in order for
381// them to appear correct in the logcat output:
382//
383// LOG_KERN (0):
384// <PRI>[<TIME>] <tag> ":" <message>
385// <PRI>[<TIME>] <tag> <tag> ":" <message>
386// <PRI>[<TIME>] <tag> <tag>_work ":" <message>
387// <PRI>[<TIME>] <tag> '<tag>.<num>' ":" <message>
388// <PRI>[<TIME>] <tag> '<tag><num>' ":" <message>
389// <PRI>[<TIME>] <tag>_host '<tag>.<num>' ":" <message>
390// (unimplemented) <PRI>[<TIME>] <tag> '<num>.<tag>' ":" <message>
391// <PRI>[<TIME>] "[INFO]"<tag> : <message>
392// <PRI>[<TIME>] "------------[ cut here ]------------" (?)
393// <PRI>[<TIME>] "---[ end trace 3225a3070ca3e4ac ]---" (?)
394// LOG_USER, LOG_MAIL, LOG_DAEMON, LOG_AUTH, LOG_SYSLOG, LOG_LPR, LOG_NEWS
395// LOG_UUCP, LOG_CRON, LOG_AUTHPRIV, LOG_FTP:
396// <PRI+TAG>[<TIME>] (see sys/syslog.h)
397// Observe:
398// Minimum tag length = 3 NB: drops things like r5:c00bbadf, but allow PM:
399// Maximum tag words = 2
400// Maximum tag length = 16 NB: we are thinking of how ugly logcat can get.
401// Not a Tag if there is no message content.
402// leading additional spaces means no tag, inherit last tag.
403// Not a Tag if <tag>: is "ERROR:", "WARNING:", "INFO:" or "CPU:"
404// Drop:
405// empty messages
406// messages with ' audit(' in them if auditd is running
407// logd.klogd:
408// return -1 if message logd.klogd: <signature>
409//
410int LogKlog::log(const char *buf) {
411 if (auditd && strstr(buf, " audit(")) {
412 return 0;
413 }
414
415 int pri = parseKernelPrio(&buf);
416
417 log_time now;
418 sniffTime(now, &buf, false);
419
420 // sniff for start marker
421 const char klogd_message[] = "logd.klogd: ";
422 if (!strncmp(buf, klogd_message, sizeof(klogd_message) - 1)) {
423 char *endp;
424 uint64_t sig = strtoll(buf + sizeof(klogd_message) - 1, &endp, 10);
425 if (sig == signature.nsec()) {
426 if (initialized) {
427 enableLogging = true;
428 } else {
429 enableLogging = false;
430 }
431 return -1;
432 }
433 return 0;
434 }
435
436 if (!enableLogging) {
437 return 0;
438 }
439
440 // Parse pid, tid and uid (not possible)
441 const pid_t pid = 0;
442 const pid_t tid = 0;
443 const uid_t uid = 0;
444
445 // Parse (rules at top) to pull out a tag from the incoming kernel message.
446 // Some may view the following as an ugly heuristic, the desire is to
447 // beautify the kernel logs into an Android Logging format; the goal is
448 // admirable but costly.
449 while (isspace(*buf)) {
450 ++buf;
451 }
452 if (!*buf) {
453 return 0;
454 }
455 const char *start = buf;
456 const char *tag = "";
457 const char *etag = tag;
458 if (!isspace(*buf)) {
459 const char *bt, *et, *cp;
460
461 bt = buf;
462 if (!strncmp(buf, "[INFO]", 6)) {
463 // <PRI>[<TIME>] "[INFO]"<tag> ":" message
464 bt = buf + 6;
465 }
466 for(et = bt; *et && (*et != ':') && !isspace(*et); ++et);
467 for(cp = et; isspace(*cp); ++cp);
468 size_t size;
469
470 if (*cp == ':') {
471 // One Word
472 tag = bt;
473 etag = et;
474 buf = cp + 1;
475 } else {
476 size = et - bt;
477 if (strncmp(bt, cp, size)) {
478 // <PRI>[<TIME>] <tag>_host '<tag>.<num>' : message
479 if (!strncmp(bt + size - 5, "_host", 5)
Mark Salyzyncb19b082015-07-13 10:19:34 -0700480 && !strncmp(bt, cp, size - 5)) {
Mark Salyzyna1aacb72014-10-15 08:49:39 -0700481 const char *b = cp;
482 cp += size - 5;
483 if (*cp == '.') {
484 while (!isspace(*++cp) && (*cp != ':'));
485 const char *e;
486 for(e = cp; isspace(*cp); ++cp);
487 if (*cp == ':') {
488 tag = b;
489 etag = e;
490 buf = cp + 1;
491 }
492 }
493 } else {
494 while (!isspace(*++cp) && (*cp != ':'));
495 const char *e;
496 for(e = cp; isspace(*cp); ++cp);
497 // Two words
498 if (*cp == ':') {
499 tag = bt;
500 etag = e;
501 buf = cp + 1;
502 }
503 }
504 } else if (isspace(cp[size])) {
505 const char *b = cp;
506 cp += size;
507 while (isspace(*++cp));
508 // <PRI>[<TIME>] <tag> <tag> : message
509 if (*cp == ':') {
510 tag = bt;
511 etag = et;
512 buf = cp + 1;
513 }
514 } else if (cp[size] == ':') {
515 // <PRI>[<TIME>] <tag> <tag> : message
516 tag = bt;
517 etag = et;
518 buf = cp + size + 1;
519 } else if ((cp[size] == '.') || isdigit(cp[size])) {
520 // <PRI>[<TIME>] <tag> '<tag>.<num>' : message
521 // <PRI>[<TIME>] <tag> '<tag><num>' : message
522 const char *b = cp;
523 cp += size;
524 while (!isspace(*++cp) && (*cp != ':'));
525 const char *e = cp;
526 while (isspace(*cp)) {
527 ++cp;
528 }
529 if (*cp == ':') {
530 tag = b;
531 etag = e;
532 buf = cp + 1;
533 }
534 } else {
535 while (!isspace(*++cp) && (*cp != ':'));
536 const char *e = cp;
537 while (isspace(*cp)) {
538 ++cp;
539 }
540 // Two words
541 if (*cp == ':') {
542 tag = bt;
543 etag = e;
544 buf = cp + 1;
545 }
546 }
547 }
548 size = etag - tag;
549 if ((size <= 1)
Mark Salyzyncb19b082015-07-13 10:19:34 -0700550 // register names like x9
551 || ((size == 2) && (isdigit(tag[0]) || isdigit(tag[1])))
552 // register names like x18 but not driver names like en0
553 || ((size == 3) && (isdigit(tag[1]) && isdigit(tag[2])))
554 // blacklist
555 || ((size == 3) && !strncmp(tag, "CPU", 3))
556 || ((size == 7) && !strncmp(tag, "WARNING", 7))
557 || ((size == 5) && !strncmp(tag, "ERROR", 5))
558 || ((size == 4) && !strncmp(tag, "INFO", 4))) {
Mark Salyzyna1aacb72014-10-15 08:49:39 -0700559 buf = start;
560 etag = tag = "";
561 }
562 }
563 size_t l = etag - tag;
Mark Salyzynd5600fd2015-06-12 14:59:42 -0700564 // skip leading space
Mark Salyzyna1aacb72014-10-15 08:49:39 -0700565 while (isspace(*buf)) {
566 ++buf;
567 }
Mark Salyzynd5600fd2015-06-12 14:59:42 -0700568 // truncate trailing space
569 size_t b = strlen(buf);
570 while (b && isspace(buf[b-1])) {
571 --b;
572 }
573 // trick ... allow tag with empty content to be logged. log() drops empty
574 if (!b && l) {
575 buf = " ";
576 b = 1;
577 }
578 size_t n = 1 + l + 1 + b + 1;
Mark Salyzyna1aacb72014-10-15 08:49:39 -0700579
580 // Allocate a buffer to hold the interpreted log message
581 int rc = n;
582 char *newstr = reinterpret_cast<char *>(malloc(n));
583 if (!newstr) {
584 rc = -ENOMEM;
585 return rc;
586 }
587 char *np = newstr;
588
589 // Convert priority into single-byte Android logger priority
590 *np = convertKernelPrioToAndroidPrio(pri);
591 ++np;
592
593 // Copy parsed tag following priority
594 strncpy(np, tag, l);
595 np += l;
596 *np = '\0';
597 ++np;
598
599 // Copy main message to the remainder
Mark Salyzynd5600fd2015-06-12 14:59:42 -0700600 strncpy(np, buf, b);
601 np[b] = '\0';
Mark Salyzyna1aacb72014-10-15 08:49:39 -0700602
603 // Log message
604 rc = logbuf->log(LOG_ID_KERNEL, now, uid, pid, tid, newstr,
605 (n <= USHRT_MAX) ? (unsigned short) n : USHRT_MAX);
606 free(newstr);
607
608 // notify readers
609 if (!rc) {
610 reader->notifyNewLog();
611 }
612
613 return rc;
614}