• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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.car;
18 
19 import static android.car.CarBugreportManager.CarBugreportManagerCallback.CAR_BUGREPORT_DUMPSTATE_CONNECTION_FAILED;
20 import static android.car.CarBugreportManager.CarBugreportManagerCallback.CAR_BUGREPORT_DUMPSTATE_FAILED;
21 
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.annotation.RequiresPermission;
25 import android.car.CarBugreportManager.CarBugreportManagerCallback;
26 import android.car.ICarBugreportCallback;
27 import android.car.ICarBugreportService;
28 import android.content.Context;
29 import android.content.pm.PackageManager;
30 import android.net.LocalSocket;
31 import android.net.LocalSocketAddress;
32 import android.os.Binder;
33 import android.os.Handler;
34 import android.os.HandlerThread;
35 import android.os.ParcelFileDescriptor;
36 import android.os.Process;
37 import android.os.RemoteException;
38 import android.os.SystemClock;
39 import android.os.SystemProperties;
40 import android.util.Log;
41 import android.util.Slog;
42 
43 import com.android.internal.annotations.GuardedBy;
44 
45 import java.io.BufferedReader;
46 import java.io.DataInputStream;
47 import java.io.DataOutputStream;
48 import java.io.IOException;
49 import java.io.InputStream;
50 import java.io.InputStreamReader;
51 import java.io.OutputStream;
52 import java.io.PrintWriter;
53 
54 /**
55  * Bugreport service for cars.
56  */
57 public class CarBugreportManagerService extends ICarBugreportService.Stub implements
58         CarServiceBase {
59 
60     private static final String TAG = "CarBugreportMgrService";
61 
62     /**
63      * {@code dumpstate} progress prefixes.
64      *
65      * <p>The protocol is described in {@code frameworks/native/cmds/bugreportz/readme.md}.
66      */
67     private static final String BEGIN_PREFIX = "BEGIN:";
68     private static final String PROGRESS_PREFIX = "PROGRESS:";
69     private static final String OK_PREFIX = "OK:";
70     private static final String FAIL_PREFIX = "FAIL:";
71 
72     private static final String BUGREPORTD_SERVICE = "car-bugreportd";
73 
74     // The socket definitions must match the actual socket names defined in car_bugreportd service
75     // definition.
76     private static final String BUGREPORT_PROGRESS_SOCKET = "car_br_progress_socket";
77     private static final String BUGREPORT_OUTPUT_SOCKET = "car_br_output_socket";
78     private static final String BUGREPORT_EXTRA_OUTPUT_SOCKET = "car_br_extra_output_socket";
79 
80     private static final int SOCKET_CONNECTION_MAX_RETRY = 10;
81     private static final int SOCKET_CONNECTION_RETRY_DELAY_IN_MS = 5000;
82 
83     private final Context mContext;
84     private final Object mLock = new Object();
85 
86     private HandlerThread mHandlerThread;
87     private Handler mHandler;
88     private boolean mIsServiceRunning;
89 
90     /**
91      * Create a CarBugreportManagerService instance.
92      *
93      * @param context the context
94      */
CarBugreportManagerService(Context context)95     public CarBugreportManagerService(Context context) {
96         mContext = context;
97     }
98 
99     @Override
init()100     public void init() {
101         mHandlerThread = new HandlerThread(TAG);
102         mHandlerThread.start();
103         mHandler = new Handler(mHandlerThread.getLooper());
104     }
105 
106     @Override
release()107     public void release() {
108         mHandlerThread.quitSafely();
109     }
110 
111     @Override
112     @RequiresPermission(android.Manifest.permission.DUMP)
requestBugreport(ParcelFileDescriptor output, ParcelFileDescriptor extraOutput, ICarBugreportCallback callback)113     public void requestBugreport(ParcelFileDescriptor output, ParcelFileDescriptor extraOutput,
114             ICarBugreportCallback callback) {
115 
116         // Check the caller has proper permission
117         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP,
118                 "requestZippedBugreport");
119         // Check the caller is signed with platform keys
120         PackageManager pm = mContext.getPackageManager();
121         int callingUid = Binder.getCallingUid();
122         if (pm.checkSignatures(Process.myUid(), callingUid) != PackageManager.SIGNATURE_MATCH) {
123             throw new SecurityException("Caller " + pm.getNameForUid(callingUid)
124                             + " does not have the right signature");
125         }
126         // Check if the caller is the designated bugreport app
127         String defaultAppPkgName = mContext.getString(R.string.config_car_bugreport_application);
128         String[] packageNamesForCallerUid = pm.getPackagesForUid(callingUid);
129         boolean found = false;
130         if (packageNamesForCallerUid != null) {
131             for (String packageName : packageNamesForCallerUid) {
132                 if (defaultAppPkgName.equals(packageName)) {
133                     found = true;
134                     break;
135                 }
136             }
137         }
138         if (!found) {
139             throw new SecurityException("Caller " +  pm.getNameForUid(callingUid)
140                     + " is not a designated bugreport app");
141         }
142 
143         synchronized (mLock) {
144             requestBugReportLocked(output, extraOutput, callback);
145         }
146     }
147 
148     @GuardedBy("mLock")
requestBugReportLocked(ParcelFileDescriptor output, ParcelFileDescriptor extraOutput, ICarBugreportCallback callback)149     private void requestBugReportLocked(ParcelFileDescriptor output,
150             ParcelFileDescriptor extraOutput, ICarBugreportCallback callback) {
151         if (mIsServiceRunning) {
152             Slog.w(TAG, "Bugreport Service already running");
153             reportError(callback, CarBugreportManagerCallback.CAR_BUGREPORT_IN_PROGRESS);
154             return;
155         }
156         mIsServiceRunning = true;
157         mHandler.post(() -> startBugreportd(output, extraOutput, callback));
158     }
159 
startBugreportd(ParcelFileDescriptor output, ParcelFileDescriptor extraOutput, ICarBugreportCallback callback)160     private void startBugreportd(ParcelFileDescriptor output, ParcelFileDescriptor extraOutput,
161             ICarBugreportCallback callback) {
162         Slog.i(TAG, "Starting " + BUGREPORTD_SERVICE);
163         try {
164             SystemProperties.set("ctl.start", BUGREPORTD_SERVICE);
165         } catch (RuntimeException e) {
166             Slog.e(TAG, "Failed to start " + BUGREPORTD_SERVICE, e);
167             reportError(callback, CAR_BUGREPORT_DUMPSTATE_FAILED);
168             return;
169         }
170         processBugreportSockets(output, extraOutput, callback);
171         synchronized (mLock) {
172             mIsServiceRunning = false;
173         }
174     }
175 
handleProgress(String line, ICarBugreportCallback callback)176     private void handleProgress(String line, ICarBugreportCallback callback) {
177         String progressOverTotal = line.substring(PROGRESS_PREFIX.length());
178         String[] parts = progressOverTotal.split("/");
179         if (parts.length != 2) {
180             Slog.w(TAG, "Invalid progress line from bugreportz: " + line);
181             return;
182         }
183         float progress;
184         float total;
185         try {
186             progress = Float.parseFloat(parts[0]);
187             total = Float.parseFloat(parts[1]);
188         } catch (NumberFormatException e) {
189             Slog.w(TAG, "Invalid progress value: " + line, e);
190             return;
191         }
192         if (total == 0) {
193             Slog.w(TAG, "Invalid progress total value: " + line);
194             return;
195         }
196         try {
197             callback.onProgress(100f * progress / total);
198         } catch (RemoteException e) {
199             Slog.e(TAG, "Failed to call onProgress callback", e);
200         }
201     }
202 
handleFinished(ParcelFileDescriptor output, ParcelFileDescriptor extraOutput, ICarBugreportCallback callback)203     private void handleFinished(ParcelFileDescriptor output, ParcelFileDescriptor extraOutput,
204             ICarBugreportCallback callback) {
205         Slog.i(TAG, "Finished reading bugreport");
206         // copysockettopfd calls callback.onError on error
207         if (!copySocketToPfd(output, BUGREPORT_OUTPUT_SOCKET, callback)) {
208             return;
209         }
210         if (!copySocketToPfd(extraOutput, BUGREPORT_EXTRA_OUTPUT_SOCKET, callback)) {
211             return;
212         }
213         try {
214             callback.onFinished();
215         } catch (RemoteException e) {
216             Slog.e(TAG, "Failed to call onFinished callback", e);
217         }
218     }
219 
220     /**
221      * Reads from dumpstate progress and output sockets and invokes appropriate callbacks.
222      *
223      * <p>dumpstate prints {@code BEGIN:} right away, then prints {@code PROGRESS:} as it
224      * progresses. When it finishes or fails it prints {@code OK:pathToTheZipFile} or
225      * {@code FAIL:message} accordingly.
226      */
processBugreportSockets( ParcelFileDescriptor output, ParcelFileDescriptor extraOutput, ICarBugreportCallback callback)227     private void processBugreportSockets(
228             ParcelFileDescriptor output, ParcelFileDescriptor extraOutput,
229             ICarBugreportCallback callback) {
230         LocalSocket localSocket = connectSocket(BUGREPORT_PROGRESS_SOCKET);
231         if (localSocket == null) {
232             reportError(callback, CAR_BUGREPORT_DUMPSTATE_CONNECTION_FAILED);
233             return;
234         }
235 
236         try (BufferedReader reader =
237                 new BufferedReader(new InputStreamReader(localSocket.getInputStream()))) {
238             String line;
239             while ((line = reader.readLine()) != null) {
240                 if (line.startsWith(PROGRESS_PREFIX)) {
241                     handleProgress(line, callback);
242                 } else if (line.startsWith(FAIL_PREFIX)) {
243                     String errorMessage = line.substring(FAIL_PREFIX.length());
244                     Slog.e(TAG, "Failed to dumpstate: " + errorMessage);
245                     reportError(callback, CAR_BUGREPORT_DUMPSTATE_FAILED);
246                     return;
247                 } else if (line.startsWith(OK_PREFIX)) {
248                     handleFinished(output, extraOutput, callback);
249                     return;
250                 } else if (!line.startsWith(BEGIN_PREFIX)) {
251                     Slog.w(TAG, "Received unknown progress line from dumpstate: " + line);
252                 }
253             }
254             Slog.e(TAG, "dumpstate progress unexpectedly ended");
255             reportError(callback, CAR_BUGREPORT_DUMPSTATE_FAILED);
256         } catch (IOException | RuntimeException e) {
257             Slog.i(TAG, "Failed to read from progress socket", e);
258             reportError(callback, CAR_BUGREPORT_DUMPSTATE_CONNECTION_FAILED);
259         }
260     }
261 
copySocketToPfd( ParcelFileDescriptor pfd, String remoteSocket, ICarBugreportCallback callback)262     private boolean copySocketToPfd(
263             ParcelFileDescriptor pfd, String remoteSocket, ICarBugreportCallback callback) {
264         LocalSocket localSocket = connectSocket(remoteSocket);
265         if (localSocket == null) {
266             reportError(callback, CAR_BUGREPORT_DUMPSTATE_CONNECTION_FAILED);
267             return false;
268         }
269 
270         try (
271             DataInputStream in = new DataInputStream(localSocket.getInputStream());
272             DataOutputStream out =
273                     new DataOutputStream(new ParcelFileDescriptor.AutoCloseOutputStream(pfd))
274         ) {
275             rawCopyStream(out, in);
276         } catch (IOException | RuntimeException e) {
277             Slog.e(TAG, "Failed to grab dump state from " + BUGREPORT_OUTPUT_SOCKET, e);
278             reportError(callback, CAR_BUGREPORT_DUMPSTATE_FAILED);
279             return false;
280         }
281         return true;
282     }
283 
reportError(ICarBugreportCallback callback, int errorCode)284     private void reportError(ICarBugreportCallback callback, int errorCode) {
285         try {
286             callback.onError(errorCode);
287         } catch (RemoteException e) {
288             Slog.e(TAG, "onError() failed: " + e.getMessage());
289         }
290     }
291 
292     @Override
dump(PrintWriter writer)293     public void dump(PrintWriter writer) {
294         // TODO(sgurun) implement
295     }
296 
297     @Nullable
connectSocket(@onNull String socketName)298     private LocalSocket connectSocket(@NonNull String socketName) {
299         LocalSocket socket = new LocalSocket();
300         // The dumpstate socket will be created by init upon receiving the
301         // service request. It may not be ready by this point. So we will
302         // keep retrying until success or reaching timeout.
303         int retryCount = 0;
304         while (true) {
305             // There are a few factors impacting the socket delay:
306             // 1. potential system slowness
307             // 2. car-bugreportd takes the screenshots early (before starting dumpstate). This
308             //    should be taken into account as the socket opens after screenshots are
309             //    captured.
310             // Therefore we are generous in setting the timeout. Most cases should not even
311             // come close to the timeouts, but since bugreports are taken when there is a
312             // system issue, it is hard to guess.
313             SystemClock.sleep(SOCKET_CONNECTION_RETRY_DELAY_IN_MS);
314             try {
315                 socket.connect(new LocalSocketAddress(socketName,
316                         LocalSocketAddress.Namespace.RESERVED));
317                 return socket;
318             } catch (IOException e) {
319                 if (++retryCount >= SOCKET_CONNECTION_MAX_RETRY) {
320                     Slog.i(TAG, "Failed to connect to dumpstate socket " + socketName
321                             + " after " + retryCount + " retries", e);
322                     return null;
323                 }
324                 Log.i(TAG, "Failed to connect to " + socketName + ". Will try again "
325                         + e.getMessage());
326             }
327         }
328     }
329 
330     // does not close the reader or writer.
rawCopyStream(OutputStream writer, InputStream reader)331     private static void rawCopyStream(OutputStream writer, InputStream reader) throws IOException {
332         int read;
333         byte[] buf = new byte[8192];
334         while ((read = reader.read(buf, 0, buf.length)) > 0) {
335             writer.write(buf, 0, read);
336         }
337     }
338 }
339