blob: d237fe7b4c3e6eb8044bc6e05e7edd4e34fde8b3 [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
17package com.android.server;
18
19import android.content.Context;
20import android.os.IRecoverySystem;
21import android.os.IRecoverySystemProgressListener;
22import android.os.RecoverySystem;
23import android.os.RemoteException;
24import android.os.SystemProperties;
25import android.system.ErrnoException;
26import android.system.Os;
27import android.util.Slog;
28
29import java.io.BufferedReader;
30import java.io.File;
31import java.io.FileReader;
32import java.io.FileWriter;
33import java.io.IOException;
34
35/**
36 * The recovery system service is responsible for coordinating recovery related
37 * functions on the device. It sets up (or clears) the bootloader control block
38 * (BCB), which will be read by the bootloader and the recovery image. It also
39 * triggers /system/bin/uncrypt via init to de-encrypt an OTA package on the
40 * /data partition so that it can be accessed under the recovery image.
41 */
42public final class RecoverySystemService extends SystemService {
43 private static final String TAG = "RecoverySystemService";
44 private static final boolean DEBUG = false;
45
46 // A pipe file to monitor the uncrypt progress.
47 private static final String UNCRYPT_STATUS_FILE = "/cache/recovery/uncrypt_status";
48 // Temporary command file to communicate between the system server and uncrypt.
49 private static final String COMMAND_FILE = "/cache/recovery/command";
50
51 private Context mContext;
52
53 public RecoverySystemService(Context context) {
54 super(context);
55 mContext = context;
56 }
57
58 @Override
59 public void onStart() {
60 publishBinderService(Context.RECOVERY_SERVICE, new BinderService());
61 }
62
63 private final class BinderService extends IRecoverySystem.Stub {
64 @Override // Binder call
65 public boolean uncrypt(String filename, IRecoverySystemProgressListener listener) {
66 if (DEBUG) Slog.d(TAG, "uncrypt: " + filename);
67
68 mContext.enforceCallingOrSelfPermission(android.Manifest.permission.RECOVERY, null);
69
70 // Write the filename into UNCRYPT_PACKAGE_FILE to be read by
71 // uncrypt.
72 RecoverySystem.UNCRYPT_PACKAGE_FILE.delete();
73
74 try (FileWriter uncryptFile = new FileWriter(RecoverySystem.UNCRYPT_PACKAGE_FILE)) {
75 uncryptFile.write(filename + "\n");
76 } catch (IOException e) {
77 Slog.e(TAG, "IOException when writing \"" + RecoverySystem.UNCRYPT_PACKAGE_FILE +
78 "\": " + e.getMessage());
79 return false;
80 }
81
82 // Create the status pipe file to communicate with uncrypt.
83 new File(UNCRYPT_STATUS_FILE).delete();
84 try {
85 Os.mkfifo(UNCRYPT_STATUS_FILE, 0600);
86 } catch (ErrnoException e) {
87 Slog.e(TAG, "ErrnoException when creating named pipe \"" + UNCRYPT_STATUS_FILE +
88 "\": " + e.getMessage());
89 return false;
90 }
91
92 // Trigger uncrypt via init.
93 SystemProperties.set("ctl.start", "uncrypt");
94
95 // Read the status from the pipe.
96 try (BufferedReader reader = new BufferedReader(new FileReader(UNCRYPT_STATUS_FILE))) {
97 int lastStatus = Integer.MIN_VALUE;
98 while (true) {
99 String str = reader.readLine();
100 try {
101 int status = Integer.parseInt(str);
102
103 // Avoid flooding the log with the same message.
104 if (status == lastStatus && lastStatus != Integer.MIN_VALUE) {
105 continue;
106 }
107 lastStatus = status;
108
109 if (status >= 0 && status <= 100) {
110 // Update status
111 Slog.i(TAG, "uncrypt read status: " + status);
112 if (listener != null) {
113 try {
114 listener.onProgress(status);
115 } catch (RemoteException unused) {
116 Slog.w(TAG, "RemoteException when posting progress");
117 }
118 }
119 if (status == 100) {
120 Slog.i(TAG, "uncrypt successfully finished.");
121 break;
122 }
123 } else {
124 // Error in /system/bin/uncrypt.
125 Slog.e(TAG, "uncrypt failed with status: " + status);
126 return false;
127 }
128 } catch (NumberFormatException unused) {
129 Slog.e(TAG, "uncrypt invalid status received: " + str);
130 return false;
131 }
132 }
133 } catch (IOException unused) {
134 Slog.e(TAG, "IOException when reading \"" + UNCRYPT_STATUS_FILE + "\".");
135 return false;
136 }
137
138 return true;
139 }
140
141 @Override // Binder call
142 public boolean clearBcb() {
143 if (DEBUG) Slog.d(TAG, "clearBcb");
144 return setupOrClearBcb(false, null);
145 }
146
147 @Override // Binder call
148 public boolean setupBcb(String command) {
149 if (DEBUG) Slog.d(TAG, "setupBcb: [" + command + "]");
150 return setupOrClearBcb(true, command);
151 }
152
153 private boolean setupOrClearBcb(boolean isSetup, String command) {
154 mContext.enforceCallingOrSelfPermission(android.Manifest.permission.RECOVERY, null);
155
156 if (isSetup) {
157 // Set up the command file to be read by uncrypt.
158 try (FileWriter commandFile = new FileWriter(COMMAND_FILE)) {
159 commandFile.write(command + "\n");
160 } catch (IOException e) {
161 Slog.e(TAG, "IOException when writing \"" + COMMAND_FILE +
162 "\": " + e.getMessage());
163 return false;
164 }
165 }
166
167 // Create the status pipe file to communicate with uncrypt.
168 new File(UNCRYPT_STATUS_FILE).delete();
169 try {
170 Os.mkfifo(UNCRYPT_STATUS_FILE, 0600);
171 } catch (ErrnoException e) {
172 Slog.e(TAG, "ErrnoException when creating named pipe \"" +
173 UNCRYPT_STATUS_FILE + "\": " + e.getMessage());
174 return false;
175 }
176
177 if (isSetup) {
178 SystemProperties.set("ctl.start", "setup-bcb");
179 } else {
180 SystemProperties.set("ctl.start", "clear-bcb");
181 }
182
183 // Read the status from the pipe.
184 try (BufferedReader reader = new BufferedReader(new FileReader(UNCRYPT_STATUS_FILE))) {
185 while (true) {
186 String str = reader.readLine();
187 try {
188 int status = Integer.parseInt(str);
189
190 if (status == 100) {
191 Slog.i(TAG, "uncrypt " + (isSetup ? "setup" : "clear") +
192 " bcb successfully finished.");
193 break;
194 } else {
195 // Error in /system/bin/uncrypt.
196 Slog.e(TAG, "uncrypt failed with status: " + status);
197 return false;
198 }
199 } catch (NumberFormatException unused) {
200 Slog.e(TAG, "uncrypt invalid status received: " + str);
201 return false;
202 }
203 }
204 } catch (IOException unused) {
205 Slog.e(TAG, "IOException when reading \"" + UNCRYPT_STATUS_FILE + "\".");
206 return false;
207 }
208
209 // Delete the command file as we don't need it anymore.
210 new File(COMMAND_FILE).delete();
211 return true;
212 }
213 }
214}