1 /* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server; 18 19 import android.content.BroadcastReceiver; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.SharedPreferences; 23 import android.content.pm.IPackageManager; 24 import android.os.Build; 25 import android.os.DropBoxManager; 26 import android.os.FileObserver; 27 import android.os.FileUtils; 28 import android.os.RecoverySystem; 29 import android.os.RemoteException; 30 import android.os.ServiceManager; 31 import android.os.SystemProperties; 32 import android.provider.Downloads; 33 import android.util.Slog; 34 35 import java.io.File; 36 import java.io.IOException; 37 38 /** 39 * Performs a number of miscellaneous, non-system-critical actions 40 * after the system has finished booting. 41 */ 42 public class BootReceiver extends BroadcastReceiver { 43 private static final String TAG = "BootReceiver"; 44 45 // Maximum size of a logged event (files get truncated if they're longer). 46 // Give userdebug builds a larger max to capture extra debug, esp. for last_kmsg. 47 private static final int LOG_SIZE = 48 SystemProperties.getInt("ro.debuggable", 0) == 1 ? 98304 : 65536; 49 50 private static final File TOMBSTONE_DIR = new File("/data/tombstones"); 51 52 // The pre-froyo package and class of the system updater, which 53 // ran in the system process. We need to remove its packages here 54 // in order to clean up after a pre-froyo-to-froyo update. 55 private static final String OLD_UPDATER_PACKAGE = 56 "com.google.android.systemupdater"; 57 private static final String OLD_UPDATER_CLASS = 58 "com.google.android.systemupdater.SystemUpdateReceiver"; 59 60 // Keep a reference to the observer so the finalizer doesn't disable it. 61 private static FileObserver sTombstoneObserver = null; 62 63 @Override 64 public void onReceive(final Context context, Intent intent) { 65 // Log boot events in the background to avoid blocking the main thread with I/O 66 new Thread() { 67 @Override 68 public void run() { 69 try { 70 logBootEvents(context); 71 } catch (Exception e) { 72 Slog.e(TAG, "Can't log boot events", e); 73 } 74 try { 75 boolean onlyCore = false; 76 try { 77 onlyCore = IPackageManager.Stub.asInterface(ServiceManager.getService( 78 "package")).isOnlyCoreApps(); 79 } catch (RemoteException e) { 80 } 81 if (!onlyCore) { 82 removeOldUpdatePackages(context); 83 } 84 } catch (Exception e) { 85 Slog.e(TAG, "Can't remove old update packages", e); 86 } 87 88 } 89 }.start(); 90 } 91 92 private void removeOldUpdatePackages(Context context) { 93 Downloads.removeAllDownloadsByPackage(context, OLD_UPDATER_PACKAGE, OLD_UPDATER_CLASS); 94 } 95 96 private void logBootEvents(Context ctx) throws IOException { 97 final DropBoxManager db = (DropBoxManager) ctx.getSystemService(Context.DROPBOX_SERVICE); 98 final SharedPreferences prefs = ctx.getSharedPreferences("log_files", Context.MODE_PRIVATE); 99 final String headers = new StringBuilder(512) 100 .append("Build: ").append(Build.FINGERPRINT).append("\n") 101 .append("Hardware: ").append(Build.BOARD).append("\n") 102 .append("Revision: ") 103 .append(SystemProperties.get("ro.revision", "")).append("\n") 104 .append("Bootloader: ").append(Build.BOOTLOADER).append("\n") 105 .append("Radio: ").append(Build.RADIO).append("\n") 106 .append("Kernel: ") 107 .append(FileUtils.readTextFile(new File("/proc/version"), 1024, "...\n")) 108 .append("\n").toString(); 109 110 String recovery = RecoverySystem.handleAftermath(); 111 if (recovery != null && db != null) { 112 db.addText("SYSTEM_RECOVERY_LOG", headers + recovery); 113 } 114 115 if (SystemProperties.getLong("ro.runtime.firstboot", 0) == 0) { 116 String now = Long.toString(System.currentTimeMillis()); 117 SystemProperties.set("ro.runtime.firstboot", now); 118 if (db != null) db.addText("SYSTEM_BOOT", headers); 119 120 // Negative sizes mean to take the *tail* of the file (see FileUtils.readTextFile()) 121 addFileToDropBox(db, prefs, headers, "/proc/last_kmsg", 122 -LOG_SIZE, "SYSTEM_LAST_KMSG"); 123 addFileToDropBox(db, prefs, headers, "/cache/recovery/log", 124 -LOG_SIZE, "SYSTEM_RECOVERY_LOG"); 125 addFileToDropBox(db, prefs, headers, "/data/dontpanic/apanic_console", 126 -LOG_SIZE, "APANIC_CONSOLE"); 127 addFileToDropBox(db, prefs, headers, "/data/dontpanic/apanic_threads", 128 -LOG_SIZE, "APANIC_THREADS"); 129 addAuditErrorsToDropBox(db, prefs, headers, -LOG_SIZE, "SYSTEM_AUDIT"); 130 addFsckErrorsToDropBox(db, prefs, headers, -LOG_SIZE, "SYSTEM_FSCK"); 131 } else { 132 if (db != null) db.addText("SYSTEM_RESTART", headers); 133 } 134 135 // Scan existing tombstones (in case any new ones appeared) 136 File[] tombstoneFiles = TOMBSTONE_DIR.listFiles(); 137 for (int i = 0; tombstoneFiles != null && i < tombstoneFiles.length; i++) { 138 addFileToDropBox(db, prefs, headers, tombstoneFiles[i].getPath(), 139 LOG_SIZE, "SYSTEM_TOMBSTONE"); 140 } 141 142 // Start watching for new tombstone files; will record them as they occur. 143 // This gets registered with the singleton file observer thread. 144 sTombstoneObserver = new FileObserver(TOMBSTONE_DIR.getPath(), FileObserver.CLOSE_WRITE) { 145 @Override 146 public void onEvent(int event, String path) { 147 try { 148 String filename = new File(TOMBSTONE_DIR, path).getPath(); 149 addFileToDropBox(db, prefs, headers, filename, LOG_SIZE, "SYSTEM_TOMBSTONE"); 150 } catch (IOException e) { 151 Slog.e(TAG, "Can't log tombstone", e); 152 } 153 } 154 }; 155 156 sTombstoneObserver.startWatching(); 157 } 158 159 private static void addFileToDropBox( 160 DropBoxManager db, SharedPreferences prefs, 161 String headers, String filename, int maxSize, String tag) throws IOException { 162 if (db == null || !db.isTagEnabled(tag)) return; // Logging disabled 163 164 File file = new File(filename); 165 long fileTime = file.lastModified(); 166 if (fileTime <= 0) return; // File does not exist 167 168 if (prefs != null) { 169 long lastTime = prefs.getLong(filename, 0); 170 if (lastTime == fileTime) return; // Already logged this particular file 171 // TODO: move all these SharedPreferences Editor commits 172 // outside this function to the end of logBootEvents 173 prefs.edit().putLong(filename, fileTime).apply(); 174 } 175 176 Slog.i(TAG, "Copying " + filename + " to DropBox (" + tag + ")"); 177 db.addText(tag, headers + FileUtils.readTextFile(file, maxSize, "[[TRUNCATED]]\n")); 178 } 179 180 private static void addAuditErrorsToDropBox(DropBoxManager db, SharedPreferences prefs, 181 String headers, int maxSize, String tag) throws IOException { 182 if (db == null || !db.isTagEnabled(tag)) return; // Logging disabled 183 Slog.i(TAG, "Copying audit failures to DropBox"); 184 185 File file = new File("/proc/last_kmsg"); 186 long fileTime = file.lastModified(); 187 if (fileTime <= 0) return; // File does not exist 188 189 if (prefs != null) { 190 long lastTime = prefs.getLong(tag, 0); 191 if (lastTime == fileTime) return; // Already logged this particular file 192 // TODO: move all these SharedPreferences Editor commits 193 // outside this function to the end of logBootEvents 194 prefs.edit().putLong(tag, fileTime).apply(); 195 } 196 197 String log = FileUtils.readTextFile(file, maxSize, "[[TRUNCATED]]\n"); 198 StringBuilder sb = new StringBuilder(); 199 for (String line : log.split("\n")) { 200 if (line.contains("audit")) { 201 sb.append(line + "\n"); 202 } 203 } 204 Slog.i(TAG, "Copied " + sb.toString().length() + " worth of audits to DropBox"); 205 db.addText(tag, headers + sb.toString()); 206 } 207 208 private static void addFsckErrorsToDropBox(DropBoxManager db, SharedPreferences prefs, 209 String headers, int maxSize, String tag) throws IOException { 210 boolean upload_needed = false; 211 if (db == null || !db.isTagEnabled(tag)) return; // Logging disabled 212 Slog.i(TAG, "Checking for fsck errors"); 213 214 File file = new File("/dev/fscklogs/log"); 215 long fileTime = file.lastModified(); 216 if (fileTime <= 0) return; // File does not exist 217 218 String log = FileUtils.readTextFile(file, maxSize, "[[TRUNCATED]]\n"); 219 StringBuilder sb = new StringBuilder(); 220 for (String line : log.split("\n")) { 221 if (line.contains("FILE SYSTEM WAS MODIFIED")) { 222 upload_needed = true; 223 break; 224 } 225 } 226 227 if (upload_needed) { 228 addFileToDropBox(db, prefs, headers, "/dev/fscklogs/log", maxSize, tag); 229 } 230 231 // Remove the file so we don't re-upload if the runtime restarts. 232 file.delete(); 233 } 234 } 235