1 /* 2 * Copyright (C) 2009 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 package com.android.server; 18 19 import android.content.BroadcastReceiver; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.pm.IPackageManager; 23 import android.os.Build; 24 import android.os.DropBoxManager; 25 import android.os.Environment; 26 import android.os.FileObserver; 27 import android.os.FileUtils; 28 import android.os.RecoverySystem; 29 import android.os.RemoteException; 30 import android.os.ServiceManager; 31 import android.os.SystemProperties; 32 import android.os.storage.StorageManager; 33 import android.provider.Downloads; 34 import android.text.TextUtils; 35 import android.util.AtomicFile; 36 import android.util.Slog; 37 import android.util.Xml; 38 39 import com.android.internal.annotations.VisibleForTesting; 40 import com.android.internal.logging.MetricsLogger; 41 import com.android.internal.util.FastXmlSerializer; 42 import com.android.internal.util.XmlUtils; 43 44 import java.io.File; 45 import java.io.FileInputStream; 46 import java.io.FileOutputStream; 47 import java.io.IOException; 48 import java.io.FileNotFoundException; 49 import java.nio.charset.StandardCharsets; 50 import java.util.HashMap; 51 import java.util.Iterator; 52 import java.util.regex.Matcher; 53 import java.util.regex.Pattern; 54 55 import org.xmlpull.v1.XmlPullParser; 56 import org.xmlpull.v1.XmlPullParserException; 57 import org.xmlpull.v1.XmlSerializer; 58 59 /** 60 * Performs a number of miscellaneous, non-system-critical actions 61 * after the system has finished booting. 62 */ 63 public class BootReceiver extends BroadcastReceiver { 64 private static final String TAG = "BootReceiver"; 65 66 // Maximum size of a logged event (files get truncated if they're longer). 67 // Give userdebug builds a larger max to capture extra debug, esp. for last_kmsg. 68 private static final int LOG_SIZE = 69 SystemProperties.getInt("ro.debuggable", 0) == 1 ? 98304 : 65536; 70 71 private static final File TOMBSTONE_DIR = new File("/data/tombstones"); 72 73 // The pre-froyo package and class of the system updater, which 74 // ran in the system process. We need to remove its packages here 75 // in order to clean up after a pre-froyo-to-froyo update. 76 private static final String OLD_UPDATER_PACKAGE = 77 "com.google.android.systemupdater"; 78 private static final String OLD_UPDATER_CLASS = 79 "com.google.android.systemupdater.SystemUpdateReceiver"; 80 81 // Keep a reference to the observer so the finalizer doesn't disable it. 82 private static FileObserver sTombstoneObserver = null; 83 84 private static final String LOG_FILES_FILE = "log-files.xml"; 85 private static final AtomicFile sFile = new AtomicFile(new File( 86 Environment.getDataSystemDirectory(), LOG_FILES_FILE)); 87 private static final String LAST_HEADER_FILE = "last-header.txt"; 88 private static final File lastHeaderFile = new File( 89 Environment.getDataSystemDirectory(), LAST_HEADER_FILE); 90 91 // example: fs_stat,/dev/block/platform/soc/by-name/userdata,0x5 92 private static final String FS_STAT_PATTERN = "fs_stat,[^,]*/([^/,]+),(0x[0-9a-fA-F]+)"; 93 private static final int FS_STAT_FS_FIXED = 0x400; // should match with fs_mgr.cpp:FsStatFlags 94 private static final String FSCK_PASS_PATTERN = "Pass ([1-9]E?):"; 95 private static final String FSCK_TREE_OPTIMIZATION_PATTERN = 96 "Inode [0-9]+ extent tree.*could be shorter"; 97 private static final String FSCK_FS_MODIFIED = "FILE SYSTEM WAS MODIFIED"; 98 // ro.boottime.init.mount_all. + postfix for mount_all duration 99 private static final String[] MOUNT_DURATION_PROPS_POSTFIX = 100 new String[] { "early", "default", "late" }; 101 // for reboot, fs shutdown time is recorded in last_kmsg. 102 private static final String[] LAST_KMSG_FILES = 103 new String[] { "/sys/fs/pstore/console-ramoops", "/proc/last_kmsg" }; 104 // first: fs shutdown time in ms, second: umount status defined in init/reboot.h 105 private static final String LAST_SHUTDOWN_TIME_PATTERN = 106 "powerctl_shutdown_time_ms:([0-9]+):([0-9]+)"; 107 private static final int UMOUNT_STATUS_NOT_AVAILABLE = 4; // should match with init/reboot.h 108 109 // Location of file with metrics recorded during shutdown 110 private static final String SHUTDOWN_METRICS_FILE = "/data/system/shutdown-metrics.txt"; 111 112 private static final String SHUTDOWN_TRON_METRICS_PREFIX = "shutdown_"; 113 114 @Override 115 public void onReceive(final Context context, Intent intent) { 116 // Log boot events in the background to avoid blocking the main thread with I/O 117 new Thread() { 118 @Override 119 public void run() { 120 try { 121 logBootEvents(context); 122 } catch (Exception e) { 123 Slog.e(TAG, "Can't log boot events", e); 124 } 125 try { 126 boolean onlyCore = false; 127 try { 128 onlyCore = IPackageManager.Stub.asInterface(ServiceManager.getService( 129 "package")).isOnlyCoreApps(); 130 } catch (RemoteException e) { 131 } 132 if (!onlyCore) { 133 removeOldUpdatePackages(context); 134 } 135 } catch (Exception e) { 136 Slog.e(TAG, "Can't remove old update packages", e); 137 } 138 139 } 140 }.start(); 141 } 142 143 private void removeOldUpdatePackages(Context context) { 144 Downloads.removeAllDownloadsByPackage(context, OLD_UPDATER_PACKAGE, OLD_UPDATER_CLASS); 145 } 146 147 private String getPreviousBootHeaders() { 148 try { 149 return FileUtils.readTextFile(lastHeaderFile, 0, null); 150 } catch (IOException e) { 151 return null; 152 } 153 } 154 155 private String getCurrentBootHeaders() throws IOException { 156 return new StringBuilder(512) 157 .append("Build: ").append(Build.FINGERPRINT).append("\n") 158 .append("Hardware: ").append(Build.BOARD).append("\n") 159 .append("Revision: ") 160 .append(SystemProperties.get("ro.revision", "")).append("\n") 161 .append("Bootloader: ").append(Build.BOOTLOADER).append("\n") 162 .append("Radio: ").append(Build.RADIO).append("\n") 163 .append("Kernel: ") 164 .append(FileUtils.readTextFile(new File("/proc/version"), 1024, "...\n")) 165 .append("\n").toString(); 166 } 167 168 169 private String getBootHeadersToLogAndUpdate() throws IOException { 170 final String oldHeaders = getPreviousBootHeaders(); 171 final String newHeaders = getCurrentBootHeaders(); 172 173 try { 174 FileUtils.stringToFile(lastHeaderFile, newHeaders); 175 } catch (IOException e) { 176 Slog.e(TAG, "Error writing " + lastHeaderFile, e); 177 } 178 179 if (oldHeaders == null) { 180 // If we failed to read the old headers, use the current headers 181 // but note this in the headers so we know 182 return "isPrevious: false\n" + newHeaders; 183 } 184 185 return "isPrevious: true\n" + oldHeaders; 186 } 187 188 private void logBootEvents(Context ctx) throws IOException { 189 final DropBoxManager db = (DropBoxManager) ctx.getSystemService(Context.DROPBOX_SERVICE); 190 final String headers = getBootHeadersToLogAndUpdate(); 191 final String bootReason = SystemProperties.get("ro.boot.bootreason", null); 192 193 String recovery = RecoverySystem.handleAftermath(ctx); 194 if (recovery != null && db != null) { 195 db.addText("SYSTEM_RECOVERY_LOG", headers + recovery); 196 } 197 198 String lastKmsgFooter = ""; 199 if (bootReason != null) { 200 lastKmsgFooter = new StringBuilder(512) 201 .append("\n") 202 .append("Boot info:\n") 203 .append("Last boot reason: ").append(bootReason).append("\n") 204 .toString(); 205 } 206 207 HashMap<String, Long> timestamps = readTimestamps(); 208 209 if (SystemProperties.getLong("ro.runtime.firstboot", 0) == 0) { 210 if (StorageManager.inCryptKeeperBounce()) { 211 // Encrypted, first boot to get PIN/pattern/password so data is tmpfs 212 // Don't set ro.runtime.firstboot so that we will do this again 213 // when data is properly mounted 214 } else { 215 String now = Long.toString(System.currentTimeMillis()); 216 SystemProperties.set("ro.runtime.firstboot", now); 217 } 218 if (db != null) db.addText("SYSTEM_BOOT", headers); 219 220 // Negative sizes mean to take the *tail* of the file (see FileUtils.readTextFile()) 221 addFileWithFootersToDropBox(db, timestamps, headers, lastKmsgFooter, 222 "/proc/last_kmsg", -LOG_SIZE, "SYSTEM_LAST_KMSG"); 223 addFileWithFootersToDropBox(db, timestamps, headers, lastKmsgFooter, 224 "/sys/fs/pstore/console-ramoops", -LOG_SIZE, "SYSTEM_LAST_KMSG"); 225 addFileWithFootersToDropBox(db, timestamps, headers, lastKmsgFooter, 226 "/sys/fs/pstore/console-ramoops-0", -LOG_SIZE, "SYSTEM_LAST_KMSG"); 227 addFileToDropBox(db, timestamps, headers, "/cache/recovery/log", -LOG_SIZE, 228 "SYSTEM_RECOVERY_LOG"); 229 addFileToDropBox(db, timestamps, headers, "/cache/recovery/last_kmsg", 230 -LOG_SIZE, "SYSTEM_RECOVERY_KMSG"); 231 addAuditErrorsToDropBox(db, timestamps, headers, -LOG_SIZE, "SYSTEM_AUDIT"); 232 } else { 233 if (db != null) db.addText("SYSTEM_RESTART", headers); 234 } 235 // log always available fs_stat last so that logcat collecting tools can wait until 236 // fs_stat to get all file system metrics. 237 logFsShutdownTime(); 238 logFsMountTime(); 239 addFsckErrorsToDropBoxAndLogFsStat(db, timestamps, headers, -LOG_SIZE, "SYSTEM_FSCK"); 240 logSystemServerShutdownTimeMetrics(); 241 242 // Scan existing tombstones (in case any new ones appeared) 243 File[] tombstoneFiles = TOMBSTONE_DIR.listFiles(); 244 for (int i = 0; tombstoneFiles != null && i < tombstoneFiles.length; i++) { 245 if (tombstoneFiles[i].isFile()) { 246 addFileToDropBox(db, timestamps, headers, tombstoneFiles[i].getPath(), 247 LOG_SIZE, "SYSTEM_TOMBSTONE"); 248 } 249 } 250 251 writeTimestamps(timestamps); 252 253 // Start watching for new tombstone files; will record them as they occur. 254 // This gets registered with the singleton file observer thread. 255 sTombstoneObserver = new FileObserver(TOMBSTONE_DIR.getPath(), FileObserver.CLOSE_WRITE) { 256 @Override 257 public void onEvent(int event, String path) { 258 HashMap<String, Long> timestamps = readTimestamps(); 259 try { 260 File file = new File(TOMBSTONE_DIR, path); 261 if (file.isFile()) { 262 addFileToDropBox(db, timestamps, headers, file.getPath(), LOG_SIZE, 263 "SYSTEM_TOMBSTONE"); 264 } 265 } catch (IOException e) { 266 Slog.e(TAG, "Can't log tombstone", e); 267 } 268 writeTimestamps(timestamps); 269 } 270 }; 271 272 sTombstoneObserver.startWatching(); 273 } 274 275 private static void addFileToDropBox( 276 DropBoxManager db, HashMap<String, Long> timestamps, 277 String headers, String filename, int maxSize, String tag) throws IOException { 278 addFileWithFootersToDropBox(db, timestamps, headers, "", filename, maxSize, tag); 279 } 280 281 private static void addFileWithFootersToDropBox( 282 DropBoxManager db, HashMap<String, Long> timestamps, 283 String headers, String footers, String filename, int maxSize, 284 String tag) throws IOException { 285 if (db == null || !db.isTagEnabled(tag)) return; // Logging disabled 286 287 File file = new File(filename); 288 long fileTime = file.lastModified(); 289 if (fileTime <= 0) return; // File does not exist 290 291 if (timestamps.containsKey(filename) && timestamps.get(filename) == fileTime) { 292 return; // Already logged this particular file 293 } 294 295 timestamps.put(filename, fileTime); 296 297 Slog.i(TAG, "Copying " + filename + " to DropBox (" + tag + ")"); 298 db.addText(tag, headers + FileUtils.readTextFile(file, maxSize, "[[TRUNCATED]]\n") + 299 footers); 300 } 301 302 private static void addAuditErrorsToDropBox(DropBoxManager db, 303 HashMap<String, Long> timestamps, String headers, int maxSize, String tag) 304 throws IOException { 305 if (db == null || !db.isTagEnabled(tag)) return; // Logging disabled 306 Slog.i(TAG, "Copying audit failures to DropBox"); 307 308 File file = new File("/proc/last_kmsg"); 309 long fileTime = file.lastModified(); 310 if (fileTime <= 0) { 311 file = new File("/sys/fs/pstore/console-ramoops"); 312 fileTime = file.lastModified(); 313 if (fileTime <= 0) { 314 file = new File("/sys/fs/pstore/console-ramoops-0"); 315 fileTime = file.lastModified(); 316 } 317 } 318 319 if (fileTime <= 0) return; // File does not exist 320 321 if (timestamps.containsKey(tag) && timestamps.get(tag) == fileTime) { 322 return; // Already logged this particular file 323 } 324 325 timestamps.put(tag, fileTime); 326 327 String log = FileUtils.readTextFile(file, maxSize, "[[TRUNCATED]]\n"); 328 StringBuilder sb = new StringBuilder(); 329 for (String line : log.split("\n")) { 330 if (line.contains("audit")) { 331 sb.append(line + "\n"); 332 } 333 } 334 Slog.i(TAG, "Copied " + sb.toString().length() + " worth of audits to DropBox"); 335 db.addText(tag, headers + sb.toString()); 336 } 337 338 private static void addFsckErrorsToDropBoxAndLogFsStat(DropBoxManager db, 339 HashMap<String, Long> timestamps, String headers, int maxSize, String tag) 340 throws IOException { 341 boolean uploadEnabled = true; 342 if (db == null || !db.isTagEnabled(tag)) { 343 uploadEnabled = false; 344 } 345 boolean uploadNeeded = false; 346 Slog.i(TAG, "Checking for fsck errors"); 347 348 File file = new File("/dev/fscklogs/log"); 349 long fileTime = file.lastModified(); 350 if (fileTime <= 0) return; // File does not exist 351 352 String log = FileUtils.readTextFile(file, maxSize, "[[TRUNCATED]]\n"); 353 Pattern pattern = Pattern.compile(FS_STAT_PATTERN); 354 String lines[] = log.split("\n"); 355 int lineNumber = 0; 356 int lastFsStatLineNumber = 0; 357 for (String line : lines) { // should check all lines 358 if (line.contains(FSCK_FS_MODIFIED)) { 359 uploadNeeded = true; 360 } else if (line.contains("fs_stat")){ 361 Matcher matcher = pattern.matcher(line); 362 if (matcher.find()) { 363 handleFsckFsStat(matcher, lines, lastFsStatLineNumber, lineNumber); 364 lastFsStatLineNumber = lineNumber; 365 } else { 366 Slog.w(TAG, "cannot parse fs_stat:" + line); 367 } 368 } 369 lineNumber++; 370 } 371 372 if (uploadEnabled && uploadNeeded ) { 373 addFileToDropBox(db, timestamps, headers, "/dev/fscklogs/log", maxSize, tag); 374 } 375 376 // Remove the file so we don't re-upload if the runtime restarts. 377 file.delete(); 378 } 379 380 private static void logFsMountTime() { 381 for (String propPostfix : MOUNT_DURATION_PROPS_POSTFIX) { 382 int duration = SystemProperties.getInt("ro.boottime.init.mount_all." + propPostfix, 0); 383 if (duration != 0) { 384 MetricsLogger.histogram(null, "boot_mount_all_duration_" + propPostfix, duration); 385 } 386 } 387 } 388 389 // TODO b/64815357 Move to bootstat.cpp and log AbsoluteRebootTime 390 private static void logSystemServerShutdownTimeMetrics() { 391 File metricsFile = new File(SHUTDOWN_METRICS_FILE); 392 String metricsStr = null; 393 if (metricsFile.exists()) { 394 try { 395 metricsStr = FileUtils.readTextFile(metricsFile, 0, null); 396 } catch (IOException e) { 397 Slog.e(TAG, "Problem reading " + metricsFile, e); 398 } 399 } 400 if (!TextUtils.isEmpty(metricsStr)) { 401 String[] array = metricsStr.split(","); 402 for (String keyValueStr : array) { 403 String[] keyValue = keyValueStr.split(":"); 404 if (keyValue.length != 2) { 405 Slog.e(TAG, "Wrong format of shutdown metrics - " + metricsStr); 406 continue; 407 } 408 // Ignore keys that are not indended for tron 409 if (keyValue[0].startsWith(SHUTDOWN_TRON_METRICS_PREFIX)) { 410 logTronShutdownMetric(keyValue[0], keyValue[1]); 411 } 412 } 413 } 414 metricsFile.delete(); 415 } 416 417 private static void logTronShutdownMetric(String metricName, String valueStr) { 418 int value; 419 try { 420 value = Integer.parseInt(valueStr); 421 } catch (NumberFormatException e) { 422 Slog.e(TAG, "Cannot parse metric " + metricName + " int value - " + valueStr); 423 return; 424 } 425 if (value >= 0) { 426 MetricsLogger.histogram(null, metricName, value); 427 } 428 } 429 430 private static void logFsShutdownTime() { 431 File f = null; 432 for (String fileName : LAST_KMSG_FILES) { 433 File file = new File(fileName); 434 if (!file.exists()) continue; 435 f = file; 436 break; 437 } 438 if (f == null) { // no last_kmsg 439 return; 440 } 441 442 final int maxReadSize = 16*1024; 443 // last_kmsg can be very big, so only parse the last part 444 String lines; 445 try { 446 lines = FileUtils.readTextFile(f, -maxReadSize, null); 447 } catch (IOException e) { 448 Slog.w(TAG, "cannot read last msg", e); 449 return; 450 } 451 Pattern pattern = Pattern.compile(LAST_SHUTDOWN_TIME_PATTERN, Pattern.MULTILINE); 452 Matcher matcher = pattern.matcher(lines); 453 if (matcher.find()) { 454 MetricsLogger.histogram(null, "boot_fs_shutdown_duration", 455 Integer.parseInt(matcher.group(1))); 456 MetricsLogger.histogram(null, "boot_fs_shutdown_umount_stat", 457 Integer.parseInt(matcher.group(2))); 458 Slog.i(TAG, "boot_fs_shutdown," + matcher.group(1) + "," + matcher.group(2)); 459 } else { // not found 460 // This can happen when a device has too much kernel log after file system unmount 461 // ,exceeding maxReadSize. And having that much kernel logging can affect overall 462 // performance as well. So it is better to fix the kernel to reduce the amount of log. 463 MetricsLogger.histogram(null, "boot_fs_shutdown_umount_stat", 464 UMOUNT_STATUS_NOT_AVAILABLE); 465 Slog.w(TAG, "boot_fs_shutdown, string not found"); 466 } 467 } 468 469 /** 470 * Fix fs_stat from e2fsck. 471 * For now, only handle the case of quota warning caused by tree optimization. Clear fs fix 472 * flag (=0x400) caused by that. 473 * 474 * @param partition partition name 475 * @param statOrg original stat reported from e2fsck log 476 * @param lines e2fsck logs broken down into lines 477 * @param startLineNumber start line to parse 478 * @param endLineNumber end line. exclusive. 479 * @return updated fs_stat. For tree optimization, will clear bit 0x400. 480 */ 481 @VisibleForTesting 482 public static int fixFsckFsStat(String partition, int statOrg, String[] lines, 483 int startLineNumber, int endLineNumber) { 484 int stat = statOrg; 485 if ((stat & FS_STAT_FS_FIXED) != 0) { 486 // fs was fixed. should check if quota warning was caused by tree optimization. 487 // This is not a real fix but optimization, so should not be counted as a fs fix. 488 Pattern passPattern = Pattern.compile(FSCK_PASS_PATTERN); 489 Pattern treeOptPattern = Pattern.compile(FSCK_TREE_OPTIMIZATION_PATTERN); 490 String currentPass = ""; 491 boolean foundTreeOptimization = false; 492 boolean foundQuotaFix = false; 493 boolean foundTimestampAdjustment = false; 494 boolean foundOtherFix = false; 495 String otherFixLine = null; 496 for (int i = startLineNumber; i < endLineNumber; i++) { 497 String line = lines[i]; 498 if (line.contains(FSCK_FS_MODIFIED)) { // no need to parse above this 499 break; 500 } else if (line.startsWith("Pass ")) { 501 Matcher matcher = passPattern.matcher(line); 502 if (matcher.find()) { 503 currentPass = matcher.group(1); 504 } 505 } else if (line.startsWith("Inode ")) { 506 Matcher matcher = treeOptPattern.matcher(line); 507 if (matcher.find() && currentPass.equals("1")) { 508 foundTreeOptimization = true; 509 Slog.i(TAG, "fs_stat, partition:" + partition + " found tree optimization:" 510 + line); 511 } else { 512 foundOtherFix = true; 513 otherFixLine = line; 514 break; 515 } 516 } else if (line.startsWith("[QUOTA WARNING]") && currentPass.equals("5")) { 517 Slog.i(TAG, "fs_stat, partition:" + partition + " found quota warning:" 518 + line); 519 foundQuotaFix = true; 520 if (!foundTreeOptimization) { // only quota warning, this is real fix. 521 otherFixLine = line; 522 break; 523 } 524 } else if (line.startsWith("Update quota info") && currentPass.equals("5")) { 525 // follows "[QUOTA WARNING]", ignore 526 } else if (line.startsWith("Timestamp(s) on inode") && 527 line.contains("beyond 2310-04-04 are likely pre-1970") && 528 currentPass.equals("1")) { 529 Slog.i(TAG, "fs_stat, partition:" + partition + " found timestamp adjustment:" 530 + line); 531 // followed by next line, "Fix? yes" 532 if (lines[i + 1].contains("Fix? yes")) { 533 i++; 534 } 535 foundTimestampAdjustment = true; 536 } else { 537 line = line.trim(); 538 // ignore empty msg or any msg before Pass 1 539 if (!line.isEmpty() && !currentPass.isEmpty()) { 540 foundOtherFix = true; 541 otherFixLine = line; 542 break; 543 } 544 } 545 } 546 if (foundOtherFix) { 547 if (otherFixLine != null) { 548 Slog.i(TAG, "fs_stat, partition:" + partition + " fix:" + otherFixLine); 549 } 550 } else if (foundQuotaFix && !foundTreeOptimization) { 551 Slog.i(TAG, "fs_stat, got quota fix without tree optimization, partition:" + 552 partition); 553 } else if ((foundTreeOptimization && foundQuotaFix) || foundTimestampAdjustment) { 554 // not a real fix, so clear it. 555 Slog.i(TAG, "fs_stat, partition:" + partition + " fix ignored"); 556 stat &= ~FS_STAT_FS_FIXED; 557 } 558 } 559 return stat; 560 } 561 562 private static void handleFsckFsStat(Matcher match, String[] lines, int startLineNumber, 563 int endLineNumber) { 564 String partition = match.group(1); 565 int stat; 566 try { 567 stat = Integer.decode(match.group(2)); 568 } catch (NumberFormatException e) { 569 Slog.w(TAG, "cannot parse fs_stat: partition:" + partition + " stat:" + match.group(2)); 570 return; 571 } 572 stat = fixFsckFsStat(partition, stat, lines, startLineNumber, endLineNumber); 573 MetricsLogger.histogram(null, "boot_fs_stat_" + partition, stat); 574 Slog.i(TAG, "fs_stat, partition:" + partition + " stat:0x" + Integer.toHexString(stat)); 575 } 576 577 private static HashMap<String, Long> readTimestamps() { 578 synchronized (sFile) { 579 HashMap<String, Long> timestamps = new HashMap<String, Long>(); 580 boolean success = false; 581 try (final FileInputStream stream = sFile.openRead()) { 582 XmlPullParser parser = Xml.newPullParser(); 583 parser.setInput(stream, StandardCharsets.UTF_8.name()); 584 585 int type; 586 while ((type = parser.next()) != XmlPullParser.START_TAG 587 && type != XmlPullParser.END_DOCUMENT) { 588 ; 589 } 590 591 if (type != XmlPullParser.START_TAG) { 592 throw new IllegalStateException("no start tag found"); 593 } 594 595 int outerDepth = parser.getDepth(); // Skip the outer <log-files> tag. 596 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 597 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { 598 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { 599 continue; 600 } 601 602 String tagName = parser.getName(); 603 if (tagName.equals("log")) { 604 final String filename = parser.getAttributeValue(null, "filename"); 605 final long timestamp = Long.valueOf(parser.getAttributeValue( 606 null, "timestamp")); 607 timestamps.put(filename, timestamp); 608 } else { 609 Slog.w(TAG, "Unknown tag: " + parser.getName()); 610 XmlUtils.skipCurrentTag(parser); 611 } 612 } 613 success = true; 614 } catch (FileNotFoundException e) { 615 Slog.i(TAG, "No existing last log timestamp file " + sFile.getBaseFile() + 616 "; starting empty"); 617 } catch (IOException e) { 618 Slog.w(TAG, "Failed parsing " + e); 619 } catch (IllegalStateException e) { 620 Slog.w(TAG, "Failed parsing " + e); 621 } catch (NullPointerException e) { 622 Slog.w(TAG, "Failed parsing " + e); 623 } catch (XmlPullParserException e) { 624 Slog.w(TAG, "Failed parsing " + e); 625 } finally { 626 if (!success) { 627 timestamps.clear(); 628 } 629 } 630 return timestamps; 631 } 632 } 633 634 private void writeTimestamps(HashMap<String, Long> timestamps) { 635 synchronized (sFile) { 636 final FileOutputStream stream; 637 try { 638 stream = sFile.startWrite(); 639 } catch (IOException e) { 640 Slog.w(TAG, "Failed to write timestamp file: " + e); 641 return; 642 } 643 644 try { 645 XmlSerializer out = new FastXmlSerializer(); 646 out.setOutput(stream, StandardCharsets.UTF_8.name()); 647 out.startDocument(null, true); 648 out.startTag(null, "log-files"); 649 650 Iterator<String> itor = timestamps.keySet().iterator(); 651 while (itor.hasNext()) { 652 String filename = itor.next(); 653 out.startTag(null, "log"); 654 out.attribute(null, "filename", filename); 655 out.attribute(null, "timestamp", timestamps.get(filename).toString()); 656 out.endTag(null, "log"); 657 } 658 659 out.endTag(null, "log-files"); 660 out.endDocument(); 661 662 sFile.finishWrite(stream); 663 } catch (IOException e) { 664 Slog.w(TAG, "Failed to write timestamp file, using the backup: " + e); 665 sFile.failWrite(stream); 666 } 667 } 668 } 669 } 670