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
17package com.android.server;
18
19import android.content.BroadcastReceiver;
20import android.content.Context;
21import android.content.Intent;
22import android.content.SharedPreferences;
23import android.os.Build;
24import android.os.DropBoxManager;
25import android.os.FileObserver;
26import android.os.FileUtils;
27import android.os.RecoverySystem;
28import android.os.SystemProperties;
29import android.provider.Downloads;
30import android.util.Slog;
31
32import java.io.File;
33import java.io.IOException;
34
35/**
36 * Performs a number of miscellaneous, non-system-critical actions
37 * after the system has finished booting.
38 */
39public class BootReceiver extends BroadcastReceiver {
40    private static final String TAG = "BootReceiver";
41
42    // Maximum size of a logged event (files get truncated if they're longer).
43    // Give userdebug builds a larger max to capture extra debug, esp. for last_kmsg.
44    private static final int LOG_SIZE =
45        SystemProperties.getInt("ro.debuggable", 0) == 1 ? 98304 : 65536;
46
47    private static final File TOMBSTONE_DIR = new File("/data/tombstones");
48
49    // The pre-froyo package and class of the system updater, which
50    // ran in the system process.  We need to remove its packages here
51    // in order to clean up after a pre-froyo-to-froyo update.
52    private static final String OLD_UPDATER_PACKAGE =
53        "com.google.android.systemupdater";
54    private static final String OLD_UPDATER_CLASS =
55        "com.google.android.systemupdater.SystemUpdateReceiver";
56
57    // Keep a reference to the observer so the finalizer doesn't disable it.
58    private static FileObserver sTombstoneObserver = null;
59
60    @Override
61    public void onReceive(final Context context, Intent intent) {
62        // Log boot events in the background to avoid blocking the main thread with I/O
63        new Thread() {
64            @Override
65            public void run() {
66                try {
67                    logBootEvents(context);
68                } catch (Exception e) {
69                    Slog.e(TAG, "Can't log boot events", e);
70                }
71                try {
72                    removeOldUpdatePackages(context);
73                } catch (Exception e) {
74                    Slog.e(TAG, "Can't remove old update packages", e);
75                }
76
77            }
78        }.start();
79    }
80
81    private void removeOldUpdatePackages(Context context) {
82        Downloads.removeAllDownloadsByPackage(context, OLD_UPDATER_PACKAGE, OLD_UPDATER_CLASS);
83    }
84
85    private void logBootEvents(Context ctx) throws IOException {
86        final DropBoxManager db = (DropBoxManager) ctx.getSystemService(Context.DROPBOX_SERVICE);
87        final SharedPreferences prefs = ctx.getSharedPreferences("log_files", Context.MODE_PRIVATE);
88        final String headers = new StringBuilder(512)
89            .append("Build: ").append(Build.FINGERPRINT).append("\n")
90            .append("Hardware: ").append(Build.BOARD).append("\n")
91            .append("Revision: ")
92            .append(SystemProperties.get("ro.revision", "")).append("\n")
93            .append("Bootloader: ").append(Build.BOOTLOADER).append("\n")
94            .append("Radio: ").append(Build.RADIO).append("\n")
95            .append("Kernel: ")
96            .append(FileUtils.readTextFile(new File("/proc/version"), 1024, "...\n"))
97            .append("\n").toString();
98
99        String recovery = RecoverySystem.handleAftermath();
100        if (recovery != null && db != null) {
101            db.addText("SYSTEM_RECOVERY_LOG", headers + recovery);
102        }
103
104        if (SystemProperties.getLong("ro.runtime.firstboot", 0) == 0) {
105            String now = Long.toString(System.currentTimeMillis());
106            SystemProperties.set("ro.runtime.firstboot", now);
107            if (db != null) db.addText("SYSTEM_BOOT", headers);
108
109            // Negative sizes mean to take the *tail* of the file (see FileUtils.readTextFile())
110            addFileToDropBox(db, prefs, headers, "/proc/last_kmsg",
111                    -LOG_SIZE, "SYSTEM_LAST_KMSG");
112            addFileToDropBox(db, prefs, headers, "/cache/recovery/log",
113                    -LOG_SIZE, "SYSTEM_RECOVERY_LOG");
114            addFileToDropBox(db, prefs, headers, "/data/dontpanic/apanic_console",
115                    -LOG_SIZE, "APANIC_CONSOLE");
116            addFileToDropBox(db, prefs, headers, "/data/dontpanic/apanic_threads",
117                    -LOG_SIZE, "APANIC_THREADS");
118        } else {
119            if (db != null) db.addText("SYSTEM_RESTART", headers);
120        }
121
122        // Scan existing tombstones (in case any new ones appeared)
123        File[] tombstoneFiles = TOMBSTONE_DIR.listFiles();
124        for (int i = 0; tombstoneFiles != null && i < tombstoneFiles.length; i++) {
125            addFileToDropBox(db, prefs, headers, tombstoneFiles[i].getPath(),
126                    LOG_SIZE, "SYSTEM_TOMBSTONE");
127        }
128
129        // Start watching for new tombstone files; will record them as they occur.
130        // This gets registered with the singleton file observer thread.
131        sTombstoneObserver = new FileObserver(TOMBSTONE_DIR.getPath(), FileObserver.CLOSE_WRITE) {
132            @Override
133            public void onEvent(int event, String path) {
134                try {
135                    String filename = new File(TOMBSTONE_DIR, path).getPath();
136                    addFileToDropBox(db, prefs, headers, filename, LOG_SIZE, "SYSTEM_TOMBSTONE");
137                } catch (IOException e) {
138                    Slog.e(TAG, "Can't log tombstone", e);
139                }
140            }
141        };
142
143        sTombstoneObserver.startWatching();
144    }
145
146    private static void addFileToDropBox(
147            DropBoxManager db, SharedPreferences prefs,
148            String headers, String filename, int maxSize, String tag) throws IOException {
149        if (db == null || !db.isTagEnabled(tag)) return;  // Logging disabled
150
151        File file = new File(filename);
152        long fileTime = file.lastModified();
153        if (fileTime <= 0) return;  // File does not exist
154
155        if (prefs != null) {
156            long lastTime = prefs.getLong(filename, 0);
157            if (lastTime == fileTime) return;  // Already logged this particular file
158            // TODO: move all these SharedPreferences Editor commits
159            // outside this function to the end of logBootEvents
160            prefs.edit().putLong(filename, fileTime).apply();
161        }
162
163        Slog.i(TAG, "Copying " + filename + " to DropBox (" + tag + ")");
164        db.addText(tag, headers + FileUtils.readTextFile(file, maxSize, "[[TRUNCATED]]\n"));
165    }
166}
167