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.NonNull;
20 import android.content.Context;
21 import android.os.Environment;
22 import android.os.IBinder;
23 import android.os.ParcelFileDescriptor;
24 import android.os.RemoteException;
25 import android.os.ServiceManager;
26 import android.provider.Settings;
27 import android.util.Slog;
28 
29 import com.android.internal.annotations.GuardedBy;
30 import com.android.internal.annotations.VisibleForTesting;
31 import com.android.internal.util.ArrayUtils;
32 
33 import java.io.File;
34 import java.io.FileNotFoundException;
35 import java.io.FileWriter;
36 import java.io.IOException;
37 
38 /**
39  * Provides utils to dump/wipe pre-reboot information.
40  */
41 final class PreRebootLogger {
42     private static final String TAG = "PreRebootLogger";
43     private static final String PREREBOOT_DIR = "prereboot";
44 
45     private static final String[] BUFFERS_TO_DUMP = {"system"};
46     private static final String[] SERVICES_TO_DUMP = {Context.ROLLBACK_SERVICE, "package"};
47 
48     private static final Object sLock = new Object();
49 
50     /**
51      * Process pre-reboot information. Dump pre-reboot information to {@link #PREREBOOT_DIR} if
52      * enabled {@link Settings.Global#ADB_ENABLED}; wipe dumped information otherwise.
53      */
log(Context context)54     static void log(Context context) {
55         log(context, getDumpDir());
56     }
57 
58     @VisibleForTesting
log(Context context, @NonNull File dumpDir)59     static void log(Context context, @NonNull File dumpDir) {
60         if (Settings.Global.getInt(
61                 context.getContentResolver(), Settings.Global.ADB_ENABLED, 0) == 1) {
62             Slog.d(TAG, "Dumping pre-reboot information...");
63             dump(dumpDir);
64         } else {
65             Slog.d(TAG, "Wiping pre-reboot information...");
66             wipe(dumpDir);
67         }
68     }
69 
dump(@onNull File dumpDir)70     private static void dump(@NonNull File dumpDir) {
71         synchronized (sLock) {
72             for (String buffer : BUFFERS_TO_DUMP) {
73                 dumpLogsLocked(dumpDir, buffer);
74             }
75             for (String service : SERVICES_TO_DUMP) {
76                 dumpServiceLocked(dumpDir, service);
77             }
78         }
79     }
80 
wipe(@onNull File dumpDir)81     private static void wipe(@NonNull File dumpDir) {
82         synchronized (sLock) {
83             for (File file : dumpDir.listFiles()) {
84                 file.delete();
85             }
86         }
87     }
88 
getDumpDir()89     private static File getDumpDir() {
90         final File dumpDir = new File(Environment.getDataMiscDirectory(), PREREBOOT_DIR);
91         if (!dumpDir.exists() || !dumpDir.isDirectory()) {
92             throw new UnsupportedOperationException("Pre-reboot dump directory not found");
93         }
94         return dumpDir;
95     }
96 
97     @GuardedBy("sLock")
dumpLogsLocked(@onNull File dumpDir, @NonNull String buffer)98     private static void dumpLogsLocked(@NonNull File dumpDir, @NonNull String buffer) {
99         try {
100             final File dumpFile = new File(dumpDir, buffer);
101             if (dumpFile.createNewFile()) {
102                 dumpFile.setWritable(true /* writable */, true /* ownerOnly */);
103             } else {
104                 // Wipes dumped information in existing file before recording new information.
105                 new FileWriter(dumpFile, false).flush();
106             }
107 
108             final String[] cmdline =
109                     {"logcat", "-d", "-b", buffer, "-f", dumpFile.getAbsolutePath()};
110             Runtime.getRuntime().exec(cmdline).waitFor();
111         } catch (IOException | InterruptedException e) {
112             Slog.d(TAG, "Dump system log buffer before reboot fail", e);
113         }
114     }
115 
116     @GuardedBy("sLock")
dumpServiceLocked(@onNull File dumpDir, @NonNull String serviceName)117     private static void dumpServiceLocked(@NonNull File dumpDir, @NonNull String serviceName) {
118         final IBinder binder = ServiceManager.checkService(serviceName);
119         if (binder == null) {
120             return;
121         }
122 
123         try {
124             final File dumpFile = new File(dumpDir, serviceName);
125             final ParcelFileDescriptor fd = ParcelFileDescriptor.open(dumpFile,
126                     ParcelFileDescriptor.MODE_CREATE | ParcelFileDescriptor.MODE_TRUNCATE
127                             | ParcelFileDescriptor.MODE_WRITE_ONLY);
128             binder.dump(fd.getFileDescriptor(), ArrayUtils.emptyArray(String.class));
129         } catch (FileNotFoundException | RemoteException e) {
130             Slog.d(TAG, String.format("Dump %s service before reboot fail", serviceName), e);
131         }
132     }
133 }
134