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 static android.os.ParcelFileDescriptor.MODE_READ_WRITE; 20 import static android.system.OsConstants.O_RDONLY; 21 22 import android.content.BroadcastReceiver; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.os.Build; 26 import android.os.DropBoxManager; 27 import android.os.Environment; 28 import android.os.FileUtils; 29 import android.os.MessageQueue.OnFileDescriptorEventListener; 30 import android.os.ParcelFileDescriptor; 31 import android.os.RecoverySystem; 32 import android.os.SystemProperties; 33 import android.os.TombstoneWithHeadersProto; 34 import android.provider.Downloads; 35 import android.system.ErrnoException; 36 import android.system.Os; 37 import android.system.OsConstants; 38 import android.text.TextUtils; 39 import android.util.AtomicFile; 40 import android.util.EventLog; 41 import android.util.Slog; 42 import android.util.Xml; 43 import android.util.proto.ProtoFieldFilter; 44 import android.util.proto.ProtoOutputStream; 45 import android.util.proto.ProtoParseException; 46 47 import com.android.internal.annotations.VisibleForTesting; 48 import com.android.internal.logging.MetricsLogger; 49 import com.android.internal.util.FrameworkStatsLog; 50 import com.android.internal.util.XmlUtils; 51 import com.android.modules.utils.TypedXmlPullParser; 52 import com.android.modules.utils.TypedXmlSerializer; 53 import com.android.server.am.DropboxRateLimiter; 54 import com.android.server.os.TombstoneProtos.Tombstone; 55 56 import libcore.io.IoUtils; 57 import org.xmlpull.v1.XmlPullParser; 58 import org.xmlpull.v1.XmlPullParserException; 59 60 import java.io.BufferedInputStream; 61 import java.io.BufferedOutputStream; 62 import java.io.ByteArrayOutputStream; 63 import java.io.File; 64 import java.io.FileDescriptor; 65 import java.io.FileInputStream; 66 import java.io.FileNotFoundException; 67 import java.io.FileOutputStream; 68 import java.io.IOException; 69 import java.nio.file.Files; 70 import java.nio.file.attribute.PosixFilePermissions; 71 import java.util.HashMap; 72 import java.util.Iterator; 73 import java.util.concurrent.TimeUnit; 74 import java.util.concurrent.locks.ReentrantLock; 75 import java.util.regex.Matcher; 76 import java.util.regex.Pattern; 77 78 /** 79 * Performs a number of miscellaneous, non-system-critical actions 80 * after the system has finished booting. 81 */ 82 public class BootReceiver extends BroadcastReceiver { 83 private static final String TAG = "BootReceiver"; 84 85 private static final String TAG_TRUNCATED = "[[TRUNCATED]]\n"; 86 87 // Maximum size of a logged event (files get truncated if they're longer). 88 // Give userdebug builds a larger max to capture extra debug, esp. for last_kmsg. 89 private static final int LOG_SIZE = 90 SystemProperties.getInt("ro.debuggable", 0) == 1 ? 98304 : 65536; 91 private static final int LASTK_LOG_SIZE = 92 SystemProperties.getInt("ro.debuggable", 0) == 1 ? 196608 : 65536; 93 private static final int GMSCORE_LASTK_LOG_SIZE = 196608; 94 95 private static final String TAG_TOMBSTONE = "SYSTEM_TOMBSTONE"; 96 private static final String TAG_TOMBSTONE_PROTO = "SYSTEM_TOMBSTONE_PROTO"; 97 private static final String TAG_TOMBSTONE_PROTO_WITH_HEADERS = 98 "SYSTEM_TOMBSTONE_PROTO_WITH_HEADERS"; 99 100 // Directory to store temporary tombstones. 101 private static final File TOMBSTONE_TMP_DIR = new File("/data/tombstones"); 102 103 // The pre-froyo package and class of the system updater, which 104 // ran in the system process. We need to remove its packages here 105 // in order to clean up after a pre-froyo-to-froyo update. 106 private static final String OLD_UPDATER_PACKAGE = 107 "com.google.android.systemupdater"; 108 private static final String OLD_UPDATER_CLASS = 109 "com.google.android.systemupdater.SystemUpdateReceiver"; 110 111 private static final String LOG_FILES_FILE = "log-files.xml"; 112 private static final AtomicFile sFile = new AtomicFile(new File( 113 Environment.getDataSystemDirectory(), LOG_FILES_FILE), "log-files"); 114 private static final String LAST_HEADER_FILE = "last-header.txt"; 115 private static final File lastHeaderFile = new File( 116 Environment.getDataSystemDirectory(), LAST_HEADER_FILE); 117 118 // example: fs_stat,/dev/block/platform/soc/by-name/userdata,0x5 119 private static final String FS_STAT_PATTERN = "fs_stat,[^,]*/([^/,]+),(0x[0-9a-fA-F]+)"; 120 private static final int FS_STAT_FSCK_FS_FIXED = 121 0x400; // should match with fs_mgr.cpp:FsStatFlags 122 private static final String FSCK_PASS_PATTERN = "Pass ([1-9]E?):"; 123 private static final String FSCK_TREE_OPTIMIZATION_PATTERN = 124 "Inode [0-9]+ extent tree.*could be shorter"; 125 private static final String E2FSCK_FS_MODIFIED = "FILE SYSTEM WAS MODIFIED"; 126 private static final String F2FS_FSCK_FS_MODIFIED = "[FSCK] Unreachable"; 127 // ro.boottime.init.mount_all. + postfix for mount_all duration 128 private static final String[] MOUNT_DURATION_PROPS_POSTFIX = 129 new String[] { "early", "default", "late" }; 130 // for reboot, fs shutdown time is recorded in last_kmsg. 131 private static final String[] LAST_KMSG_FILES = 132 new String[] { "/sys/fs/pstore/console-ramoops", "/proc/last_kmsg" }; 133 // first: fs shutdown time in ms, second: umount status defined in init/reboot.h 134 private static final String LAST_SHUTDOWN_TIME_PATTERN = 135 "powerctl_shutdown_time_ms:([0-9]+):([0-9]+)"; 136 private static final int UMOUNT_STATUS_NOT_AVAILABLE = 4; // should match with init/reboot.h 137 138 // Location of file with metrics recorded during shutdown 139 private static final String SHUTDOWN_METRICS_FILE = "/data/system/shutdown-metrics.txt"; 140 141 private static final String SHUTDOWN_TRON_METRICS_PREFIX = "shutdown_"; 142 private static final String METRIC_SYSTEM_SERVER = "shutdown_system_server"; 143 private static final String METRIC_SHUTDOWN_TIME_START = "begin_shutdown"; 144 145 // Location of ftrace pipe for notifications from kernel memory tools like KFENCE and KASAN. 146 private static final String ERROR_REPORT_TRACE_PIPE = 147 "/sys/kernel/tracing/instances/bootreceiver/trace_pipe"; 148 // Stop after sending too many reports. See http://b/182159975. 149 private static final int MAX_ERROR_REPORTS = 8; 150 private static int sSentReports = 0; 151 152 // Max tombstone file size to add to dropbox. 153 private static final long MAX_TOMBSTONE_SIZE_BYTES = 154 DropBoxManagerService.DEFAULT_QUOTA_KB * 1024; 155 156 @Override onReceive(final Context context, Intent intent)157 public void onReceive(final Context context, Intent intent) { 158 if (!Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) { 159 return; 160 } 161 162 // Log boot events in the background to avoid blocking the main thread with I/O 163 new Thread() { 164 @Override 165 public void run() { 166 try { 167 logBootEvents(context); 168 } catch (Exception e) { 169 Slog.e(TAG, "Can't log boot events", e); 170 } 171 try { 172 removeOldUpdatePackages(context); 173 } catch (Exception e) { 174 Slog.e(TAG, "Can't remove old update packages", e); 175 } 176 177 } 178 }.start(); 179 180 FileDescriptor tracefd = null; 181 try { 182 tracefd = Os.open(ERROR_REPORT_TRACE_PIPE, O_RDONLY, 0600); 183 } catch (ErrnoException e) { 184 Slog.wtf(TAG, "Could not open " + ERROR_REPORT_TRACE_PIPE, e); 185 return; 186 } 187 188 /* 189 * Event listener to watch for memory tool error reports. 190 * We read from /sys/kernel/tracing/instances/bootreceiver/trace_pipe (set up by the 191 * system), which will print an ftrace event when a memory corruption is detected in the 192 * kernel. 193 * When an error is detected, we set the dmesg.start system property to notify dmesgd 194 * about a new error. 195 */ 196 OnFileDescriptorEventListener traceCallback = new OnFileDescriptorEventListener() { 197 final int mBufferSize = 1024; 198 byte[] mTraceBuffer = new byte[mBufferSize]; 199 @Override 200 public int onFileDescriptorEvents(FileDescriptor fd, int events) { 201 /* 202 * Read from the tracing pipe set up to monitor the error_report_end events. 203 * When a tracing event occurs, the kernel writes a short (~100 bytes) line to the 204 * pipe, e.g.: 205 * ...-11210 [004] d..1 285.322307: error_report_end: [kfence] ffffff8938a05000 206 * The buffer size we use for reading should be enough to read the whole 207 * line, but to be on the safe side we keep reading until the buffer 208 * contains a '\n' character. In the unlikely case of a very buggy kernel 209 * the buffer may contain multiple tracing events that cannot be attributed 210 * to particular error reports. dmesgd will take care of all errors. 211 */ 212 try { 213 int nbytes = Os.read(fd, mTraceBuffer, 0, mBufferSize); 214 if (nbytes > 0) { 215 String readStr = new String(mTraceBuffer); 216 if (readStr.indexOf("\n") == -1) { 217 return OnFileDescriptorEventListener.EVENT_INPUT; 218 } 219 if (sSentReports < MAX_ERROR_REPORTS) { 220 SystemProperties.set("dmesgd.start", "1"); 221 sSentReports++; 222 } 223 } 224 } catch (Exception e) { 225 Slog.wtf(TAG, "Error watching for trace events", e); 226 return 0; // Unregister the handler. 227 } finally { 228 IoUtils.closeQuietly(fd); 229 } 230 return OnFileDescriptorEventListener.EVENT_INPUT; 231 } 232 }; 233 234 IoThread.get().getLooper().getQueue().addOnFileDescriptorEventListener( 235 tracefd, OnFileDescriptorEventListener.EVENT_INPUT, traceCallback); 236 237 } 238 removeOldUpdatePackages(Context context)239 private void removeOldUpdatePackages(Context context) { 240 Downloads.removeAllDownloadsByPackage(context, OLD_UPDATER_PACKAGE, OLD_UPDATER_CLASS); 241 } 242 getPreviousBootHeaders()243 private static String getPreviousBootHeaders() { 244 try { 245 return FileUtils.readTextFile(lastHeaderFile, 0, null); 246 } catch (IOException e) { 247 return null; 248 } 249 } 250 getCurrentBootHeaders()251 private static String getCurrentBootHeaders() throws IOException { 252 StringBuilder builder = new StringBuilder(512) 253 .append("Build: ").append(Build.FINGERPRINT).append("\n") 254 .append("Hardware: ").append(Build.BOARD).append("\n") 255 .append("Revision: ") 256 .append(SystemProperties.get("ro.revision", "")).append("\n") 257 .append("Bootloader: ").append(Build.BOOTLOADER).append("\n") 258 .append("Radio: ").append(Build.getRadioVersion()).append("\n") 259 .append("Kernel: ") 260 .append(FileUtils.readTextFile(new File("/proc/version"), 1024, "...\n")); 261 262 // If device is not using 4KB pages, add the PageSize 263 long pageSize = Os.sysconf(OsConstants._SC_PAGESIZE); 264 if (pageSize != 4096) { 265 builder.append("PageSize: ").append(pageSize).append("\n"); 266 } 267 builder.append("\n"); 268 return builder.toString(); 269 } 270 271 getBootHeadersToLogAndUpdate()272 private static String getBootHeadersToLogAndUpdate() throws IOException { 273 final String oldHeaders = getPreviousBootHeaders(); 274 final String newHeaders = getCurrentBootHeaders(); 275 276 try { 277 FileUtils.stringToFile(lastHeaderFile, newHeaders); 278 } catch (IOException e) { 279 Slog.e(TAG, "Error writing " + lastHeaderFile, e); 280 } 281 282 if (oldHeaders == null) { 283 // If we failed to read the old headers, use the current headers 284 // but note this in the headers so we know 285 return "isPrevious: false\n" + newHeaders; 286 } 287 288 return "isPrevious: true\n" + oldHeaders; 289 } 290 logBootEvents(Context ctx)291 private void logBootEvents(Context ctx) throws IOException { 292 final DropBoxManager db = (DropBoxManager) ctx.getSystemService(Context.DROPBOX_SERVICE); 293 final String headers = getBootHeadersToLogAndUpdate(); 294 final String bootReason = SystemProperties.get("ro.boot.bootreason", null); 295 296 String recovery = RecoverySystem.handleAftermath(ctx); 297 if (recovery != null && db != null) { 298 db.addText("SYSTEM_RECOVERY_LOG", headers + recovery); 299 } 300 301 String lastKmsgFooter = ""; 302 if (bootReason != null) { 303 lastKmsgFooter = new StringBuilder(512) 304 .append("\n") 305 .append("Boot info:\n") 306 .append("Last boot reason: ").append(bootReason).append("\n") 307 .toString(); 308 } 309 310 HashMap<String, Long> timestamps = readTimestamps(); 311 312 if (SystemProperties.getLong("ro.runtime.firstboot", 0) == 0) { 313 String now = Long.toString(System.currentTimeMillis()); 314 SystemProperties.set("ro.runtime.firstboot", now); 315 if (db != null) db.addText("SYSTEM_BOOT", headers); 316 317 // Negative sizes mean to take the *tail* of the file (see FileUtils.readTextFile()) 318 addLastkToDropBox(db, timestamps, headers, lastKmsgFooter, 319 "/proc/last_kmsg", -LASTK_LOG_SIZE, "SYSTEM_LAST_KMSG"); 320 addLastkToDropBox(db, timestamps, headers, lastKmsgFooter, 321 "/sys/fs/pstore/console-ramoops", -LASTK_LOG_SIZE, "SYSTEM_LAST_KMSG"); 322 addLastkToDropBox(db, timestamps, headers, lastKmsgFooter, 323 "/sys/fs/pstore/console-ramoops-0", -LASTK_LOG_SIZE, "SYSTEM_LAST_KMSG"); 324 addFileToDropBox(db, timestamps, headers, "/cache/recovery/log", -LOG_SIZE, 325 "SYSTEM_RECOVERY_LOG"); 326 addFileToDropBox(db, timestamps, headers, "/cache/recovery/last_kmsg", 327 -LOG_SIZE, "SYSTEM_RECOVERY_KMSG"); 328 addAuditErrorsToDropBox(db, timestamps, headers, -LOG_SIZE, "SYSTEM_AUDIT"); 329 } else { 330 if (db != null) db.addText("SYSTEM_RESTART", headers); 331 } 332 // log always available fs_stat last so that logcat collecting tools can wait until 333 // fs_stat to get all file system metrics. 334 logFsShutdownTime(); 335 logFsMountTime(); 336 addFsckErrorsToDropBoxAndLogFsStat(db, timestamps, headers, -LOG_SIZE, "SYSTEM_FSCK"); 337 logSystemServerShutdownTimeMetrics(); 338 writeTimestamps(timestamps); 339 } 340 341 private static final DropboxRateLimiter sDropboxRateLimiter = new DropboxRateLimiter(); 342 343 /** Initialize the rate limiter. */ initDropboxRateLimiter()344 public static void initDropboxRateLimiter() { 345 sDropboxRateLimiter.init(); 346 } 347 348 /** 349 * Reset the dropbox rate limiter. 350 */ 351 @VisibleForTesting resetDropboxRateLimiter()352 public static void resetDropboxRateLimiter() { 353 sDropboxRateLimiter.reset(); 354 } 355 356 /** 357 * Add a tombstone to the DropBox. 358 * 359 * @param ctx Context 360 * @param tombstone path to the tombstone 361 * @param proto whether the tombstone is stored as proto 362 * @param processName the name of the process corresponding to the tombstone 363 * @param tmpFileLock the lock for reading/writing tmp files 364 */ addTombstoneToDropBox( Context ctx, File tombstone, boolean proto, String processName, ReentrantLock tmpFileLock)365 public static void addTombstoneToDropBox( 366 Context ctx, File tombstone, boolean proto, String processName, 367 ReentrantLock tmpFileLock) { 368 final DropBoxManager db = ctx.getSystemService(DropBoxManager.class); 369 if (db == null) { 370 Slog.e(TAG, "Can't log tombstone: DropBoxManager not available"); 371 return; 372 } 373 374 // Check if we should rate limit and abort early if needed. 375 DropboxRateLimiter.RateLimitResult rateLimitResult = 376 sDropboxRateLimiter.shouldRateLimit( 377 proto ? TAG_TOMBSTONE_PROTO_WITH_HEADERS : TAG_TOMBSTONE, processName); 378 if (rateLimitResult.shouldRateLimit()) return; 379 380 HashMap<String, Long> timestamps = readTimestamps(); 381 try { 382 if (proto) { 383 if (recordFileTimestamp(tombstone, timestamps)) { 384 // We need to attach the count indicating the number of dropped dropbox entries 385 // due to rate limiting. Do this by enclosing the proto tombsstone in a 386 // container proto that has the dropped entry count and the proto tombstone as 387 // bytes (to avoid the complexity of reading and writing nested protos). 388 tmpFileLock.lock(); 389 try { 390 addAugmentedProtoToDropbox(tombstone, db, rateLimitResult); 391 } finally { 392 tmpFileLock.unlock(); 393 } 394 } 395 } else { 396 // Add the header indicating how many events have been dropped due to rate limiting. 397 final String headers = getBootHeadersToLogAndUpdate() 398 + rateLimitResult.createHeader(); 399 addFileToDropBox(db, timestamps, headers, tombstone.getPath(), LOG_SIZE, 400 TAG_TOMBSTONE); 401 } 402 } catch (IOException e) { 403 Slog.e(TAG, "Can't log tombstone", e); 404 } 405 writeTimestamps(timestamps); 406 } 407 408 /** 409 * Processes a tombstone file and adds it to the DropBox after filtering and applying 410 * rate limiting. 411 * Filtering removes memory sections from the tombstone proto to reduce size while preserving 412 * critical information. The filtered tombstone is then added to DropBox in both proto 413 * and text formats, with the text format derived from the filtered proto. 414 * Rate limiting is applied as it is the case with other crash types. 415 * 416 * @param ctx Context 417 * @param tombstone path to the tombstone 418 * @param processName the name of the process corresponding to the tombstone 419 * @param tmpFileLock the lock for reading/writing tmp files 420 */ filterAndAddTombstoneToDropBox( Context ctx, File tombstone, String processName, ReentrantLock tmpFileLock)421 public static void filterAndAddTombstoneToDropBox( 422 Context ctx, File tombstone, String processName, ReentrantLock tmpFileLock) { 423 final DropBoxManager db = ctx.getSystemService(DropBoxManager.class); 424 if (db == null) { 425 Slog.e(TAG, "Can't log tombstone: DropBoxManager not available"); 426 return; 427 } 428 File filteredProto = null; 429 // Check if we should rate limit and abort early if needed. 430 DropboxRateLimiter.RateLimitResult rateLimitResult = 431 sDropboxRateLimiter.shouldRateLimit(TAG_TOMBSTONE_PROTO_WITH_HEADERS, processName); 432 if (rateLimitResult.shouldRateLimit()) return; 433 434 HashMap<String, Long> timestamps = readTimestamps(); 435 try { 436 tmpFileLock.lock(); 437 Slog.i(TAG, "Filtering tombstone file: " + tombstone.getName()); 438 // Create a temporary tombstone without memory sections. 439 filteredProto = createTempTombstoneWithoutMemory(tombstone); 440 Slog.i(TAG, "Generated tombstone file: " + filteredProto.getName()); 441 442 if (recordFileTimestamp(tombstone, timestamps)) { 443 // We need to attach the count indicating the number of dropped dropbox entries 444 // due to rate limiting. Do this by enclosing the proto tombsstone in a 445 // container proto that has the dropped entry count and the proto tombstone as 446 // bytes (to avoid the complexity of reading and writing nested protos). 447 Slog.i(TAG, "Adding tombstone " + filteredProto.getName() + " to dropbox"); 448 addAugmentedProtoToDropbox(filteredProto, db, rateLimitResult); 449 } 450 // Always add the text version of the tombstone to the DropBox, in order to 451 // match the previous behaviour. 452 Slog.i(TAG, "Adding text tombstone version of " + filteredProto.getName() 453 + " to dropbox"); 454 addTextTombstoneFromProtoToDropbox(filteredProto, db, timestamps, rateLimitResult); 455 456 } catch (IOException | ProtoParseException e) { 457 Slog.e(TAG, "Failed to log tombstone '" + tombstone.getName() 458 + "' to DropBox. Error during processing or writing: " + e.getMessage(), e); 459 } finally { 460 if (filteredProto != null) { 461 filteredProto.delete(); 462 } 463 tmpFileLock.unlock(); 464 } 465 writeTimestamps(timestamps); 466 } 467 468 /** 469 * Creates a temporary tombstone file by filtering out memory mapping fields. 470 * This ensures that the unneeded memory mapping data is removed from the tombstone 471 * before adding it to Dropbox 472 * 473 * @param tombstone the original tombstone file to process 474 * @return a temporary file containing the filtered tombstone data 475 * @throws IOException if an I/O error occurs during processing 476 */ createTempTombstoneWithoutMemory(File tombstone)477 private static File createTempTombstoneWithoutMemory(File tombstone) throws IOException { 478 // Process the proto tombstone file and write it to a temporary file 479 File tombstoneProto = 480 File.createTempFile(tombstone.getName(), ".pb.tmp", TOMBSTONE_TMP_DIR); 481 ProtoFieldFilter protoFilter = 482 new ProtoFieldFilter(fieldNumber -> fieldNumber != (int) Tombstone.MEMORY_MAPPINGS); 483 484 try (FileInputStream fis = new FileInputStream(tombstone); 485 BufferedInputStream bis = new BufferedInputStream(fis); 486 FileOutputStream fos = new FileOutputStream(tombstoneProto); 487 BufferedOutputStream bos = new BufferedOutputStream(fos)) { 488 protoFilter.filter(bis, bos); 489 return tombstoneProto; 490 } 491 } 492 addTextTombstoneFromProtoToDropbox(File tombstone, DropBoxManager db, HashMap<String, Long> timestamps, DropboxRateLimiter.RateLimitResult rateLimitResult)493 private static void addTextTombstoneFromProtoToDropbox(File tombstone, DropBoxManager db, 494 HashMap<String, Long> timestamps, DropboxRateLimiter.RateLimitResult rateLimitResult) { 495 File tombstoneTextFile = null; 496 497 try { 498 tombstoneTextFile = File.createTempFile(tombstone.getName(), 499 ".pb.txt.tmp", TOMBSTONE_TMP_DIR); 500 501 // Create a ProcessBuilder to execute pbtombstone 502 ProcessBuilder pb = new ProcessBuilder("/system/bin/pbtombstone", tombstone.getPath()); 503 pb.redirectOutput(tombstoneTextFile); 504 Process process = pb.start(); 505 506 // Wait 10 seconds for the process to complete 507 if (!process.waitFor(10, TimeUnit.SECONDS)) { 508 Slog.e(TAG, "pbtombstone timed out"); 509 process.destroyForcibly(); 510 return; 511 } 512 513 int exitCode = process.exitValue(); 514 if (exitCode != 0) { 515 Slog.e(TAG, "pbtombstone failed with exit code " + exitCode); 516 } else { 517 final String headers = getBootHeadersToLogAndUpdate() 518 + rateLimitResult.createHeader(); 519 addFileToDropBox(db, timestamps, headers, tombstoneTextFile.getPath(), LOG_SIZE, 520 TAG_TOMBSTONE); 521 } 522 } catch (IOException | InterruptedException e) { 523 Slog.e(TAG, "Failed to process tombstone with pbtombstone", e); 524 } finally { 525 if (tombstoneTextFile != null) { 526 tombstoneTextFile.delete(); 527 } 528 } 529 } 530 addAugmentedProtoToDropbox( File tombstone, DropBoxManager db, DropboxRateLimiter.RateLimitResult rateLimitResult)531 private static void addAugmentedProtoToDropbox( 532 File tombstone, DropBoxManager db, 533 DropboxRateLimiter.RateLimitResult rateLimitResult) throws IOException { 534 // Do not add proto files larger than 20Mb to DropBox as they can cause OOMs when 535 // processing large tombstones. The text tombstone is still added to DropBox. 536 if (tombstone.length() > MAX_TOMBSTONE_SIZE_BYTES) { 537 Slog.w(TAG, "Tombstone too large to add to DropBox: " + tombstone.toPath()); 538 return; 539 } 540 541 // Read the proto tombstone file as bytes. 542 // Previously used Files.readAllBytes() which internally creates a ThreadLocal BufferCache 543 // via ChannelInputStream that isn't properly released. Switched to 544 // FileInputStream.transferTo() which avoids the NIO channels completely, 545 // preventing the memory leak while maintaining the same functionality. 546 final byte[] tombstoneBytes; 547 try (FileInputStream fis = new FileInputStream(tombstone); 548 ByteArrayOutputStream baos = new ByteArrayOutputStream()) { 549 fis.transferTo(baos); 550 tombstoneBytes = baos.toByteArray(); 551 } 552 final File tombstoneProtoWithHeaders = File.createTempFile( 553 tombstone.getName(), ".tmp", TOMBSTONE_TMP_DIR); 554 Files.setPosixFilePermissions( 555 tombstoneProtoWithHeaders.toPath(), 556 PosixFilePermissions.fromString("rw-rw----")); 557 558 // Write the new proto container proto with headers. 559 try (ParcelFileDescriptor pfd = ParcelFileDescriptor.open( 560 tombstoneProtoWithHeaders, MODE_READ_WRITE)) { 561 ProtoOutputStream protoStream = 562 new ProtoOutputStream(pfd.getFileDescriptor()); 563 protoStream.write(TombstoneWithHeadersProto.TOMBSTONE, tombstoneBytes); 564 protoStream.write( 565 TombstoneWithHeadersProto.DROPPED_COUNT, 566 rateLimitResult.droppedCountSinceRateLimitActivated()); 567 protoStream.flush(); 568 569 // Add the proto to dropbox. 570 db.addFile(TAG_TOMBSTONE_PROTO_WITH_HEADERS, tombstoneProtoWithHeaders, 0); 571 } catch (FileNotFoundException ex) { 572 Slog.e(TAG, "failed to open for write: " + tombstoneProtoWithHeaders, ex); 573 throw ex; 574 } catch (IOException ex) { 575 Slog.e(TAG, "IO exception during write: " + tombstoneProtoWithHeaders, ex); 576 } finally { 577 // Remove the temporary file and unlock the lock. 578 if (tombstoneProtoWithHeaders != null) { 579 tombstoneProtoWithHeaders.delete(); 580 } 581 } 582 } 583 addLastkToDropBox( DropBoxManager db, HashMap<String, Long> timestamps, String headers, String footers, String filename, int maxSize, String tag)584 private static void addLastkToDropBox( 585 DropBoxManager db, HashMap<String, Long> timestamps, 586 String headers, String footers, String filename, int maxSize, 587 String tag) throws IOException { 588 int extraSize = headers.length() + TAG_TRUNCATED.length() + footers.length(); 589 // GMSCore will do 2nd truncation to be 192KiB 590 // LASTK_LOG_SIZE + extraSize must be less than GMSCORE_LASTK_LOG_SIZE 591 if (LASTK_LOG_SIZE + extraSize > GMSCORE_LASTK_LOG_SIZE) { 592 if (GMSCORE_LASTK_LOG_SIZE > extraSize) { 593 maxSize = -(GMSCORE_LASTK_LOG_SIZE - extraSize); 594 } else { 595 maxSize = 0; 596 } 597 } 598 addFileWithFootersToDropBox(db, timestamps, headers, footers, filename, maxSize, tag); 599 } 600 addFileToDropBox( DropBoxManager db, HashMap<String, Long> timestamps, String headers, String filename, int maxSize, String tag)601 private static void addFileToDropBox( 602 DropBoxManager db, HashMap<String, Long> timestamps, 603 String headers, String filename, int maxSize, String tag) throws IOException { 604 addFileWithFootersToDropBox(db, timestamps, headers, "", filename, maxSize, tag); 605 } 606 addFileWithFootersToDropBox( DropBoxManager db, HashMap<String, Long> timestamps, String headers, String footers, String filename, int maxSize, String tag)607 private static void addFileWithFootersToDropBox( 608 DropBoxManager db, HashMap<String, Long> timestamps, 609 String headers, String footers, String filename, int maxSize, 610 String tag) throws IOException { 611 if (db == null || !db.isTagEnabled(tag)) return; // Logging disabled 612 613 File file = new File(filename); 614 if (!recordFileTimestamp(file, timestamps)) { 615 return; 616 } 617 618 String fileContents = FileUtils.readTextFile(file, maxSize, TAG_TRUNCATED); 619 String text = headers + fileContents + footers; 620 // Create an additional report for system server native crashes, with a special tag. 621 if (tag.equals(TAG_TOMBSTONE) && fileContents.contains(">>> system_server <<<")) { 622 addTextToDropBox(db, "system_server_native_crash", text, filename, maxSize); 623 } 624 if (tag.equals(TAG_TOMBSTONE)) { 625 FrameworkStatsLog.write(FrameworkStatsLog.TOMB_STONE_OCCURRED); 626 } 627 addTextToDropBox(db, tag, text, filename, maxSize); 628 } 629 recordFileTimestamp(File file, HashMap<String, Long> timestamps)630 private static boolean recordFileTimestamp(File file, HashMap<String, Long> timestamps) { 631 final long fileTime = file.lastModified(); 632 if (fileTime <= 0) return false; // File does not exist 633 634 final String filename = file.getPath(); 635 if (timestamps.containsKey(filename) && timestamps.get(filename) == fileTime) { 636 return false; // Already logged this particular file 637 } 638 639 timestamps.put(filename, fileTime); 640 return true; 641 } 642 addTextToDropBox(DropBoxManager db, String tag, String text, String filename, int maxSize)643 private static void addTextToDropBox(DropBoxManager db, String tag, String text, 644 String filename, int maxSize) { 645 Slog.i(TAG, "Copying " + filename + " to DropBox (" + tag + ")"); 646 db.addText(tag, text); 647 EventLog.writeEvent(DropboxLogTags.DROPBOX_FILE_COPY, filename, maxSize, tag); 648 } 649 addAuditErrorsToDropBox(DropBoxManager db, HashMap<String, Long> timestamps, String headers, int maxSize, String tag)650 private static void addAuditErrorsToDropBox(DropBoxManager db, 651 HashMap<String, Long> timestamps, String headers, int maxSize, String tag) 652 throws IOException { 653 if (db == null || !db.isTagEnabled(tag)) return; // Logging disabled 654 Slog.i(TAG, "Copying audit failures to DropBox"); 655 656 File file = new File("/proc/last_kmsg"); 657 long fileTime = file.lastModified(); 658 if (fileTime <= 0) { 659 file = new File("/sys/fs/pstore/console-ramoops"); 660 fileTime = file.lastModified(); 661 if (fileTime <= 0) { 662 file = new File("/sys/fs/pstore/console-ramoops-0"); 663 fileTime = file.lastModified(); 664 } 665 } 666 667 if (fileTime <= 0) return; // File does not exist 668 669 if (timestamps.containsKey(tag) && timestamps.get(tag) == fileTime) { 670 return; // Already logged this particular file 671 } 672 673 timestamps.put(tag, fileTime); 674 675 String log = FileUtils.readTextFile(file, maxSize, TAG_TRUNCATED); 676 StringBuilder sb = new StringBuilder(); 677 for (String line : log.split("\n")) { 678 if (line.contains("audit")) { 679 sb.append(line + "\n"); 680 } 681 } 682 Slog.i(TAG, "Copied " + sb.toString().length() + " worth of audits to DropBox"); 683 db.addText(tag, headers + sb.toString()); 684 } 685 addFsckErrorsToDropBoxAndLogFsStat(DropBoxManager db, HashMap<String, Long> timestamps, String headers, int maxSize, String tag)686 private static void addFsckErrorsToDropBoxAndLogFsStat(DropBoxManager db, 687 HashMap<String, Long> timestamps, String headers, int maxSize, String tag) 688 throws IOException { 689 boolean uploadEnabled = true; 690 if (db == null || !db.isTagEnabled(tag)) { 691 uploadEnabled = false; 692 } 693 boolean uploadNeeded = false; 694 Slog.i(TAG, "Checking for fsck errors"); 695 696 File file = new File("/dev/fscklogs/log"); 697 long fileTime = file.lastModified(); 698 if (fileTime <= 0) return; // File does not exist 699 700 String log = FileUtils.readTextFile(file, maxSize, TAG_TRUNCATED); 701 Pattern pattern = Pattern.compile(FS_STAT_PATTERN); 702 String lines[] = log.split("\n"); 703 int lineNumber = 0; 704 int lastFsStatLineNumber = 0; 705 for (String line : lines) { // should check all lines 706 if (line.contains(E2FSCK_FS_MODIFIED) || line.contains(F2FS_FSCK_FS_MODIFIED)) { 707 uploadNeeded = true; 708 } else if (line.contains("fs_stat")) { 709 Matcher matcher = pattern.matcher(line); 710 if (matcher.find()) { 711 handleFsckFsStat(matcher, lines, lastFsStatLineNumber, lineNumber); 712 lastFsStatLineNumber = lineNumber; 713 } else { 714 Slog.w(TAG, "cannot parse fs_stat:" + line); 715 } 716 } 717 lineNumber++; 718 } 719 720 if (uploadEnabled && uploadNeeded) { 721 addFileToDropBox(db, timestamps, headers, "/dev/fscklogs/log", maxSize, tag); 722 } 723 724 // Rename the file so we don't re-upload if the runtime restarts. 725 File pfile = new File("/dev/fscklogs/fsck"); 726 file.renameTo(pfile); 727 } 728 logFsMountTime()729 private static void logFsMountTime() { 730 for (String propPostfix : MOUNT_DURATION_PROPS_POSTFIX) { 731 int duration = SystemProperties.getInt("ro.boottime.init.mount_all." + propPostfix, 0); 732 if (duration != 0) { 733 int eventType; 734 switch (propPostfix) { 735 case "early": 736 eventType = 737 FrameworkStatsLog 738 .BOOT_TIME_EVENT_DURATION__EVENT__MOUNT_EARLY_DURATION; 739 break; 740 case "default": 741 eventType = 742 FrameworkStatsLog 743 .BOOT_TIME_EVENT_DURATION__EVENT__MOUNT_DEFAULT_DURATION; 744 break; 745 case "late": 746 eventType = 747 FrameworkStatsLog 748 .BOOT_TIME_EVENT_DURATION__EVENT__MOUNT_LATE_DURATION; 749 break; 750 default: 751 continue; 752 } 753 FrameworkStatsLog.write(FrameworkStatsLog.BOOT_TIME_EVENT_DURATION_REPORTED, 754 eventType, duration); 755 } 756 } 757 } 758 759 // TODO b/64815357 Move to bootstat.cpp and log AbsoluteRebootTime logSystemServerShutdownTimeMetrics()760 private static void logSystemServerShutdownTimeMetrics() { 761 File metricsFile = new File(SHUTDOWN_METRICS_FILE); 762 String metricsStr = null; 763 if (metricsFile.exists()) { 764 try { 765 metricsStr = FileUtils.readTextFile(metricsFile, 0, null); 766 } catch (IOException e) { 767 Slog.e(TAG, "Problem reading " + metricsFile, e); 768 } 769 } 770 if (!TextUtils.isEmpty(metricsStr)) { 771 String reboot = null; 772 String reason = null; 773 String start_time = null; 774 String duration = null; 775 String[] array = metricsStr.split(","); 776 for (String keyValueStr : array) { 777 String[] keyValue = keyValueStr.split(":"); 778 if (keyValue.length != 2) { 779 Slog.e(TAG, "Wrong format of shutdown metrics - " + metricsStr); 780 continue; 781 } 782 // Ignore keys that are not indended for tron 783 if (keyValue[0].startsWith(SHUTDOWN_TRON_METRICS_PREFIX)) { 784 logTronShutdownMetric(keyValue[0], keyValue[1]); 785 if (keyValue[0].equals(METRIC_SYSTEM_SERVER)) { 786 duration = keyValue[1]; 787 } 788 } 789 if (keyValue[0].equals("reboot")) { 790 reboot = keyValue[1]; 791 } else if (keyValue[0].equals("reason")) { 792 reason = keyValue[1]; 793 } else if (keyValue[0].equals(METRIC_SHUTDOWN_TIME_START)) { 794 start_time = keyValue[1]; 795 } 796 } 797 logStatsdShutdownAtom(reboot, reason, start_time, duration); 798 } 799 metricsFile.delete(); 800 } 801 logTronShutdownMetric(String metricName, String valueStr)802 private static void logTronShutdownMetric(String metricName, String valueStr) { 803 int value; 804 try { 805 value = Integer.parseInt(valueStr); 806 } catch (NumberFormatException e) { 807 Slog.e(TAG, "Cannot parse metric " + metricName + " int value - " + valueStr); 808 return; 809 } 810 if (value >= 0) { 811 MetricsLogger.histogram(null, metricName, value); 812 } 813 } 814 logStatsdShutdownAtom( String rebootStr, String reasonStr, String startStr, String durationStr)815 private static void logStatsdShutdownAtom( 816 String rebootStr, String reasonStr, String startStr, String durationStr) { 817 boolean reboot = false; 818 String reason = "<EMPTY>"; 819 long start = 0; 820 long duration = 0; 821 822 if (rebootStr != null) { 823 if (rebootStr.equals("y")) { 824 reboot = true; 825 } else if (!rebootStr.equals("n")) { 826 Slog.e(TAG, "Unexpected value for reboot : " + rebootStr); 827 } 828 } else { 829 Slog.e(TAG, "No value received for reboot"); 830 } 831 832 if (reasonStr != null) { 833 reason = reasonStr; 834 } else { 835 Slog.e(TAG, "No value received for shutdown reason"); 836 } 837 838 if (startStr != null) { 839 try { 840 start = Long.parseLong(startStr); 841 } catch (NumberFormatException e) { 842 Slog.e(TAG, "Cannot parse shutdown start time: " + startStr); 843 } 844 } else { 845 Slog.e(TAG, "No value received for shutdown start time"); 846 } 847 848 if (durationStr != null) { 849 try { 850 duration = Long.parseLong(durationStr); 851 } catch (NumberFormatException e) { 852 Slog.e(TAG, "Cannot parse shutdown duration: " + startStr); 853 } 854 } else { 855 Slog.e(TAG, "No value received for shutdown duration"); 856 } 857 858 FrameworkStatsLog.write(FrameworkStatsLog.SHUTDOWN_SEQUENCE_REPORTED, reboot, reason, start, 859 duration); 860 } 861 logFsShutdownTime()862 private static void logFsShutdownTime() { 863 File f = null; 864 for (String fileName : LAST_KMSG_FILES) { 865 File file = new File(fileName); 866 if (!file.exists()) continue; 867 f = file; 868 break; 869 } 870 if (f == null) { // no last_kmsg 871 return; 872 } 873 874 final int maxReadSize = 16*1024; 875 // last_kmsg can be very big, so only parse the last part 876 String lines; 877 try { 878 lines = FileUtils.readTextFile(f, -maxReadSize, null); 879 } catch (IOException e) { 880 Slog.w(TAG, "cannot read last msg", e); 881 return; 882 } 883 Pattern pattern = Pattern.compile(LAST_SHUTDOWN_TIME_PATTERN, Pattern.MULTILINE); 884 Matcher matcher = pattern.matcher(lines); 885 if (matcher.find()) { 886 FrameworkStatsLog.write(FrameworkStatsLog.BOOT_TIME_EVENT_DURATION_REPORTED, 887 FrameworkStatsLog.BOOT_TIME_EVENT_DURATION__EVENT__SHUTDOWN_DURATION, 888 Integer.parseInt(matcher.group(1))); 889 FrameworkStatsLog.write(FrameworkStatsLog.BOOT_TIME_EVENT_ERROR_CODE_REPORTED, 890 FrameworkStatsLog.BOOT_TIME_EVENT_ERROR_CODE__EVENT__SHUTDOWN_UMOUNT_STAT, 891 Integer.parseInt(matcher.group(2))); 892 Slog.i(TAG, "boot_fs_shutdown," + matcher.group(1) + "," + matcher.group(2)); 893 } else { // not found 894 // This can happen when a device has too much kernel log after file system unmount 895 // ,exceeding maxReadSize. And having that much kernel logging can affect overall 896 // performance as well. So it is better to fix the kernel to reduce the amount of log. 897 FrameworkStatsLog.write(FrameworkStatsLog.BOOT_TIME_EVENT_ERROR_CODE_REPORTED, 898 FrameworkStatsLog.BOOT_TIME_EVENT_ERROR_CODE__EVENT__SHUTDOWN_UMOUNT_STAT, 899 UMOUNT_STATUS_NOT_AVAILABLE); 900 Slog.w(TAG, "boot_fs_shutdown, string not found"); 901 } 902 } 903 904 /** 905 * Fix fs_stat from e2fsck. 906 * For now, only handle the case of quota warning caused by tree optimization. Clear fs fix 907 * flag (=0x400) caused by that. 908 * 909 * @param partition partition name 910 * @param statOrg original stat reported from e2fsck log 911 * @param lines e2fsck logs broken down into lines 912 * @param startLineNumber start line to parse 913 * @param endLineNumber end line. exclusive. 914 * @return updated fs_stat. For tree optimization, will clear bit 0x400. 915 */ 916 @VisibleForTesting fixFsckFsStat(String partition, int statOrg, String[] lines, int startLineNumber, int endLineNumber)917 public static int fixFsckFsStat(String partition, int statOrg, String[] lines, 918 int startLineNumber, int endLineNumber) { 919 int stat = statOrg; 920 if ((stat & FS_STAT_FSCK_FS_FIXED) != 0) { 921 // fs was fixed. should check if quota warning was caused by tree optimization. 922 // This is not a real fix but optimization, so should not be counted as a fs fix. 923 Pattern passPattern = Pattern.compile(FSCK_PASS_PATTERN); 924 Pattern treeOptPattern = Pattern.compile(FSCK_TREE_OPTIMIZATION_PATTERN); 925 String currentPass = ""; 926 boolean foundTreeOptimization = false; 927 boolean foundQuotaFix = false; 928 boolean foundTimestampAdjustment = false; 929 boolean foundOtherFix = false; 930 String otherFixLine = null; 931 for (int i = startLineNumber; i < endLineNumber; i++) { 932 String line = lines[i]; 933 if (line.contains(E2FSCK_FS_MODIFIED) 934 || line.contains(F2FS_FSCK_FS_MODIFIED)) { // no need to parse above this 935 break; 936 } else if (line.startsWith("Pass ")) { 937 Matcher matcher = passPattern.matcher(line); 938 if (matcher.find()) { 939 currentPass = matcher.group(1); 940 } 941 } else if (line.startsWith("Inode ")) { 942 Matcher matcher = treeOptPattern.matcher(line); 943 if (matcher.find() && currentPass.equals("1")) { 944 foundTreeOptimization = true; 945 Slog.i(TAG, "fs_stat, partition:" + partition + " found tree optimization:" 946 + line); 947 } else { 948 foundOtherFix = true; 949 otherFixLine = line; 950 break; 951 } 952 } else if (line.startsWith("[QUOTA WARNING]") && currentPass.equals("5")) { 953 Slog.i(TAG, "fs_stat, partition:" + partition + " found quota warning:" 954 + line); 955 foundQuotaFix = true; 956 if (!foundTreeOptimization) { // only quota warning, this is real fix. 957 otherFixLine = line; 958 break; 959 } 960 } else if (line.startsWith("Update quota info") && currentPass.equals("5")) { 961 // follows "[QUOTA WARNING]", ignore 962 } else if (line.startsWith("Timestamp(s) on inode") 963 && line.contains("beyond 2310-04-04 are likely pre-1970") 964 && currentPass.equals("1")) { 965 Slog.i(TAG, "fs_stat, partition:" + partition + " found timestamp adjustment:" 966 + line); 967 // followed by next line, "Fix? yes" 968 if (lines[i + 1].contains("Fix? yes")) { 969 i++; 970 } 971 foundTimestampAdjustment = true; 972 } else { 973 line = line.trim(); 974 // ignore empty msg or any msg before Pass 1 975 if (!line.isEmpty() && !currentPass.isEmpty()) { 976 foundOtherFix = true; 977 otherFixLine = line; 978 break; 979 } 980 } 981 } 982 if (foundOtherFix) { 983 if (otherFixLine != null) { 984 Slog.i(TAG, "fs_stat, partition:" + partition + " fix:" + otherFixLine); 985 } 986 } else if (foundQuotaFix && !foundTreeOptimization) { 987 Slog.i(TAG, "fs_stat, got quota fix without tree optimization, partition:" + 988 partition); 989 } else if ((foundTreeOptimization && foundQuotaFix) || foundTimestampAdjustment) { 990 // not a real fix, so clear it. 991 Slog.i(TAG, "fs_stat, partition:" + partition + " fix ignored"); 992 stat &= ~FS_STAT_FSCK_FS_FIXED; 993 } 994 } 995 return stat; 996 } 997 handleFsckFsStat(Matcher match, String[] lines, int startLineNumber, int endLineNumber)998 private static void handleFsckFsStat(Matcher match, String[] lines, int startLineNumber, 999 int endLineNumber) { 1000 String partition = match.group(1); 1001 int stat; 1002 try { 1003 stat = Integer.decode(match.group(2)); 1004 } catch (NumberFormatException e) { 1005 Slog.w(TAG, "cannot parse fs_stat: partition:" + partition + " stat:" + match.group(2)); 1006 return; 1007 } 1008 stat = fixFsckFsStat(partition, stat, lines, startLineNumber, endLineNumber); 1009 if ("userdata".equals(partition) || "data".equals(partition)) { 1010 FrameworkStatsLog.write(FrameworkStatsLog.BOOT_TIME_EVENT_ERROR_CODE_REPORTED, 1011 FrameworkStatsLog 1012 .BOOT_TIME_EVENT_ERROR_CODE__EVENT__FS_MGR_FS_STAT_DATA_PARTITION, 1013 stat); 1014 } 1015 Slog.i(TAG, "fs_stat, partition:" + partition + " stat:0x" + Integer.toHexString(stat)); 1016 } 1017 readTimestamps()1018 private static HashMap<String, Long> readTimestamps() { 1019 synchronized (sFile) { 1020 HashMap<String, Long> timestamps = new HashMap<String, Long>(); 1021 boolean success = false; 1022 try (final FileInputStream stream = sFile.openRead()) { 1023 TypedXmlPullParser parser = Xml.resolvePullParser(stream); 1024 1025 int type; 1026 while ((type = parser.next()) != XmlPullParser.START_TAG 1027 && type != XmlPullParser.END_DOCUMENT) { 1028 ; 1029 } 1030 1031 if (type != XmlPullParser.START_TAG) { 1032 throw new IllegalStateException("no start tag found"); 1033 } 1034 1035 int outerDepth = parser.getDepth(); // Skip the outer <log-files> tag. 1036 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 1037 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { 1038 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { 1039 continue; 1040 } 1041 1042 String tagName = parser.getName(); 1043 if (tagName.equals("log")) { 1044 final String filename = parser.getAttributeValue(null, "filename"); 1045 final long timestamp = parser.getAttributeLong(null, "timestamp"); 1046 timestamps.put(filename, timestamp); 1047 } else { 1048 Slog.w(TAG, "Unknown tag: " + parser.getName()); 1049 XmlUtils.skipCurrentTag(parser); 1050 } 1051 } 1052 success = true; 1053 } catch (FileNotFoundException e) { 1054 Slog.i(TAG, "No existing last log timestamp file " + sFile.getBaseFile() + 1055 "; starting empty"); 1056 } catch (IOException e) { 1057 Slog.w(TAG, "Failed parsing " + e); 1058 } catch (IllegalStateException e) { 1059 Slog.w(TAG, "Failed parsing " + e); 1060 } catch (NullPointerException e) { 1061 Slog.w(TAG, "Failed parsing " + e); 1062 } catch (XmlPullParserException e) { 1063 Slog.w(TAG, "Failed parsing " + e); 1064 } finally { 1065 if (!success) { 1066 timestamps.clear(); 1067 } 1068 } 1069 return timestamps; 1070 } 1071 } 1072 writeTimestamps(HashMap<String, Long> timestamps)1073 private static void writeTimestamps(HashMap<String, Long> timestamps) { 1074 synchronized (sFile) { 1075 final FileOutputStream stream; 1076 try { 1077 stream = sFile.startWrite(); 1078 } catch (IOException e) { 1079 Slog.w(TAG, "Failed to write timestamp file: " + e); 1080 return; 1081 } 1082 1083 try { 1084 TypedXmlSerializer out = Xml.resolveSerializer(stream); 1085 out.startDocument(null, true); 1086 out.startTag(null, "log-files"); 1087 1088 Iterator<String> itor = timestamps.keySet().iterator(); 1089 while (itor.hasNext()) { 1090 String filename = itor.next(); 1091 out.startTag(null, "log"); 1092 out.attribute(null, "filename", filename); 1093 out.attributeLong(null, "timestamp", timestamps.get(filename)); 1094 out.endTag(null, "log"); 1095 } 1096 1097 out.endTag(null, "log-files"); 1098 out.endDocument(); 1099 1100 sFile.finishWrite(stream); 1101 } catch (IOException e) { 1102 Slog.w(TAG, "Failed to write timestamp file, using the backup: " + e); 1103 sFile.failWrite(stream); 1104 } 1105 } 1106 } 1107 } 1108