Mark Salyzyn | a1aacb7 | 2014-10-15 08:49:39 -0700 | [diff] [blame] | 1 | /* |
| 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 | |
| 37 | static const char priority_message[] = { KMSG_PRIORITY(LOG_INFO), '\0' }; |
| 38 | |
| 39 | log_time LogKlog::correction = log_time(CLOCK_REALTIME) - log_time(CLOCK_MONOTONIC); |
| 40 | |
| 41 | LogKlog::LogKlog(LogBuffer *buf, LogReader *reader, int fdWrite, int fdRead, bool auditd) |
| 42 | : SocketListener(fdRead, false) |
| 43 | , logbuf(buf) |
| 44 | , reader(reader) |
| 45 | , signature(CLOCK_MONOTONIC) |
| 46 | , fdWrite(fdWrite) |
| 47 | , fdRead(fdRead) |
| 48 | , initialized(false) |
| 49 | , enableLogging(true) |
| 50 | , auditd(auditd) { |
| 51 | static const char klogd_message[] = "%slogd.klogd: %" PRIu64 "\n"; |
| 52 | char buffer[sizeof(priority_message) + sizeof(klogd_message) + 20 - 4]; |
| 53 | snprintf(buffer, sizeof(buffer), klogd_message, priority_message, |
| 54 | signature.nsec()); |
| 55 | write(fdWrite, buffer, strlen(buffer)); |
| 56 | } |
| 57 | |
| 58 | bool LogKlog::onDataAvailable(SocketClient *cli) { |
| 59 | if (!initialized) { |
| 60 | prctl(PR_SET_NAME, "logd.klogd"); |
| 61 | initialized = true; |
| 62 | enableLogging = false; |
| 63 | } |
| 64 | |
| 65 | char buffer[LOGGER_ENTRY_MAX_PAYLOAD]; |
| 66 | size_t len = 0; |
| 67 | |
| 68 | for(;;) { |
| 69 | ssize_t retval = 0; |
| 70 | if ((sizeof(buffer) - 1 - len) > 0) { |
| 71 | retval = read(cli->getSocket(), buffer + len, sizeof(buffer) - 1 - len); |
| 72 | } |
| 73 | if ((retval == 0) && (len == 0)) { |
| 74 | break; |
| 75 | } |
| 76 | if (retval < 0) { |
| 77 | return false; |
| 78 | } |
| 79 | len += retval; |
| 80 | bool full = len == (sizeof(buffer) - 1); |
| 81 | char *ep = buffer + len; |
| 82 | *ep = '\0'; |
| 83 | len = 0; |
| 84 | for(char *ptr, *tok = buffer; |
| 85 | ((tok = strtok_r(tok, "\r\n", &ptr))); |
| 86 | tok = NULL) { |
| 87 | if (((tok + strlen(tok)) == ep) && (retval != 0) && full) { |
| 88 | len = strlen(tok); |
| 89 | memmove(buffer, tok, len); |
| 90 | break; |
| 91 | } |
| 92 | if (*tok) { |
| 93 | log(tok); |
| 94 | } |
| 95 | } |
| 96 | } |
| 97 | |
| 98 | return true; |
| 99 | } |
| 100 | |
| 101 | |
| 102 | void LogKlog::calculateCorrection(const log_time &monotonic, |
| 103 | const char *real_string) { |
| 104 | log_time real; |
| 105 | if (!real.strptime(real_string, "%Y-%m-%d %H:%M:%S.%09q UTC")) { |
| 106 | return; |
| 107 | } |
| 108 | // kernel report UTC, log_time::strptime is localtime from calendar. |
| 109 | // Bionic and liblog strptime does not support %z or %Z to pick up |
| 110 | // timezone so we are calculating our own correction. |
| 111 | time_t now = real.tv_sec; |
| 112 | struct tm tm; |
| 113 | memset(&tm, 0, sizeof(tm)); |
| 114 | tm.tm_isdst = -1; |
| 115 | localtime_r(&now, &tm); |
| 116 | real.tv_sec += tm.tm_gmtoff; |
| 117 | correction = real - monotonic; |
| 118 | } |
| 119 | |
| 120 | void LogKlog::sniffTime(log_time &now, const char **buf, bool reverse) { |
| 121 | const char *cp; |
| 122 | if ((cp = now.strptime(*buf, "[ %s.%q]"))) { |
| 123 | static const char suspend[] = "PM: suspend entry "; |
| 124 | static const char resume[] = "PM: suspend exit "; |
| 125 | static const char suspended[] = "Suspended for "; |
| 126 | |
| 127 | if (isspace(*cp)) { |
| 128 | ++cp; |
| 129 | } |
| 130 | if (!strncmp(cp, suspend, sizeof(suspend) - 1)) { |
| 131 | calculateCorrection(now, cp + sizeof(suspend) - 1); |
| 132 | } else if (!strncmp(cp, resume, sizeof(resume) - 1)) { |
| 133 | calculateCorrection(now, cp + sizeof(resume) - 1); |
| 134 | } else if (!strncmp(cp, suspended, sizeof(suspended) - 1)) { |
| 135 | log_time real; |
| 136 | char *endp; |
| 137 | real.tv_sec = strtol(cp + sizeof(suspended) - 1, &endp, 10); |
| 138 | if (*endp == '.') { |
| 139 | real.tv_nsec = strtol(endp + 1, &endp, 10) * 1000000L; |
| 140 | if (reverse) { |
| 141 | correction -= real; |
| 142 | } else { |
| 143 | correction += real; |
| 144 | } |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | convertMonotonicToReal(now); |
| 149 | *buf = cp; |
| 150 | } else { |
| 151 | now = log_time(CLOCK_REALTIME); |
| 152 | } |
| 153 | } |
| 154 | |
| 155 | // Passed the entire SYSLOG_ACTION_READ_ALL buffer and interpret a |
| 156 | // compensated start time. |
| 157 | void LogKlog::synchronize(const char *buf) { |
| 158 | const char *cp = strstr(buf, "] PM: suspend e"); |
| 159 | if (!cp) { |
| 160 | return; |
| 161 | } |
| 162 | |
| 163 | do { |
| 164 | --cp; |
| 165 | } while ((cp > buf) && (isdigit(*cp) || isspace(*cp) || (*cp == '.'))); |
| 166 | |
| 167 | log_time now; |
| 168 | sniffTime(now, &cp, true); |
| 169 | |
| 170 | char *suspended = strstr(buf, "] Suspended for "); |
| 171 | if (!suspended || (suspended > cp)) { |
| 172 | return; |
| 173 | } |
| 174 | cp = suspended; |
| 175 | |
| 176 | do { |
| 177 | --cp; |
| 178 | } while ((cp > buf) && (isdigit(*cp) || isspace(*cp) || (*cp == '.'))); |
| 179 | |
| 180 | sniffTime(now, &cp, true); |
| 181 | } |
| 182 | |
| 183 | // kernel log prefix, convert to a kernel log priority number |
| 184 | static int parseKernelPrio(const char **buf) { |
| 185 | int pri = LOG_USER | LOG_INFO; |
| 186 | const char *cp = *buf; |
| 187 | if (*cp == '<') { |
| 188 | pri = 0; |
| 189 | while(isdigit(*++cp)) { |
| 190 | pri = (pri * 10) + *cp - '0'; |
| 191 | } |
| 192 | if (*cp == '>') { |
| 193 | ++cp; |
| 194 | } else { |
| 195 | cp = *buf; |
| 196 | pri = LOG_USER | LOG_INFO; |
| 197 | } |
| 198 | *buf = cp; |
| 199 | } |
| 200 | return pri; |
| 201 | } |
| 202 | |
| 203 | // Convert kernel log priority number into an Android Logger priority number |
| 204 | static int convertKernelPrioToAndroidPrio(int pri) { |
| 205 | switch(pri & LOG_PRIMASK) { |
| 206 | case LOG_EMERG: |
| 207 | // FALLTHRU |
| 208 | case LOG_ALERT: |
| 209 | // FALLTHRU |
| 210 | case LOG_CRIT: |
| 211 | return ANDROID_LOG_FATAL; |
| 212 | |
| 213 | case LOG_ERR: |
| 214 | return ANDROID_LOG_ERROR; |
| 215 | |
| 216 | case LOG_WARNING: |
| 217 | return ANDROID_LOG_WARN; |
| 218 | |
| 219 | default: |
| 220 | // FALLTHRU |
| 221 | case LOG_NOTICE: |
| 222 | // FALLTHRU |
| 223 | case LOG_INFO: |
| 224 | break; |
| 225 | |
| 226 | case LOG_DEBUG: |
| 227 | return ANDROID_LOG_DEBUG; |
| 228 | } |
| 229 | |
| 230 | return ANDROID_LOG_INFO; |
| 231 | } |
| 232 | |
| 233 | // |
| 234 | // log a message into the kernel log buffer |
| 235 | // |
| 236 | // Filter rules to parse <PRI> <TIME> <tag> and <message> in order for |
| 237 | // them to appear correct in the logcat output: |
| 238 | // |
| 239 | // LOG_KERN (0): |
| 240 | // <PRI>[<TIME>] <tag> ":" <message> |
| 241 | // <PRI>[<TIME>] <tag> <tag> ":" <message> |
| 242 | // <PRI>[<TIME>] <tag> <tag>_work ":" <message> |
| 243 | // <PRI>[<TIME>] <tag> '<tag>.<num>' ":" <message> |
| 244 | // <PRI>[<TIME>] <tag> '<tag><num>' ":" <message> |
| 245 | // <PRI>[<TIME>] <tag>_host '<tag>.<num>' ":" <message> |
| 246 | // (unimplemented) <PRI>[<TIME>] <tag> '<num>.<tag>' ":" <message> |
| 247 | // <PRI>[<TIME>] "[INFO]"<tag> : <message> |
| 248 | // <PRI>[<TIME>] "------------[ cut here ]------------" (?) |
| 249 | // <PRI>[<TIME>] "---[ end trace 3225a3070ca3e4ac ]---" (?) |
| 250 | // LOG_USER, LOG_MAIL, LOG_DAEMON, LOG_AUTH, LOG_SYSLOG, LOG_LPR, LOG_NEWS |
| 251 | // LOG_UUCP, LOG_CRON, LOG_AUTHPRIV, LOG_FTP: |
| 252 | // <PRI+TAG>[<TIME>] (see sys/syslog.h) |
| 253 | // Observe: |
| 254 | // Minimum tag length = 3 NB: drops things like r5:c00bbadf, but allow PM: |
| 255 | // Maximum tag words = 2 |
| 256 | // Maximum tag length = 16 NB: we are thinking of how ugly logcat can get. |
| 257 | // Not a Tag if there is no message content. |
| 258 | // leading additional spaces means no tag, inherit last tag. |
| 259 | // Not a Tag if <tag>: is "ERROR:", "WARNING:", "INFO:" or "CPU:" |
| 260 | // Drop: |
| 261 | // empty messages |
| 262 | // messages with ' audit(' in them if auditd is running |
| 263 | // logd.klogd: |
| 264 | // return -1 if message logd.klogd: <signature> |
| 265 | // |
| 266 | int LogKlog::log(const char *buf) { |
| 267 | if (auditd && strstr(buf, " audit(")) { |
| 268 | return 0; |
| 269 | } |
| 270 | |
| 271 | int pri = parseKernelPrio(&buf); |
| 272 | |
| 273 | log_time now; |
| 274 | sniffTime(now, &buf, false); |
| 275 | |
| 276 | // sniff for start marker |
| 277 | const char klogd_message[] = "logd.klogd: "; |
| 278 | if (!strncmp(buf, klogd_message, sizeof(klogd_message) - 1)) { |
| 279 | char *endp; |
| 280 | uint64_t sig = strtoll(buf + sizeof(klogd_message) - 1, &endp, 10); |
| 281 | if (sig == signature.nsec()) { |
| 282 | if (initialized) { |
| 283 | enableLogging = true; |
| 284 | } else { |
| 285 | enableLogging = false; |
| 286 | } |
| 287 | return -1; |
| 288 | } |
| 289 | return 0; |
| 290 | } |
| 291 | |
| 292 | if (!enableLogging) { |
| 293 | return 0; |
| 294 | } |
| 295 | |
| 296 | // Parse pid, tid and uid (not possible) |
| 297 | const pid_t pid = 0; |
| 298 | const pid_t tid = 0; |
| 299 | const uid_t uid = 0; |
| 300 | |
| 301 | // Parse (rules at top) to pull out a tag from the incoming kernel message. |
| 302 | // Some may view the following as an ugly heuristic, the desire is to |
| 303 | // beautify the kernel logs into an Android Logging format; the goal is |
| 304 | // admirable but costly. |
| 305 | while (isspace(*buf)) { |
| 306 | ++buf; |
| 307 | } |
| 308 | if (!*buf) { |
| 309 | return 0; |
| 310 | } |
| 311 | const char *start = buf; |
| 312 | const char *tag = ""; |
| 313 | const char *etag = tag; |
| 314 | if (!isspace(*buf)) { |
| 315 | const char *bt, *et, *cp; |
| 316 | |
| 317 | bt = buf; |
| 318 | if (!strncmp(buf, "[INFO]", 6)) { |
| 319 | // <PRI>[<TIME>] "[INFO]"<tag> ":" message |
| 320 | bt = buf + 6; |
| 321 | } |
| 322 | for(et = bt; *et && (*et != ':') && !isspace(*et); ++et); |
| 323 | for(cp = et; isspace(*cp); ++cp); |
| 324 | size_t size; |
| 325 | |
| 326 | if (*cp == ':') { |
| 327 | // One Word |
| 328 | tag = bt; |
| 329 | etag = et; |
| 330 | buf = cp + 1; |
| 331 | } else { |
| 332 | size = et - bt; |
| 333 | if (strncmp(bt, cp, size)) { |
| 334 | // <PRI>[<TIME>] <tag>_host '<tag>.<num>' : message |
| 335 | if (!strncmp(bt + size - 5, "_host", 5) |
| 336 | && !strncmp(bt, cp, size - 5)) { |
| 337 | const char *b = cp; |
| 338 | cp += size - 5; |
| 339 | if (*cp == '.') { |
| 340 | while (!isspace(*++cp) && (*cp != ':')); |
| 341 | const char *e; |
| 342 | for(e = cp; isspace(*cp); ++cp); |
| 343 | if (*cp == ':') { |
| 344 | tag = b; |
| 345 | etag = e; |
| 346 | buf = cp + 1; |
| 347 | } |
| 348 | } |
| 349 | } else { |
| 350 | while (!isspace(*++cp) && (*cp != ':')); |
| 351 | const char *e; |
| 352 | for(e = cp; isspace(*cp); ++cp); |
| 353 | // Two words |
| 354 | if (*cp == ':') { |
| 355 | tag = bt; |
| 356 | etag = e; |
| 357 | buf = cp + 1; |
| 358 | } |
| 359 | } |
| 360 | } else if (isspace(cp[size])) { |
| 361 | const char *b = cp; |
| 362 | cp += size; |
| 363 | while (isspace(*++cp)); |
| 364 | // <PRI>[<TIME>] <tag> <tag> : message |
| 365 | if (*cp == ':') { |
| 366 | tag = bt; |
| 367 | etag = et; |
| 368 | buf = cp + 1; |
| 369 | } |
| 370 | } else if (cp[size] == ':') { |
| 371 | // <PRI>[<TIME>] <tag> <tag> : message |
| 372 | tag = bt; |
| 373 | etag = et; |
| 374 | buf = cp + size + 1; |
| 375 | } else if ((cp[size] == '.') || isdigit(cp[size])) { |
| 376 | // <PRI>[<TIME>] <tag> '<tag>.<num>' : message |
| 377 | // <PRI>[<TIME>] <tag> '<tag><num>' : message |
| 378 | const char *b = cp; |
| 379 | cp += size; |
| 380 | while (!isspace(*++cp) && (*cp != ':')); |
| 381 | const char *e = cp; |
| 382 | while (isspace(*cp)) { |
| 383 | ++cp; |
| 384 | } |
| 385 | if (*cp == ':') { |
| 386 | tag = b; |
| 387 | etag = e; |
| 388 | buf = cp + 1; |
| 389 | } |
| 390 | } else { |
| 391 | while (!isspace(*++cp) && (*cp != ':')); |
| 392 | const char *e = cp; |
| 393 | while (isspace(*cp)) { |
| 394 | ++cp; |
| 395 | } |
| 396 | // Two words |
| 397 | if (*cp == ':') { |
| 398 | tag = bt; |
| 399 | etag = e; |
| 400 | buf = cp + 1; |
| 401 | } |
| 402 | } |
| 403 | } |
| 404 | size = etag - tag; |
| 405 | if ((size <= 1) |
| 406 | || ((size == 2) && (isdigit(tag[0]) || isdigit(tag[1]))) |
| 407 | || ((size == 3) && !strncmp(tag, "CPU", 3)) |
| 408 | || ((size == 7) && !strncmp(tag, "WARNING", 7)) |
| 409 | || ((size == 5) && !strncmp(tag, "ERROR", 5)) |
| 410 | || ((size == 4) && !strncmp(tag, "INFO", 4))) { |
| 411 | buf = start; |
| 412 | etag = tag = ""; |
| 413 | } |
| 414 | } |
| 415 | size_t l = etag - tag; |
| 416 | while (isspace(*buf)) { |
| 417 | ++buf; |
| 418 | } |
| 419 | size_t n = 1 + l + 1 + strlen(buf) + 1; |
| 420 | |
| 421 | // Allocate a buffer to hold the interpreted log message |
| 422 | int rc = n; |
| 423 | char *newstr = reinterpret_cast<char *>(malloc(n)); |
| 424 | if (!newstr) { |
| 425 | rc = -ENOMEM; |
| 426 | return rc; |
| 427 | } |
| 428 | char *np = newstr; |
| 429 | |
| 430 | // Convert priority into single-byte Android logger priority |
| 431 | *np = convertKernelPrioToAndroidPrio(pri); |
| 432 | ++np; |
| 433 | |
| 434 | // Copy parsed tag following priority |
| 435 | strncpy(np, tag, l); |
| 436 | np += l; |
| 437 | *np = '\0'; |
| 438 | ++np; |
| 439 | |
| 440 | // Copy main message to the remainder |
| 441 | strcpy(np, buf); |
| 442 | |
| 443 | // Log message |
| 444 | rc = logbuf->log(LOG_ID_KERNEL, now, uid, pid, tid, newstr, |
| 445 | (n <= USHRT_MAX) ? (unsigned short) n : USHRT_MAX); |
| 446 | free(newstr); |
| 447 | |
| 448 | // notify readers |
| 449 | if (!rc) { |
| 450 | reader->notifyNewLog(); |
| 451 | } |
| 452 | |
| 453 | return rc; |
| 454 | } |