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