blob: 7164a303121ada40ca28824ee48e55b26849d061 [file] [log] [blame]
Tao Baoe8a403d2015-12-31 07:44:55 -08001/*
2 * Copyright (C) 2016 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
Kenny Rootf96836e2019-11-19 17:11:34 -080017package com.android.server.recoverysystem;
Tao Baoe8a403d2015-12-31 07:44:55 -080018
19import android.content.Context;
Kenny Rootf76cfc32019-11-08 14:36:03 -080020import android.content.IntentSender;
Tao Baodd3baae2016-02-26 10:28:58 -080021import android.net.LocalSocket;
22import android.net.LocalSocketAddress;
Kenny Rootf76cfc32019-11-08 14:36:03 -080023import android.os.Binder;
Tao Baoe8a403d2015-12-31 07:44:55 -080024import android.os.IRecoverySystem;
25import android.os.IRecoverySystemProgressListener;
Tao Bao794c8b02016-09-27 11:15:42 -070026import android.os.PowerManager;
Kenny Root4ad77bf2019-12-16 17:23:43 -080027import android.os.Process;
Tao Baoe8a403d2015-12-31 07:44:55 -080028import android.os.RecoverySystem;
29import android.os.RemoteException;
Kenny Root4ad77bf2019-12-16 17:23:43 -080030import android.os.ResultReceiver;
31import android.os.ShellCallback;
Tao Baoe8a403d2015-12-31 07:44:55 -080032import android.os.SystemProperties;
Tao Baoe8a403d2015-12-31 07:44:55 -080033import android.util.Slog;
34
Kenny Rootd508e1e2019-11-15 10:20:59 -080035import com.android.internal.annotations.VisibleForTesting;
Kenny Rootf76cfc32019-11-08 14:36:03 -080036import com.android.internal.widget.LockSettingsInternal;
37import com.android.internal.widget.RebootEscrowListener;
38import com.android.server.LocalServices;
Kenny Rootf96836e2019-11-19 17:11:34 -080039import com.android.server.SystemService;
40
Tao Baodd3baae2016-02-26 10:28:58 -080041import libcore.io.IoUtils;
42
43import java.io.DataInputStream;
44import java.io.DataOutputStream;
Kenny Root4ad77bf2019-12-16 17:23:43 -080045import java.io.FileDescriptor;
Tao Baoe8a403d2015-12-31 07:44:55 -080046import java.io.FileWriter;
47import java.io.IOException;
Kenny Rootd508e1e2019-11-15 10:20:59 -080048import java.nio.charset.StandardCharsets;
Tao Baoe8a403d2015-12-31 07:44:55 -080049
50/**
51 * The recovery system service is responsible for coordinating recovery related
52 * functions on the device. It sets up (or clears) the bootloader control block
53 * (BCB), which will be read by the bootloader and the recovery image. It also
54 * triggers /system/bin/uncrypt via init to de-encrypt an OTA package on the
55 * /data partition so that it can be accessed under the recovery image.
56 */
Kenny Rootf76cfc32019-11-08 14:36:03 -080057public class RecoverySystemService extends IRecoverySystem.Stub implements RebootEscrowListener {
Tao Baoe8a403d2015-12-31 07:44:55 -080058 private static final String TAG = "RecoverySystemService";
59 private static final boolean DEBUG = false;
60
Tao Baodd3baae2016-02-26 10:28:58 -080061 // The socket at /dev/socket/uncrypt to communicate with uncrypt.
62 private static final String UNCRYPT_SOCKET = "uncrypt";
63
Tao Bao794c8b02016-09-27 11:15:42 -070064 // The init services that communicate with /system/bin/uncrypt.
Kenny Rootd508e1e2019-11-15 10:20:59 -080065 @VisibleForTesting
66 static final String INIT_SERVICE_UNCRYPT = "init.svc.uncrypt";
67 @VisibleForTesting
68 static final String INIT_SERVICE_SETUP_BCB = "init.svc.setup-bcb";
69 @VisibleForTesting
70 static final String INIT_SERVICE_CLEAR_BCB = "init.svc.clear-bcb";
Tao Baoe8a403d2015-12-31 07:44:55 -080071
Tao Bao794c8b02016-09-27 11:15:42 -070072 private static final Object sRequestLock = new Object();
73
Kenny Rootd508e1e2019-11-15 10:20:59 -080074 private static final int SOCKET_CONNECTION_MAX_RETRY = 30;
Tao Baoe8a403d2015-12-31 07:44:55 -080075
Kenny Rootd508e1e2019-11-15 10:20:59 -080076 private final Injector mInjector;
77 private final Context mContext;
78
Kenny Rootf76cfc32019-11-08 14:36:03 -080079 private boolean mPreparedForReboot;
80 private String mUnattendedRebootToken;
81 private IntentSender mPreparedForRebootIntentSender;
82
Kenny Rootd508e1e2019-11-15 10:20:59 -080083 static class Injector {
84 protected final Context mContext;
85
86 Injector(Context context) {
87 mContext = context;
88 }
89
90 public Context getContext() {
91 return mContext;
92 }
93
Kenny Rootf76cfc32019-11-08 14:36:03 -080094 public LockSettingsInternal getLockSettingsService() {
95 return LocalServices.getService(LockSettingsInternal.class);
96 }
97
Kenny Rootd508e1e2019-11-15 10:20:59 -080098 public PowerManager getPowerManager() {
99 return (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
100 }
101
102 public String systemPropertiesGet(String key) {
103 return SystemProperties.get(key);
104 }
105
106 public void systemPropertiesSet(String key, String value) {
107 SystemProperties.set(key, value);
108 }
109
110 public boolean uncryptPackageFileDelete() {
111 return RecoverySystem.UNCRYPT_PACKAGE_FILE.delete();
112 }
113
114 public String getUncryptPackageFileName() {
115 return RecoverySystem.UNCRYPT_PACKAGE_FILE.getName();
116 }
117
118 public FileWriter getUncryptPackageFileWriter() throws IOException {
119 return new FileWriter(RecoverySystem.UNCRYPT_PACKAGE_FILE);
120 }
121
122 public UncryptSocket connectService() {
123 UncryptSocket socket = new UncryptSocket();
124 if (!socket.connectService()) {
125 socket.close();
126 return null;
127 }
128 return socket;
129 }
130
131 public void threadSleep(long millis) throws InterruptedException {
132 Thread.sleep(millis);
133 }
Tao Baoe8a403d2015-12-31 07:44:55 -0800134 }
135
Kenny Rootd508e1e2019-11-15 10:20:59 -0800136 /**
137 * Handles the lifecycle events for the RecoverySystemService.
138 */
139 public static final class Lifecycle extends SystemService {
Kenny Rootf76cfc32019-11-08 14:36:03 -0800140 private RecoverySystemService mRecoverySystemService;
141
Kenny Rootd508e1e2019-11-15 10:20:59 -0800142 public Lifecycle(Context context) {
143 super(context);
144 }
145
146 @Override
Kenny Rootf76cfc32019-11-08 14:36:03 -0800147 public void onBootPhase(int phase) {
148 if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
149 mRecoverySystemService.onSystemServicesReady();
150 }
151 }
152
153 @Override
Kenny Rootd508e1e2019-11-15 10:20:59 -0800154 public void onStart() {
Kenny Rootf76cfc32019-11-08 14:36:03 -0800155 mRecoverySystemService = new RecoverySystemService(getContext());
156 publishBinderService(Context.RECOVERY_SERVICE, mRecoverySystemService);
Kenny Rootd508e1e2019-11-15 10:20:59 -0800157 }
Tao Baoe8a403d2015-12-31 07:44:55 -0800158 }
159
Kenny Rootd508e1e2019-11-15 10:20:59 -0800160 private RecoverySystemService(Context context) {
161 this(new Injector(context));
162 }
Tao Baoe8a403d2015-12-31 07:44:55 -0800163
Kenny Rootd508e1e2019-11-15 10:20:59 -0800164 @VisibleForTesting
165 RecoverySystemService(Injector injector) {
166 mInjector = injector;
167 mContext = injector.getContext();
168 }
Tao Baoe8a403d2015-12-31 07:44:55 -0800169
Kenny Rootf76cfc32019-11-08 14:36:03 -0800170 @VisibleForTesting
171 void onSystemServicesReady() {
172 mInjector.getLockSettingsService().setRebootEscrowListener(this);
173 }
174
Kenny Rootd508e1e2019-11-15 10:20:59 -0800175 @Override // Binder call
176 public boolean uncrypt(String filename, IRecoverySystemProgressListener listener) {
177 if (DEBUG) Slog.d(TAG, "uncrypt: " + filename);
Tao Baoe8a403d2015-12-31 07:44:55 -0800178
Kenny Rootd508e1e2019-11-15 10:20:59 -0800179 synchronized (sRequestLock) {
180 mContext.enforceCallingOrSelfPermission(android.Manifest.permission.RECOVERY, null);
Tao Bao794c8b02016-09-27 11:15:42 -0700181
Kenny Rootd508e1e2019-11-15 10:20:59 -0800182 if (!checkAndWaitForUncryptService()) {
183 Slog.e(TAG, "uncrypt service is unavailable.");
184 return false;
185 }
Tao Bao794c8b02016-09-27 11:15:42 -0700186
Kenny Rootd508e1e2019-11-15 10:20:59 -0800187 // Write the filename into uncrypt package file to be read by
188 // uncrypt.
189 mInjector.uncryptPackageFileDelete();
Tao Bao794c8b02016-09-27 11:15:42 -0700190
Kenny Rootd508e1e2019-11-15 10:20:59 -0800191 try (FileWriter uncryptFile = mInjector.getUncryptPackageFileWriter()) {
192 uncryptFile.write(filename + "\n");
193 } catch (IOException e) {
194 Slog.e(TAG, "IOException when writing \""
195 + mInjector.getUncryptPackageFileName() + "\":", e);
196 return false;
197 }
Tao Bao794c8b02016-09-27 11:15:42 -0700198
Kenny Rootd508e1e2019-11-15 10:20:59 -0800199 // Trigger uncrypt via init.
200 mInjector.systemPropertiesSet("ctl.start", "uncrypt");
Tao Bao794c8b02016-09-27 11:15:42 -0700201
Kenny Rootd508e1e2019-11-15 10:20:59 -0800202 // Connect to the uncrypt service socket.
203 UncryptSocket socket = mInjector.connectService();
204 if (socket == null) {
205 Slog.e(TAG, "Failed to connect to uncrypt socket");
206 return false;
207 }
208
209 // Read the status from the socket.
210 try {
211 int lastStatus = Integer.MIN_VALUE;
212 while (true) {
213 int status = socket.getPercentageUncrypted();
214 // Avoid flooding the log with the same message.
215 if (status == lastStatus && lastStatus != Integer.MIN_VALUE) {
216 continue;
Tao Bao794c8b02016-09-27 11:15:42 -0700217 }
Kenny Rootd508e1e2019-11-15 10:20:59 -0800218 lastStatus = status;
Tao Bao794c8b02016-09-27 11:15:42 -0700219
Kenny Rootd508e1e2019-11-15 10:20:59 -0800220 if (status >= 0 && status <= 100) {
221 // Update status
222 Slog.i(TAG, "uncrypt read status: " + status);
223 if (listener != null) {
224 try {
225 listener.onProgress(status);
226 } catch (RemoteException ignored) {
227 Slog.w(TAG, "RemoteException when posting progress");
228 }
229 }
230 if (status == 100) {
231 Slog.i(TAG, "uncrypt successfully finished.");
232 // Ack receipt of the final status code. uncrypt
233 // waits for the ack so the socket won't be
234 // destroyed before we receive the code.
235 socket.sendAck();
236 break;
237 }
238 } else {
239 // Error in /system/bin/uncrypt.
240 Slog.e(TAG, "uncrypt failed with status: " + status);
241 // Ack receipt of the final status code. uncrypt waits
242 // for the ack so the socket won't be destroyed before
243 // we receive the code.
244 socket.sendAck();
245 return false;
246 }
247 }
248 } catch (IOException e) {
249 Slog.e(TAG, "IOException when reading status: ", e);
250 return false;
251 } finally {
252 socket.close();
253 }
254
255 return true;
256 }
257 }
258
259 @Override // Binder call
260 public boolean clearBcb() {
261 if (DEBUG) Slog.d(TAG, "clearBcb");
262 synchronized (sRequestLock) {
263 return setupOrClearBcb(false, null);
264 }
265 }
266
267 @Override // Binder call
268 public boolean setupBcb(String command) {
269 if (DEBUG) Slog.d(TAG, "setupBcb: [" + command + "]");
270 synchronized (sRequestLock) {
271 return setupOrClearBcb(true, command);
272 }
273 }
274
275 @Override // Binder call
276 public void rebootRecoveryWithCommand(String command) {
277 if (DEBUG) Slog.d(TAG, "rebootRecoveryWithCommand: [" + command + "]");
278 synchronized (sRequestLock) {
279 if (!setupOrClearBcb(true, command)) {
280 return;
281 }
282
283 // Having set up the BCB, go ahead and reboot.
284 PowerManager pm = mInjector.getPowerManager();
285 pm.reboot(PowerManager.REBOOT_RECOVERY);
286 }
287 }
288
Kenny Rootf76cfc32019-11-08 14:36:03 -0800289 @Override // Binder call
290 public boolean requestLskf(String updateToken, IntentSender intentSender) {
291 mContext.enforceCallingOrSelfPermission(android.Manifest.permission.RECOVERY, null);
292
293 if (updateToken == null) {
294 return false;
295 }
296
297 // No need to prepare again for the same token.
298 if (mPreparedForReboot && updateToken.equals(mUnattendedRebootToken)) {
299 return true;
300 }
301
302 mPreparedForReboot = false;
303 mUnattendedRebootToken = updateToken;
304 mPreparedForRebootIntentSender = intentSender;
305
306 final long origId = Binder.clearCallingIdentity();
307 try {
308 mInjector.getLockSettingsService().prepareRebootEscrow();
309 } finally {
310 Binder.restoreCallingIdentity(origId);
311 }
312
313 return true;
314 }
315
316 @Override
317 public void onPreparedForReboot(boolean ready) {
318 if (mUnattendedRebootToken == null) {
319 Slog.w(TAG, "onPreparedForReboot called when mUnattendedRebootToken is null");
320 }
321
322 mPreparedForReboot = ready;
323 if (ready) {
324 sendPreparedForRebootIntentIfNeeded();
325 }
326 }
327
328 private void sendPreparedForRebootIntentIfNeeded() {
329 final IntentSender intentSender = mPreparedForRebootIntentSender;
330 if (intentSender != null) {
331 try {
332 intentSender.sendIntent(null, 0, null, null, null);
333 } catch (IntentSender.SendIntentException e) {
334 Slog.w(TAG, "Could not send intent for prepared reboot: " + e.getMessage());
335 }
336 }
337 }
338
339 @Override // Binder call
340 public boolean clearLskf() {
341 mContext.enforceCallingOrSelfPermission(android.Manifest.permission.RECOVERY, null);
342
343 mPreparedForReboot = false;
344 mUnattendedRebootToken = null;
345 mPreparedForRebootIntentSender = null;
346
347 final long origId = Binder.clearCallingIdentity();
348 try {
349 mInjector.getLockSettingsService().clearRebootEscrow();
350 } finally {
351 Binder.restoreCallingIdentity(origId);
352 }
353
354 return true;
355 }
356
357 @Override // Binder call
358 public boolean rebootWithLskf(String updateToken, String reason) {
359 mContext.enforceCallingOrSelfPermission(android.Manifest.permission.RECOVERY, null);
360
361 if (!mPreparedForReboot) {
362 return false;
363 }
364
365 if (updateToken != null && updateToken.equals(mUnattendedRebootToken)) {
366 if (!mInjector.getLockSettingsService().armRebootEscrow()) {
367 return false;
368 }
369
370 PowerManager pm = mInjector.getPowerManager();
371 pm.reboot(reason);
372 return true;
373 }
374
375 return false;
376 }
377
Kenny Rootd508e1e2019-11-15 10:20:59 -0800378 /**
379 * Check if any of the init services is still running. If so, we cannot
380 * start a new uncrypt/setup-bcb/clear-bcb service right away; otherwise
381 * it may break the socket communication since init creates / deletes
382 * the socket (/dev/socket/uncrypt) on service start / exit.
383 */
384 private boolean checkAndWaitForUncryptService() {
385 for (int retry = 0; retry < SOCKET_CONNECTION_MAX_RETRY; retry++) {
386 final String uncryptService = mInjector.systemPropertiesGet(INIT_SERVICE_UNCRYPT);
387 final String setupBcbService = mInjector.systemPropertiesGet(INIT_SERVICE_SETUP_BCB);
388 final String clearBcbService = mInjector.systemPropertiesGet(INIT_SERVICE_CLEAR_BCB);
389 final boolean busy = "running".equals(uncryptService)
390 || "running".equals(setupBcbService) || "running".equals(clearBcbService);
391 if (DEBUG) {
392 Slog.i(TAG, "retry: " + retry + " busy: " + busy
393 + " uncrypt: [" + uncryptService + "]"
394 + " setupBcb: [" + setupBcbService + "]"
395 + " clearBcb: [" + clearBcbService + "]");
396 }
397
398 if (!busy) {
Tao Bao794c8b02016-09-27 11:15:42 -0700399 return true;
400 }
Tao Baoe8a403d2015-12-31 07:44:55 -0800401
Kenny Rootd508e1e2019-11-15 10:20:59 -0800402 try {
403 mInjector.threadSleep(1000);
404 } catch (InterruptedException e) {
405 Slog.w(TAG, "Interrupted:", e);
Tao Bao794c8b02016-09-27 11:15:42 -0700406 }
Tao Baoe8a403d2015-12-31 07:44:55 -0800407 }
408
Kenny Rootd508e1e2019-11-15 10:20:59 -0800409 return false;
410 }
Tao Bao794c8b02016-09-27 11:15:42 -0700411
Kenny Rootd508e1e2019-11-15 10:20:59 -0800412 private boolean setupOrClearBcb(boolean isSetup, String command) {
413 mContext.enforceCallingOrSelfPermission(android.Manifest.permission.RECOVERY, null);
Tao Bao794c8b02016-09-27 11:15:42 -0700414
Kenny Rootd508e1e2019-11-15 10:20:59 -0800415 final boolean available = checkAndWaitForUncryptService();
416 if (!available) {
417 Slog.e(TAG, "uncrypt service is unavailable.");
Tao Bao794c8b02016-09-27 11:15:42 -0700418 return false;
Tao Baoe8a403d2015-12-31 07:44:55 -0800419 }
420
Kenny Rootd508e1e2019-11-15 10:20:59 -0800421 if (isSetup) {
422 mInjector.systemPropertiesSet("ctl.start", "setup-bcb");
423 } else {
424 mInjector.systemPropertiesSet("ctl.start", "clear-bcb");
425 }
426
427 // Connect to the uncrypt service socket.
428 UncryptSocket socket = mInjector.connectService();
429 if (socket == null) {
430 Slog.e(TAG, "Failed to connect to uncrypt socket");
431 return false;
432 }
433
434 try {
435 // Send the BCB commands if it's to setup BCB.
436 if (isSetup) {
437 socket.sendCommand(command);
438 }
439
440 // Read the status from the socket.
441 int status = socket.getPercentageUncrypted();
442
443 // Ack receipt of the status code. uncrypt waits for the ack so
444 // the socket won't be destroyed before we receive the code.
445 socket.sendAck();
446
447 if (status == 100) {
448 Slog.i(TAG, "uncrypt " + (isSetup ? "setup" : "clear")
449 + " bcb successfully finished.");
450 } else {
451 // Error in /system/bin/uncrypt.
452 Slog.e(TAG, "uncrypt failed with status: " + status);
453 return false;
454 }
455 } catch (IOException e) {
456 Slog.e(TAG, "IOException when communicating with uncrypt:", e);
457 return false;
458 } finally {
459 socket.close();
460 }
461
462 return true;
463 }
464
465 /**
466 * Provides a wrapper for the low-level details of framing packets sent to the uncrypt
467 * socket.
468 */
469 public static class UncryptSocket {
470 private LocalSocket mLocalSocket;
471 private DataInputStream mInputStream;
472 private DataOutputStream mOutputStream;
473
474 /**
475 * Attempt to connect to the uncrypt service. Connection will be retried for up to
476 * {@link #SOCKET_CONNECTION_MAX_RETRY} times. If the connection is unsuccessful, the
477 * socket will be closed. If the connection is successful, the connection must be closed
478 * by the caller.
479 *
480 * @return true if connection was successful, false if unsuccessful
481 */
482 public boolean connectService() {
483 mLocalSocket = new LocalSocket();
Tao Baodd3baae2016-02-26 10:28:58 -0800484 boolean done = false;
485 // The uncrypt socket will be created by init upon receiving the
486 // service request. It may not be ready by this point. So we will
487 // keep retrying until success or reaching timeout.
488 for (int retry = 0; retry < SOCKET_CONNECTION_MAX_RETRY; retry++) {
489 try {
Kenny Rootd508e1e2019-11-15 10:20:59 -0800490 mLocalSocket.connect(new LocalSocketAddress(UNCRYPT_SOCKET,
Tao Baodd3baae2016-02-26 10:28:58 -0800491 LocalSocketAddress.Namespace.RESERVED));
492 done = true;
493 break;
Tao Bao12844822016-03-22 10:42:32 -0700494 } catch (IOException ignored) {
Tao Baodd3baae2016-02-26 10:28:58 -0800495 try {
496 Thread.sleep(1000);
497 } catch (InterruptedException e) {
Tao Bao794c8b02016-09-27 11:15:42 -0700498 Slog.w(TAG, "Interrupted:", e);
Tao Baodd3baae2016-02-26 10:28:58 -0800499 }
Tao Baoe8a403d2015-12-31 07:44:55 -0800500 }
501 }
Tao Baodd3baae2016-02-26 10:28:58 -0800502 if (!done) {
503 Slog.e(TAG, "Timed out connecting to uncrypt socket");
Kenny Rootd508e1e2019-11-15 10:20:59 -0800504 close();
Tao Bao794c8b02016-09-27 11:15:42 -0700505 return false;
506 }
507
Tao Bao12844822016-03-22 10:42:32 -0700508 try {
Kenny Rootd508e1e2019-11-15 10:20:59 -0800509 mInputStream = new DataInputStream(mLocalSocket.getInputStream());
510 mOutputStream = new DataOutputStream(mLocalSocket.getOutputStream());
Tao Baodd3baae2016-02-26 10:28:58 -0800511 } catch (IOException e) {
Kenny Rootd508e1e2019-11-15 10:20:59 -0800512 close();
Tao Baodd3baae2016-02-26 10:28:58 -0800513 return false;
Tao Baodd3baae2016-02-26 10:28:58 -0800514 }
515
Tao Baoe8a403d2015-12-31 07:44:55 -0800516 return true;
517 }
Kenny Rootd508e1e2019-11-15 10:20:59 -0800518
519 /**
520 * Sends a command to the uncrypt service.
521 *
522 * @param command command to send to the uncrypt service
Kenny Rootc6c26192019-11-25 10:26:53 -0800523 * @throws IOException if there was an error writing to the socket
Kenny Rootd508e1e2019-11-15 10:20:59 -0800524 */
525 public void sendCommand(String command) throws IOException {
Kenny Rootd508e1e2019-11-15 10:20:59 -0800526 byte[] cmdUtf8 = command.getBytes(StandardCharsets.UTF_8);
527 mOutputStream.writeInt(cmdUtf8.length);
528 mOutputStream.write(cmdUtf8, 0, cmdUtf8.length);
529 }
530
531 /**
532 * Reads the status from the uncrypt service which is usually represented as a percentage.
533 * @return an integer representing the percentage completed
Kenny Rootc6c26192019-11-25 10:26:53 -0800534 * @throws IOException if there was an error reading the socket
Kenny Rootd508e1e2019-11-15 10:20:59 -0800535 */
536 public int getPercentageUncrypted() throws IOException {
Kenny Rootd508e1e2019-11-15 10:20:59 -0800537 return mInputStream.readInt();
538 }
539
540 /**
541 * Sends a confirmation to the uncrypt service.
Kenny Rootc6c26192019-11-25 10:26:53 -0800542 * @throws IOException if there was an error writing to the socket
Kenny Rootd508e1e2019-11-15 10:20:59 -0800543 */
544 public void sendAck() throws IOException {
Kenny Rootd508e1e2019-11-15 10:20:59 -0800545 mOutputStream.writeInt(0);
546 }
547
548 /**
549 * Closes the socket and all underlying data streams.
550 */
551 public void close() {
552 IoUtils.closeQuietly(mInputStream);
553 IoUtils.closeQuietly(mOutputStream);
554 IoUtils.closeQuietly(mLocalSocket);
555 }
Tao Baoe8a403d2015-12-31 07:44:55 -0800556 }
Kenny Root4ad77bf2019-12-16 17:23:43 -0800557
558 private boolean isCallerShell() {
559 final int callingUid = Binder.getCallingUid();
560 return callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID;
561 }
562
563 private void enforceShell() {
564 if (!isCallerShell()) {
565 throw new SecurityException("Caller must be shell");
566 }
567 }
568
569 @Override
570 public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
571 String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
572 enforceShell();
573 final long origId = Binder.clearCallingIdentity();
574 try {
575 new RecoverySystemShellCommand(this).exec(
576 this, in, out, err, args, callback, resultReceiver);
577 } finally {
578 Binder.restoreCallingIdentity(origId);
579 }
580 }
Tao Baoe8a403d2015-12-31 07:44:55 -0800581}