blob: 24806578884f615292bf4fa49c233be1b6ce0b1d [file] [log] [blame]
Selim Gurunc8c82f62019-04-17 14:07:30 -07001/*
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
17package com.android.car;
18
Zhomart Mukhamejanovad23f272019-06-04 20:27:24 -070019import static android.car.CarBugreportManager.CarBugreportManagerCallback.CAR_BUGREPORT_DUMPSTATE_CONNECTION_FAILED;
20import static android.car.CarBugreportManager.CarBugreportManagerCallback.CAR_BUGREPORT_DUMPSTATE_FAILED;
21
22import android.annotation.NonNull;
23import android.annotation.Nullable;
Selim Gurunc8c82f62019-04-17 14:07:30 -070024import android.annotation.RequiresPermission;
25import android.car.CarBugreportManager.CarBugreportManagerCallback;
26import android.car.ICarBugreportCallback;
27import android.car.ICarBugreportService;
28import android.content.Context;
Selim Gurun606b2ec2019-05-24 22:47:47 -070029import android.content.pm.PackageManager;
Selim Gurunc8c82f62019-04-17 14:07:30 -070030import android.net.LocalSocket;
31import android.net.LocalSocketAddress;
Selim Gurun606b2ec2019-05-24 22:47:47 -070032import android.os.Binder;
Selim Gurunc8c82f62019-04-17 14:07:30 -070033import android.os.Handler;
34import android.os.HandlerThread;
35import android.os.ParcelFileDescriptor;
Selim Gurun606b2ec2019-05-24 22:47:47 -070036import android.os.Process;
Selim Gurunc8c82f62019-04-17 14:07:30 -070037import android.os.RemoteException;
38import android.os.SystemClock;
39import android.os.SystemProperties;
40import android.util.Log;
41import android.util.Slog;
42
43import com.android.internal.annotations.GuardedBy;
44
Zhomart Mukhamejanovad23f272019-06-04 20:27:24 -070045import java.io.BufferedReader;
Selim Gurunc8c82f62019-04-17 14:07:30 -070046import java.io.DataInputStream;
47import java.io.DataOutputStream;
48import java.io.IOException;
49import java.io.InputStream;
Zhomart Mukhamejanovad23f272019-06-04 20:27:24 -070050import java.io.InputStreamReader;
Selim Gurunc8c82f62019-04-17 14:07:30 -070051import java.io.OutputStream;
52import java.io.PrintWriter;
53
Selim Gurunc8c82f62019-04-17 14:07:30 -070054/**
Selim Guruna85b7e72019-06-07 11:01:42 -070055 * Bugreport service for cars.
Selim Gurunc8c82f62019-04-17 14:07:30 -070056 */
57public class CarBugreportManagerService extends ICarBugreportService.Stub implements
58 CarServiceBase {
59
60 private static final String TAG = "CarBugreportMgrService";
Selim Gurunc8c82f62019-04-17 14:07:30 -070061
Zhomart Mukhamejanovad23f272019-06-04 20:27:24 -070062 /**
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:";
Selim Gurunc8c82f62019-04-17 14:07:30 -070071
Zhomart Mukhamejanovad23f272019-06-04 20:27:24 -070072 private static final String BUGREPORTD_SERVICE = "car-bugreportd";
Selim Gurunc8c82f62019-04-17 14:07:30 -070073
Selim Gurunc4d7c012019-05-13 20:27:22 -070074 // 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";
Selim Guruna85b7e72019-06-07 11:01:42 -070078 private static final String BUGREPORT_EXTRA_OUTPUT_SOCKET = "car_br_extra_output_socket";
Selim Gurunc4d7c012019-05-13 20:27:22 -070079
Selim Gurunc8c82f62019-04-17 14:07:30 -070080 private static final int SOCKET_CONNECTION_MAX_RETRY = 10;
Selim Guruna85b7e72019-06-07 11:01:42 -070081 private static final int SOCKET_CONNECTION_RETRY_DELAY_IN_MS = 5000;
Selim Gurunc8c82f62019-04-17 14:07:30 -070082
Zhomart Mukhamejanovad23f272019-06-04 20:27:24 -070083 private final Context mContext;
84 private final Object mLock = new Object();
85
Keun young Parkb241d022020-04-20 20:31:34 -070086 private final HandlerThread mHandlerThread = CarServiceUtils.getHandlerThread(
87 getClass().getSimpleName());
88 private final Handler mHandler = new Handler(mHandlerThread.getLooper());
Zhomart Mukhamejanovad23f272019-06-04 20:27:24 -070089 private boolean mIsServiceRunning;
90
Selim Gurunc8c82f62019-04-17 14:07:30 -070091 /**
92 * Create a CarBugreportManagerService instance.
93 *
94 * @param context the context
95 */
96 public CarBugreportManagerService(Context context) {
97 mContext = context;
98 }
99
100 @Override
101 public void init() {
Keun young Parkb241d022020-04-20 20:31:34 -0700102 // nothing to do
Selim Gurunc8c82f62019-04-17 14:07:30 -0700103 }
104
105 @Override
106 public void release() {
Keun young Parkb241d022020-04-20 20:31:34 -0700107 // nothing to do
Selim Gurunc8c82f62019-04-17 14:07:30 -0700108 }
109
110 @Override
111 @RequiresPermission(android.Manifest.permission.DUMP)
Selim Guruna85b7e72019-06-07 11:01:42 -0700112 public void requestBugreport(ParcelFileDescriptor output, ParcelFileDescriptor extraOutput,
113 ICarBugreportCallback callback) {
Selim Gurunc4d7c012019-05-13 20:27:22 -0700114
Selim Gurun606b2ec2019-05-24 22:47:47 -0700115 // Check the caller has proper permission
Selim Gurunc4d7c012019-05-13 20:27:22 -0700116 mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP,
Selim Gurun606b2ec2019-05-24 22:47:47 -0700117 "requestZippedBugreport");
118 // Check the caller is signed with platform keys
119 PackageManager pm = mContext.getPackageManager();
120 int callingUid = Binder.getCallingUid();
121 if (pm.checkSignatures(Process.myUid(), callingUid) != PackageManager.SIGNATURE_MATCH) {
122 throw new SecurityException("Caller " + pm.getNameForUid(callingUid)
123 + " does not have the right signature");
124 }
Zhomart Mukhamejanov30862412019-06-10 16:59:15 -0700125 // Check if the caller is the designated bugreport app
126 String defaultAppPkgName = mContext.getString(R.string.config_car_bugreport_application);
Selim Gurun606b2ec2019-05-24 22:47:47 -0700127 String[] packageNamesForCallerUid = pm.getPackagesForUid(callingUid);
128 boolean found = false;
129 if (packageNamesForCallerUid != null) {
130 for (String packageName : packageNamesForCallerUid) {
131 if (defaultAppPkgName.equals(packageName)) {
132 found = true;
133 break;
134 }
135 }
136 }
137 if (!found) {
138 throw new SecurityException("Caller " + pm.getNameForUid(callingUid)
139 + " is not a designated bugreport app");
140 }
Selim Gurunc4d7c012019-05-13 20:27:22 -0700141
142 synchronized (mLock) {
Selim Guruna85b7e72019-06-07 11:01:42 -0700143 requestBugReportLocked(output, extraOutput, callback);
Selim Gurunc4d7c012019-05-13 20:27:22 -0700144 }
145 }
146
147 @GuardedBy("mLock")
Selim Guruna85b7e72019-06-07 11:01:42 -0700148 private void requestBugReportLocked(ParcelFileDescriptor output,
149 ParcelFileDescriptor extraOutput, ICarBugreportCallback callback) {
Selim Gurunc4d7c012019-05-13 20:27:22 -0700150 if (mIsServiceRunning) {
151 Slog.w(TAG, "Bugreport Service already running");
152 reportError(callback, CarBugreportManagerCallback.CAR_BUGREPORT_IN_PROGRESS);
153 return;
154 }
155 mIsServiceRunning = true;
Selim Guruna85b7e72019-06-07 11:01:42 -0700156 mHandler.post(() -> startBugreportd(output, extraOutput, callback));
Selim Gurunc4d7c012019-05-13 20:27:22 -0700157 }
158
Selim Guruna85b7e72019-06-07 11:01:42 -0700159 private void startBugreportd(ParcelFileDescriptor output, ParcelFileDescriptor extraOutput,
160 ICarBugreportCallback callback) {
Zhomart Mukhamejanovad23f272019-06-04 20:27:24 -0700161 Slog.i(TAG, "Starting " + BUGREPORTD_SERVICE);
162 try {
163 SystemProperties.set("ctl.start", BUGREPORTD_SERVICE);
164 } catch (RuntimeException e) {
165 Slog.e(TAG, "Failed to start " + BUGREPORTD_SERVICE, e);
166 reportError(callback, CAR_BUGREPORT_DUMPSTATE_FAILED);
167 return;
168 }
Selim Guruna85b7e72019-06-07 11:01:42 -0700169 processBugreportSockets(output, extraOutput, callback);
Selim Gurunc4d7c012019-05-13 20:27:22 -0700170 synchronized (mLock) {
171 mIsServiceRunning = false;
172 }
173 }
174
Zhomart Mukhamejanovad23f272019-06-04 20:27:24 -0700175 private void handleProgress(String line, ICarBugreportCallback callback) {
176 String progressOverTotal = line.substring(PROGRESS_PREFIX.length());
177 String[] parts = progressOverTotal.split("/");
178 if (parts.length != 2) {
179 Slog.w(TAG, "Invalid progress line from bugreportz: " + line);
180 return;
181 }
182 float progress;
183 float total;
Selim Gurunc4d7c012019-05-13 20:27:22 -0700184 try {
Zhomart Mukhamejanovad23f272019-06-04 20:27:24 -0700185 progress = Float.parseFloat(parts[0]);
186 total = Float.parseFloat(parts[1]);
187 } catch (NumberFormatException e) {
188 Slog.w(TAG, "Invalid progress value: " + line, e);
Selim Gurunc4d7c012019-05-13 20:27:22 -0700189 return;
190 }
Zhomart Mukhamejanovad23f272019-06-04 20:27:24 -0700191 if (total == 0) {
192 Slog.w(TAG, "Invalid progress total value: " + line);
Selim Gurunc4d7c012019-05-13 20:27:22 -0700193 return;
194 }
Selim Gurunc4d7c012019-05-13 20:27:22 -0700195 try {
Zhomart Mukhamejanovad23f272019-06-04 20:27:24 -0700196 callback.onProgress(100f * progress / total);
Selim Gurunc4d7c012019-05-13 20:27:22 -0700197 } catch (RemoteException e) {
Zhomart Mukhamejanovad23f272019-06-04 20:27:24 -0700198 Slog.e(TAG, "Failed to call onProgress callback", e);
Selim Gurunc4d7c012019-05-13 20:27:22 -0700199 }
200 }
201
Selim Guruna85b7e72019-06-07 11:01:42 -0700202 private void handleFinished(ParcelFileDescriptor output, ParcelFileDescriptor extraOutput,
203 ICarBugreportCallback callback) {
Zhomart Mukhamejanovad23f272019-06-04 20:27:24 -0700204 Slog.i(TAG, "Finished reading bugreport");
Selim Guruna85b7e72019-06-07 11:01:42 -0700205 // copysockettopfd calls callback.onError on error
206 if (!copySocketToPfd(output, BUGREPORT_OUTPUT_SOCKET, callback)) {
207 return;
208 }
209 if (!copySocketToPfd(extraOutput, BUGREPORT_EXTRA_OUTPUT_SOCKET, callback)) {
Zhomart Mukhamejanovad23f272019-06-04 20:27:24 -0700210 return;
211 }
Selim Gurunc4d7c012019-05-13 20:27:22 -0700212 try {
Zhomart Mukhamejanovad23f272019-06-04 20:27:24 -0700213 callback.onFinished();
214 } catch (RemoteException e) {
215 Slog.e(TAG, "Failed to call onFinished callback", e);
216 }
217 }
218
219 /**
220 * Reads from dumpstate progress and output sockets and invokes appropriate callbacks.
221 *
222 * <p>dumpstate prints {@code BEGIN:} right away, then prints {@code PROGRESS:} as it
223 * progresses. When it finishes or fails it prints {@code OK:pathToTheZipFile} or
224 * {@code FAIL:message} accordingly.
225 */
226 private void processBugreportSockets(
Selim Guruna85b7e72019-06-07 11:01:42 -0700227 ParcelFileDescriptor output, ParcelFileDescriptor extraOutput,
228 ICarBugreportCallback callback) {
Zhomart Mukhamejanovad23f272019-06-04 20:27:24 -0700229 LocalSocket localSocket = connectSocket(BUGREPORT_PROGRESS_SOCKET);
230 if (localSocket == null) {
231 reportError(callback, CAR_BUGREPORT_DUMPSTATE_CONNECTION_FAILED);
232 return;
233 }
234
235 try (BufferedReader reader =
236 new BufferedReader(new InputStreamReader(localSocket.getInputStream()))) {
237 String line;
238 while ((line = reader.readLine()) != null) {
239 if (line.startsWith(PROGRESS_PREFIX)) {
240 handleProgress(line, callback);
241 } else if (line.startsWith(FAIL_PREFIX)) {
242 String errorMessage = line.substring(FAIL_PREFIX.length());
243 Slog.e(TAG, "Failed to dumpstate: " + errorMessage);
244 reportError(callback, CAR_BUGREPORT_DUMPSTATE_FAILED);
245 return;
246 } else if (line.startsWith(OK_PREFIX)) {
Selim Guruna85b7e72019-06-07 11:01:42 -0700247 handleFinished(output, extraOutput, callback);
Zhomart Mukhamejanovad23f272019-06-04 20:27:24 -0700248 return;
249 } else if (!line.startsWith(BEGIN_PREFIX)) {
250 Slog.w(TAG, "Received unknown progress line from dumpstate: " + line);
251 }
252 }
253 Slog.e(TAG, "dumpstate progress unexpectedly ended");
254 reportError(callback, CAR_BUGREPORT_DUMPSTATE_FAILED);
255 } catch (IOException | RuntimeException e) {
256 Slog.i(TAG, "Failed to read from progress socket", e);
257 reportError(callback, CAR_BUGREPORT_DUMPSTATE_CONNECTION_FAILED);
258 }
259 }
260
261 private boolean copySocketToPfd(
Selim Guruna85b7e72019-06-07 11:01:42 -0700262 ParcelFileDescriptor pfd, String remoteSocket, ICarBugreportCallback callback) {
263 LocalSocket localSocket = connectSocket(remoteSocket);
Zhomart Mukhamejanovad23f272019-06-04 20:27:24 -0700264 if (localSocket == null) {
265 reportError(callback, CAR_BUGREPORT_DUMPSTATE_CONNECTION_FAILED);
Selim Gurunc4d7c012019-05-13 20:27:22 -0700266 return false;
267 }
268
269 try (
270 DataInputStream in = new DataInputStream(localSocket.getInputStream());
271 DataOutputStream out =
Zhomart Mukhamejanovad23f272019-06-04 20:27:24 -0700272 new DataOutputStream(new ParcelFileDescriptor.AutoCloseOutputStream(pfd))
Selim Gurunc4d7c012019-05-13 20:27:22 -0700273 ) {
274 rawCopyStream(out, in);
275 } catch (IOException | RuntimeException e) {
Zhomart Mukhamejanovad23f272019-06-04 20:27:24 -0700276 Slog.e(TAG, "Failed to grab dump state from " + BUGREPORT_OUTPUT_SOCKET, e);
277 reportError(callback, CAR_BUGREPORT_DUMPSTATE_FAILED);
Selim Gurunc4d7c012019-05-13 20:27:22 -0700278 return false;
279 }
280 return true;
281 }
282
Selim Gurunc8c82f62019-04-17 14:07:30 -0700283 private void reportError(ICarBugreportCallback callback, int errorCode) {
284 try {
285 callback.onError(errorCode);
286 } catch (RemoteException e) {
Zhomart Mukhamejanova77417b2019-04-30 13:04:03 -0700287 Slog.e(TAG, "onError() failed: " + e.getMessage());
Selim Gurunc8c82f62019-04-17 14:07:30 -0700288 }
289 }
290
Selim Gurunc4d7c012019-05-13 20:27:22 -0700291 @Override
292 public void dump(PrintWriter writer) {
293 // TODO(sgurun) implement
294 }
295
Zhomart Mukhamejanovad23f272019-06-04 20:27:24 -0700296 @Nullable
297 private LocalSocket connectSocket(@NonNull String socketName) {
Selim Gurunc8c82f62019-04-17 14:07:30 -0700298 LocalSocket socket = new LocalSocket();
299 // The dumpstate socket will be created by init upon receiving the
300 // service request. It may not be ready by this point. So we will
301 // keep retrying until success or reaching timeout.
302 int retryCount = 0;
303 while (true) {
Selim Guruna85b7e72019-06-07 11:01:42 -0700304 // There are a few factors impacting the socket delay:
305 // 1. potential system slowness
306 // 2. car-bugreportd takes the screenshots early (before starting dumpstate). This
307 // should be taken into account as the socket opens after screenshots are
308 // captured.
309 // Therefore we are generous in setting the timeout. Most cases should not even
310 // come close to the timeouts, but since bugreports are taken when there is a
311 // system issue, it is hard to guess.
312 SystemClock.sleep(SOCKET_CONNECTION_RETRY_DELAY_IN_MS);
Selim Gurunc8c82f62019-04-17 14:07:30 -0700313 try {
Selim Gurunc4d7c012019-05-13 20:27:22 -0700314 socket.connect(new LocalSocketAddress(socketName,
Selim Gurunc8c82f62019-04-17 14:07:30 -0700315 LocalSocketAddress.Namespace.RESERVED));
316 return socket;
317 } catch (IOException e) {
Selim Gurunc8c82f62019-04-17 14:07:30 -0700318 if (++retryCount >= SOCKET_CONNECTION_MAX_RETRY) {
Zhomart Mukhamejanovad23f272019-06-04 20:27:24 -0700319 Slog.i(TAG, "Failed to connect to dumpstate socket " + socketName
320 + " after " + retryCount + " retries", e);
321 return null;
Selim Gurunc8c82f62019-04-17 14:07:30 -0700322 }
Selim Guruna85b7e72019-06-07 11:01:42 -0700323 Log.i(TAG, "Failed to connect to " + socketName + ". Will try again "
Selim Gurunc4d7c012019-05-13 20:27:22 -0700324 + e.getMessage());
Selim Gurunc8c82f62019-04-17 14:07:30 -0700325 }
326 }
Selim Gurunc8c82f62019-04-17 14:07:30 -0700327 }
328
329 // does not close the reader or writer.
330 private static void rawCopyStream(OutputStream writer, InputStream reader) throws IOException {
331 int read;
332 byte[] buf = new byte[8192];
333 while ((read = reader.read(buf, 0, buf.length)) > 0) {
334 writer.write(buf, 0, read);
335 }
336 }
337}