1 /* 2 * Copyright (C) 2020 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.power; 18 19 import android.annotation.DurationMillisLong; 20 import android.annotation.NonNull; 21 import android.content.Context; 22 import android.os.Environment; 23 import android.os.IBinder; 24 import android.os.ParcelFileDescriptor; 25 import android.os.RemoteException; 26 import android.os.ServiceManager; 27 import android.provider.Settings.Global; 28 import android.util.Slog; 29 30 import com.android.internal.annotations.GuardedBy; 31 import com.android.internal.annotations.VisibleForTesting; 32 import com.android.internal.util.ArrayUtils; 33 34 import java.io.File; 35 import java.io.FileNotFoundException; 36 import java.io.FileWriter; 37 import java.io.IOException; 38 import java.util.concurrent.TimeUnit; 39 import java.util.concurrent.atomic.AtomicBoolean; 40 41 /** 42 * Provides utils to dump/wipe pre-reboot information. 43 */ 44 final class PreRebootLogger { 45 private static final String TAG = "PreRebootLogger"; 46 private static final String PREREBOOT_DIR = "prereboot"; 47 48 private static final String[] BUFFERS_TO_DUMP = {"system"}; 49 private static final String[] SERVICES_TO_DUMP = {Context.ROLLBACK_SERVICE, "package"}; 50 51 private static final Object sLock = new Object(); 52 private static final long MAX_DUMP_TIME = TimeUnit.SECONDS.toMillis(20); 53 54 /** 55 * Process pre-reboot information. Dump pre-reboot information to {@link #PREREBOOT_DIR} if 56 * enabled {@link Settings.Global#ADB_ENABLED} and having active staged session; wipe dumped 57 * information otherwise. 58 */ log(Context context)59 static void log(Context context) { 60 log(context, getDumpDir()); 61 } 62 63 @VisibleForTesting log(Context context, @NonNull File dumpDir)64 static void log(Context context, @NonNull File dumpDir) { 65 if (needDump(context)) { 66 dump(dumpDir, MAX_DUMP_TIME); 67 } else { 68 wipe(dumpDir); 69 } 70 } 71 needDump(Context context)72 private static boolean needDump(Context context) { 73 return Global.getInt(context.getContentResolver(), Global.ADB_ENABLED, 0) == 1 74 && !context.getPackageManager().getPackageInstaller() 75 .getActiveStagedSessions().isEmpty(); 76 } 77 78 @VisibleForTesting dump(@onNull File dumpDir, @DurationMillisLong long maxWaitTime)79 static void dump(@NonNull File dumpDir, @DurationMillisLong long maxWaitTime) { 80 Slog.d(TAG, "Dumping pre-reboot information..."); 81 final AtomicBoolean done = new AtomicBoolean(false); 82 final Thread t = new Thread(() -> { 83 synchronized (sLock) { 84 for (String buffer : BUFFERS_TO_DUMP) { 85 dumpLogsLocked(dumpDir, buffer); 86 } 87 for (String service : SERVICES_TO_DUMP) { 88 dumpServiceLocked(dumpDir, service); 89 } 90 } 91 done.set(true); 92 }); 93 t.start(); 94 95 try { 96 t.join(maxWaitTime); 97 } catch (InterruptedException e) { 98 Slog.e(TAG, "Failed to dump pre-reboot information due to interrupted", e); 99 } 100 101 if (!done.get()) { 102 Slog.w(TAG, "Failed to dump pre-reboot information due to timeout"); 103 } 104 } 105 wipe(@onNull File dumpDir)106 private static void wipe(@NonNull File dumpDir) { 107 Slog.d(TAG, "Wiping pre-reboot information..."); 108 synchronized (sLock) { 109 for (File file : dumpDir.listFiles()) { 110 file.delete(); 111 } 112 } 113 } 114 getDumpDir()115 private static File getDumpDir() { 116 final File dumpDir = new File(Environment.getDataMiscDirectory(), PREREBOOT_DIR); 117 if (!dumpDir.exists() || !dumpDir.isDirectory()) { 118 throw new UnsupportedOperationException("Pre-reboot dump directory not found"); 119 } 120 return dumpDir; 121 } 122 123 @GuardedBy("sLock") dumpLogsLocked(@onNull File dumpDir, @NonNull String buffer)124 private static void dumpLogsLocked(@NonNull File dumpDir, @NonNull String buffer) { 125 try { 126 final File dumpFile = new File(dumpDir, buffer); 127 if (dumpFile.createNewFile()) { 128 dumpFile.setWritable(true /* writable */, true /* ownerOnly */); 129 } else { 130 // Wipes dumped information in existing file before recording new information. 131 new FileWriter(dumpFile, false).flush(); 132 } 133 134 final String[] cmdline = 135 {"logcat", "-d", "-b", buffer, "-f", dumpFile.getAbsolutePath()}; 136 Runtime.getRuntime().exec(cmdline).waitFor(); 137 } catch (IOException | InterruptedException e) { 138 Slog.e(TAG, "Failed to dump system log buffer before reboot", e); 139 } 140 } 141 142 @GuardedBy("sLock") dumpServiceLocked(@onNull File dumpDir, @NonNull String serviceName)143 private static void dumpServiceLocked(@NonNull File dumpDir, @NonNull String serviceName) { 144 final IBinder binder = ServiceManager.checkService(serviceName); 145 if (binder == null) { 146 return; 147 } 148 149 try { 150 final File dumpFile = new File(dumpDir, serviceName); 151 final ParcelFileDescriptor fd = ParcelFileDescriptor.open(dumpFile, 152 ParcelFileDescriptor.MODE_CREATE | ParcelFileDescriptor.MODE_TRUNCATE 153 | ParcelFileDescriptor.MODE_WRITE_ONLY); 154 binder.dump(fd.getFileDescriptor(), ArrayUtils.emptyArray(String.class)); 155 } catch (FileNotFoundException | RemoteException e) { 156 Slog.e(TAG, String.format("Failed to dump %s service before reboot", serviceName), e); 157 } 158 } 159 } 160