• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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