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.pm.IPackageManager; 23import android.os.Build; 24import android.os.DropBoxManager; 25import android.os.Environment; 26import android.os.FileObserver; 27import android.os.FileUtils; 28import android.os.RecoverySystem; 29import android.os.RemoteException; 30import android.os.ServiceManager; 31import android.os.SystemProperties; 32import android.os.storage.StorageManager; 33import android.provider.Downloads; 34import android.util.AtomicFile; 35import android.util.Slog; 36import android.util.Xml; 37 38import com.android.internal.util.FastXmlSerializer; 39import com.android.internal.util.XmlUtils; 40 41import java.io.File; 42import java.io.FileInputStream; 43import java.io.FileOutputStream; 44import java.io.IOException; 45import java.io.FileNotFoundException; 46import java.nio.charset.StandardCharsets; 47import java.util.HashMap; 48import java.util.Iterator; 49 50import org.xmlpull.v1.XmlPullParser; 51import org.xmlpull.v1.XmlPullParserException; 52import org.xmlpull.v1.XmlSerializer; 53 54/** 55 * Performs a number of miscellaneous, non-system-critical actions 56 * after the system has finished booting. 57 */ 58public class BootReceiver extends BroadcastReceiver { 59 private static final String TAG = "BootReceiver"; 60 61 // Maximum size of a logged event (files get truncated if they're longer). 62 // Give userdebug builds a larger max to capture extra debug, esp. for last_kmsg. 63 private static final int LOG_SIZE = 64 SystemProperties.getInt("ro.debuggable", 0) == 1 ? 98304 : 65536; 65 66 private static final File TOMBSTONE_DIR = new File("/data/tombstones"); 67 68 // The pre-froyo package and class of the system updater, which 69 // ran in the system process. We need to remove its packages here 70 // in order to clean up after a pre-froyo-to-froyo update. 71 private static final String OLD_UPDATER_PACKAGE = 72 "com.google.android.systemupdater"; 73 private static final String OLD_UPDATER_CLASS = 74 "com.google.android.systemupdater.SystemUpdateReceiver"; 75 76 // Keep a reference to the observer so the finalizer doesn't disable it. 77 private static FileObserver sTombstoneObserver = null; 78 79 private static final String LOG_FILES_FILE = "log-files.xml"; 80 private static final AtomicFile sFile = new AtomicFile(new File( 81 Environment.getDataSystemDirectory(), LOG_FILES_FILE)); 82 83 @Override 84 public void onReceive(final Context context, Intent intent) { 85 // Log boot events in the background to avoid blocking the main thread with I/O 86 new Thread() { 87 @Override 88 public void run() { 89 try { 90 logBootEvents(context); 91 } catch (Exception e) { 92 Slog.e(TAG, "Can't log boot events", e); 93 } 94 try { 95 boolean onlyCore = false; 96 try { 97 onlyCore = IPackageManager.Stub.asInterface(ServiceManager.getService( 98 "package")).isOnlyCoreApps(); 99 } catch (RemoteException e) { 100 } 101 if (!onlyCore) { 102 removeOldUpdatePackages(context); 103 } 104 } catch (Exception e) { 105 Slog.e(TAG, "Can't remove old update packages", e); 106 } 107 108 } 109 }.start(); 110 } 111 112 private void removeOldUpdatePackages(Context context) { 113 Downloads.removeAllDownloadsByPackage(context, OLD_UPDATER_PACKAGE, OLD_UPDATER_CLASS); 114 } 115 116 private void logBootEvents(Context ctx) throws IOException { 117 final DropBoxManager db = (DropBoxManager) ctx.getSystemService(Context.DROPBOX_SERVICE); 118 final String headers = new StringBuilder(512) 119 .append("Build: ").append(Build.FINGERPRINT).append("\n") 120 .append("Hardware: ").append(Build.BOARD).append("\n") 121 .append("Revision: ") 122 .append(SystemProperties.get("ro.revision", "")).append("\n") 123 .append("Bootloader: ").append(Build.BOOTLOADER).append("\n") 124 .append("Radio: ").append(Build.RADIO).append("\n") 125 .append("Kernel: ") 126 .append(FileUtils.readTextFile(new File("/proc/version"), 1024, "...\n")) 127 .append("\n").toString(); 128 final String bootReason = SystemProperties.get("ro.boot.bootreason", null); 129 130 String recovery = RecoverySystem.handleAftermath(ctx); 131 if (recovery != null && db != null) { 132 db.addText("SYSTEM_RECOVERY_LOG", headers + recovery); 133 } 134 135 String lastKmsgFooter = ""; 136 if (bootReason != null) { 137 lastKmsgFooter = new StringBuilder(512) 138 .append("\n") 139 .append("Boot info:\n") 140 .append("Last boot reason: ").append(bootReason).append("\n") 141 .toString(); 142 } 143 144 HashMap<String, Long> timestamps = readTimestamps(); 145 146 if (SystemProperties.getLong("ro.runtime.firstboot", 0) == 0) { 147 if (StorageManager.inCryptKeeperBounce()) { 148 // Encrypted, first boot to get PIN/pattern/password so data is tmpfs 149 // Don't set ro.runtime.firstboot so that we will do this again 150 // when data is properly mounted 151 } else { 152 String now = Long.toString(System.currentTimeMillis()); 153 SystemProperties.set("ro.runtime.firstboot", now); 154 } 155 if (db != null) db.addText("SYSTEM_BOOT", headers); 156 157 // Negative sizes mean to take the *tail* of the file (see FileUtils.readTextFile()) 158 addFileWithFootersToDropBox(db, timestamps, headers, lastKmsgFooter, 159 "/proc/last_kmsg", -LOG_SIZE, "SYSTEM_LAST_KMSG"); 160 addFileWithFootersToDropBox(db, timestamps, headers, lastKmsgFooter, 161 "/sys/fs/pstore/console-ramoops", -LOG_SIZE, "SYSTEM_LAST_KMSG"); 162 addFileToDropBox(db, timestamps, headers, "/cache/recovery/log", -LOG_SIZE, 163 "SYSTEM_RECOVERY_LOG"); 164 addFileToDropBox(db, timestamps, headers, "/cache/recovery/last_kmsg", 165 -LOG_SIZE, "SYSTEM_RECOVERY_KMSG"); 166 addAuditErrorsToDropBox(db, timestamps, headers, -LOG_SIZE, "SYSTEM_AUDIT"); 167 addFsckErrorsToDropBox(db, timestamps, headers, -LOG_SIZE, "SYSTEM_FSCK"); 168 } else { 169 if (db != null) db.addText("SYSTEM_RESTART", headers); 170 } 171 172 // Scan existing tombstones (in case any new ones appeared) 173 File[] tombstoneFiles = TOMBSTONE_DIR.listFiles(); 174 for (int i = 0; tombstoneFiles != null && i < tombstoneFiles.length; i++) { 175 if (tombstoneFiles[i].isFile()) { 176 addFileToDropBox(db, timestamps, headers, tombstoneFiles[i].getPath(), 177 LOG_SIZE, "SYSTEM_TOMBSTONE"); 178 } 179 } 180 181 writeTimestamps(timestamps); 182 183 // Start watching for new tombstone files; will record them as they occur. 184 // This gets registered with the singleton file observer thread. 185 sTombstoneObserver = new FileObserver(TOMBSTONE_DIR.getPath(), FileObserver.CLOSE_WRITE) { 186 @Override 187 public void onEvent(int event, String path) { 188 HashMap<String, Long> timestamps = readTimestamps(); 189 try { 190 File file = new File(TOMBSTONE_DIR, path); 191 if (file.isFile()) { 192 addFileToDropBox(db, timestamps, headers, file.getPath(), LOG_SIZE, 193 "SYSTEM_TOMBSTONE"); 194 } 195 } catch (IOException e) { 196 Slog.e(TAG, "Can't log tombstone", e); 197 } 198 writeTimestamps(timestamps); 199 } 200 }; 201 202 sTombstoneObserver.startWatching(); 203 } 204 205 private static void addFileToDropBox( 206 DropBoxManager db, HashMap<String, Long> timestamps, 207 String headers, String filename, int maxSize, String tag) throws IOException { 208 addFileWithFootersToDropBox(db, timestamps, headers, "", filename, maxSize, tag); 209 } 210 211 private static void addFileWithFootersToDropBox( 212 DropBoxManager db, HashMap<String, Long> timestamps, 213 String headers, String footers, String filename, int maxSize, 214 String tag) throws IOException { 215 if (db == null || !db.isTagEnabled(tag)) return; // Logging disabled 216 217 File file = new File(filename); 218 long fileTime = file.lastModified(); 219 if (fileTime <= 0) return; // File does not exist 220 221 if (timestamps.containsKey(filename) && timestamps.get(filename) == fileTime) { 222 return; // Already logged this particular file 223 } 224 225 timestamps.put(filename, fileTime); 226 227 Slog.i(TAG, "Copying " + filename + " to DropBox (" + tag + ")"); 228 db.addText(tag, headers + FileUtils.readTextFile(file, maxSize, "[[TRUNCATED]]\n") + 229 footers); 230 } 231 232 private static void addAuditErrorsToDropBox(DropBoxManager db, 233 HashMap<String, Long> timestamps, String headers, int maxSize, String tag) 234 throws IOException { 235 if (db == null || !db.isTagEnabled(tag)) return; // Logging disabled 236 Slog.i(TAG, "Copying audit failures to DropBox"); 237 238 File file = new File("/proc/last_kmsg"); 239 long fileTime = file.lastModified(); 240 if (fileTime <= 0) { 241 file = new File("/sys/fs/pstore/console-ramoops"); 242 fileTime = file.lastModified(); 243 } 244 245 if (fileTime <= 0) return; // File does not exist 246 247 if (timestamps.containsKey(tag) && timestamps.get(tag) == fileTime) { 248 return; // Already logged this particular file 249 } 250 251 timestamps.put(tag, fileTime); 252 253 String log = FileUtils.readTextFile(file, maxSize, "[[TRUNCATED]]\n"); 254 StringBuilder sb = new StringBuilder(); 255 for (String line : log.split("\n")) { 256 if (line.contains("audit")) { 257 sb.append(line + "\n"); 258 } 259 } 260 Slog.i(TAG, "Copied " + sb.toString().length() + " worth of audits to DropBox"); 261 db.addText(tag, headers + sb.toString()); 262 } 263 264 private static void addFsckErrorsToDropBox(DropBoxManager db, 265 HashMap<String, Long> timestamps, String headers, int maxSize, String tag) 266 throws IOException { 267 boolean upload_needed = false; 268 if (db == null || !db.isTagEnabled(tag)) return; // Logging disabled 269 Slog.i(TAG, "Checking for fsck errors"); 270 271 File file = new File("/dev/fscklogs/log"); 272 long fileTime = file.lastModified(); 273 if (fileTime <= 0) return; // File does not exist 274 275 String log = FileUtils.readTextFile(file, maxSize, "[[TRUNCATED]]\n"); 276 StringBuilder sb = new StringBuilder(); 277 for (String line : log.split("\n")) { 278 if (line.contains("FILE SYSTEM WAS MODIFIED")) { 279 upload_needed = true; 280 break; 281 } 282 } 283 284 if (upload_needed) { 285 addFileToDropBox(db, timestamps, headers, "/dev/fscklogs/log", maxSize, tag); 286 } 287 288 // Remove the file so we don't re-upload if the runtime restarts. 289 file.delete(); 290 } 291 292 private static HashMap<String, Long> readTimestamps() { 293 synchronized (sFile) { 294 HashMap<String, Long> timestamps = new HashMap<String, Long>(); 295 boolean success = false; 296 try (final FileInputStream stream = sFile.openRead()) { 297 XmlPullParser parser = Xml.newPullParser(); 298 parser.setInput(stream, StandardCharsets.UTF_8.name()); 299 300 int type; 301 while ((type = parser.next()) != XmlPullParser.START_TAG 302 && type != XmlPullParser.END_DOCUMENT) { 303 ; 304 } 305 306 if (type != XmlPullParser.START_TAG) { 307 throw new IllegalStateException("no start tag found"); 308 } 309 310 int outerDepth = parser.getDepth(); // Skip the outer <log-files> tag. 311 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 312 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { 313 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { 314 continue; 315 } 316 317 String tagName = parser.getName(); 318 if (tagName.equals("log")) { 319 final String filename = parser.getAttributeValue(null, "filename"); 320 final long timestamp = Long.valueOf(parser.getAttributeValue( 321 null, "timestamp")); 322 timestamps.put(filename, timestamp); 323 } else { 324 Slog.w(TAG, "Unknown tag: " + parser.getName()); 325 XmlUtils.skipCurrentTag(parser); 326 } 327 } 328 success = true; 329 } catch (FileNotFoundException e) { 330 Slog.i(TAG, "No existing last log timestamp file " + sFile.getBaseFile() + 331 "; starting empty"); 332 } catch (IOException e) { 333 Slog.w(TAG, "Failed parsing " + e); 334 } catch (IllegalStateException e) { 335 Slog.w(TAG, "Failed parsing " + e); 336 } catch (NullPointerException e) { 337 Slog.w(TAG, "Failed parsing " + e); 338 } catch (XmlPullParserException e) { 339 Slog.w(TAG, "Failed parsing " + e); 340 } finally { 341 if (!success) { 342 timestamps.clear(); 343 } 344 } 345 return timestamps; 346 } 347 } 348 349 private void writeTimestamps(HashMap<String, Long> timestamps) { 350 synchronized (sFile) { 351 final FileOutputStream stream; 352 try { 353 stream = sFile.startWrite(); 354 } catch (IOException e) { 355 Slog.w(TAG, "Failed to write timestamp file: " + e); 356 return; 357 } 358 359 try { 360 XmlSerializer out = new FastXmlSerializer(); 361 out.setOutput(stream, StandardCharsets.UTF_8.name()); 362 out.startDocument(null, true); 363 out.startTag(null, "log-files"); 364 365 Iterator<String> itor = timestamps.keySet().iterator(); 366 while (itor.hasNext()) { 367 String filename = itor.next(); 368 out.startTag(null, "log"); 369 out.attribute(null, "filename", filename); 370 out.attribute(null, "timestamp", timestamps.get(filename).toString()); 371 out.endTag(null, "log"); 372 } 373 374 out.endTag(null, "log-files"); 375 out.endDocument(); 376 377 sFile.finishWrite(stream); 378 } catch (IOException e) { 379 Slog.w(TAG, "Failed to write timestamp file, using the backup: " + e); 380 sFile.failWrite(stream); 381 } 382 } 383 } 384} 385