blob: 6f378fd42a89f4f78818eef6681cda00d110102e [file] [log] [blame]
Andres Morales963295e2014-07-10 15:40:24 -07001/*
2 * Copyright (C) 2014 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
Andres Morales68d4acd2014-07-01 19:40:41 -070017package com.android.server;
18
19import android.Manifest;
Guang Zhu514c5802014-09-12 15:14:00 -070020import android.app.ActivityManager;
Andres Morales68d4acd2014-07-01 19:40:41 -070021import android.content.Context;
22import android.content.pm.PackageManager;
23import android.os.Binder;
24import android.os.IBinder;
25import android.os.RemoteException;
26import android.os.SystemProperties;
Andres Morales6429f312014-08-04 16:35:15 -070027import android.os.UserHandle;
Andres Morales68d4acd2014-07-01 19:40:41 -070028import android.service.persistentdata.IPersistentDataBlockService;
Andres Morales963295e2014-07-10 15:40:24 -070029import android.util.Slog;
Guang Zhu514c5802014-09-12 15:14:00 -070030
Andres Morales68d4acd2014-07-01 19:40:41 -070031import com.android.internal.R;
Guang Zhu514c5802014-09-12 15:14:00 -070032
Andres Morales963295e2014-07-10 15:40:24 -070033import libcore.io.IoUtils;
Andres Morales68d4acd2014-07-01 19:40:41 -070034
35import java.io.DataInputStream;
36import java.io.DataOutputStream;
37import java.io.File;
38import java.io.FileInputStream;
39import java.io.FileNotFoundException;
40import java.io.FileOutputStream;
41import java.io.IOException;
42import java.nio.ByteBuffer;
43import java.nio.channels.FileChannel;
44
45/**
46 * Service for reading and writing blocks to a persistent partition.
Andres Morales963295e2014-07-10 15:40:24 -070047 * This data will live across factory resets not initiated via the Settings UI.
48 * When a device is factory reset through Settings this data is wiped.
Andres Morales68d4acd2014-07-01 19:40:41 -070049 *
50 * Allows writing one block at a time. Namely, each time
51 * {@link android.service.persistentdata.IPersistentDataBlockService}.write(byte[] data)
52 * is called, it will overwite the data that was previously written on the block.
53 *
54 * Clients can query the size of the currently written block via
55 * {@link android.service.persistentdata.IPersistentDataBlockService}.getTotalDataSize().
56 *
57 * Clients can any number of bytes from the currently written block up to its total size by invoking
58 * {@link android.service.persistentdata.IPersistentDataBlockService}.read(byte[] data)
59 */
60public class PersistentDataBlockService extends SystemService {
61 private static final String TAG = PersistentDataBlockService.class.getSimpleName();
62
63 private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst";
64 private static final int HEADER_SIZE = 8;
Andres Morales963295e2014-07-10 15:40:24 -070065 // Magic number to mark block device as adhering to the format consumed by this service
66 private static final int PARTITION_TYPE_MARKER = 0x1990;
67 // Limit to 100k as blocks larger than this might cause strain on Binder.
68 // TODO(anmorales): Consider splitting up too-large blocks in PersistentDataBlockManager
69 private static final int MAX_DATA_BLOCK_SIZE = 1024 * 100;
Andres Morales68d4acd2014-07-01 19:40:41 -070070
71 private final Context mContext;
72 private final String mDataBlockFile;
Andres Morales963295e2014-07-10 15:40:24 -070073 private final Object mLock = new Object();
Andres Morales6429f312014-08-04 16:35:15 -070074
75 private int mAllowedAppId = -1;
Andres Morales963295e2014-07-10 15:40:24 -070076 /*
77 * Separate lock for OEM unlock related operations as they can happen in parallel with regular
78 * block operations.
79 */
80 private final Object mOemLock = new Object();
81
82 private long mBlockDeviceSize;
Andres Morales68d4acd2014-07-01 19:40:41 -070083
84 public PersistentDataBlockService(Context context) {
85 super(context);
86 mContext = context;
87 mDataBlockFile = SystemProperties.get(PERSISTENT_DATA_BLOCK_PROP);
Andres Morales963295e2014-07-10 15:40:24 -070088 mBlockDeviceSize = -1; // Load lazily
Andres Morales6429f312014-08-04 16:35:15 -070089 mAllowedAppId = getAllowedAppId(UserHandle.USER_OWNER);
90 }
91
92
93 private int getAllowedAppId(int userHandle) {
94 String allowedPackage = mContext.getResources()
Andres Morales68d4acd2014-07-01 19:40:41 -070095 .getString(R.string.config_persistentDataPackageName);
96 PackageManager pm = mContext.getPackageManager();
97 int allowedUid = -1;
98 try {
Andres Morales6429f312014-08-04 16:35:15 -070099 allowedUid = pm.getPackageUid(allowedPackage, userHandle);
Andres Morales68d4acd2014-07-01 19:40:41 -0700100 } catch (PackageManager.NameNotFoundException e) {
101 // not expected
Andres Morales963295e2014-07-10 15:40:24 -0700102 Slog.e(TAG, "not able to find package " + allowedPackage, e);
Andres Morales68d4acd2014-07-01 19:40:41 -0700103 }
Andres Morales6429f312014-08-04 16:35:15 -0700104 return UserHandle.getAppId(allowedUid);
Andres Morales68d4acd2014-07-01 19:40:41 -0700105 }
106
107 @Override
108 public void onStart() {
109 publishBinderService(Context.PERSISTENT_DATA_BLOCK_SERVICE, mService);
110 }
111
112 private void enforceOemUnlockPermission() {
113 mContext.enforceCallingOrSelfPermission(
114 Manifest.permission.OEM_UNLOCK_STATE,
115 "Can't access OEM unlock state");
116 }
117
118 private void enforceUid(int callingUid) {
Andres Morales6429f312014-08-04 16:35:15 -0700119 if (UserHandle.getAppId(callingUid) != mAllowedAppId) {
Andres Morales68d4acd2014-07-01 19:40:41 -0700120 throw new SecurityException("uid " + callingUid + " not allowed to access PST");
121 }
122 }
123
Andres Morales963295e2014-07-10 15:40:24 -0700124 private int getTotalDataSizeLocked(DataInputStream inputStream) throws IOException {
Andres Morales68d4acd2014-07-01 19:40:41 -0700125 int totalDataSize;
126 int blockId = inputStream.readInt();
Andres Morales963295e2014-07-10 15:40:24 -0700127 if (blockId == PARTITION_TYPE_MARKER) {
Andres Morales68d4acd2014-07-01 19:40:41 -0700128 totalDataSize = inputStream.readInt();
129 } else {
130 totalDataSize = 0;
131 }
132 return totalDataSize;
133 }
134
Andres Morales963295e2014-07-10 15:40:24 -0700135 private long getBlockDeviceSize() {
136 synchronized (mLock) {
137 if (mBlockDeviceSize == -1) {
138 mBlockDeviceSize = nativeGetBlockDeviceSize(mDataBlockFile);
Andres Morales68d4acd2014-07-01 19:40:41 -0700139 }
140 }
141
142 return mBlockDeviceSize;
143 }
144
Andres Morales963295e2014-07-10 15:40:24 -0700145 private native long nativeGetBlockDeviceSize(String path);
146 private native int nativeWipe(String path);
Andres Morales68d4acd2014-07-01 19:40:41 -0700147
148 private final IBinder mService = new IPersistentDataBlockService.Stub() {
149 @Override
150 public int write(byte[] data) throws RemoteException {
151 enforceUid(Binder.getCallingUid());
152
153 // Need to ensure we don't write over the last byte
Andres Morales963295e2014-07-10 15:40:24 -0700154 long maxBlockSize = getBlockDeviceSize() - HEADER_SIZE - 1;
155 if (data.length > maxBlockSize) {
156 // partition is ~500k so shouldn't be a problem to downcast
157 return (int) -maxBlockSize;
Andres Morales68d4acd2014-07-01 19:40:41 -0700158 }
159
160 DataOutputStream outputStream;
161 try {
162 outputStream = new DataOutputStream(new FileOutputStream(new File(mDataBlockFile)));
163 } catch (FileNotFoundException e) {
Andres Morales963295e2014-07-10 15:40:24 -0700164 Slog.e(TAG, "partition not available?", e);
Andres Morales68d4acd2014-07-01 19:40:41 -0700165 return -1;
166 }
167
168 ByteBuffer headerAndData = ByteBuffer.allocate(data.length + HEADER_SIZE);
Andres Morales963295e2014-07-10 15:40:24 -0700169 headerAndData.putInt(PARTITION_TYPE_MARKER);
Andres Morales68d4acd2014-07-01 19:40:41 -0700170 headerAndData.putInt(data.length);
171 headerAndData.put(data);
172
173 try {
Andres Morales963295e2014-07-10 15:40:24 -0700174 synchronized (mLock) {
175 outputStream.write(headerAndData.array());
176 return data.length;
177 }
Andres Morales68d4acd2014-07-01 19:40:41 -0700178 } catch (IOException e) {
Andres Morales963295e2014-07-10 15:40:24 -0700179 Slog.e(TAG, "failed writing to the persistent data block", e);
Andres Morales68d4acd2014-07-01 19:40:41 -0700180 return -1;
181 } finally {
182 try {
183 outputStream.close();
184 } catch (IOException e) {
Andres Morales963295e2014-07-10 15:40:24 -0700185 Slog.e(TAG, "failed closing output stream", e);
Andres Morales68d4acd2014-07-01 19:40:41 -0700186 }
187 }
188 }
189
190 @Override
Andres Morales963295e2014-07-10 15:40:24 -0700191 public byte[] read() {
Andres Morales68d4acd2014-07-01 19:40:41 -0700192 enforceUid(Binder.getCallingUid());
193
194 DataInputStream inputStream;
195 try {
196 inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
197 } catch (FileNotFoundException e) {
Andres Morales963295e2014-07-10 15:40:24 -0700198 Slog.e(TAG, "partition not available?", e);
199 return null;
Andres Morales68d4acd2014-07-01 19:40:41 -0700200 }
201
202 try {
Andres Morales963295e2014-07-10 15:40:24 -0700203 synchronized (mLock) {
204 int totalDataSize = getTotalDataSizeLocked(inputStream);
205
206 if (totalDataSize == 0) {
207 return new byte[0];
208 }
209
210 byte[] data = new byte[totalDataSize];
211 int read = inputStream.read(data, 0, totalDataSize);
212 if (read < totalDataSize) {
213 // something went wrong, not returning potentially corrupt data
214 Slog.e(TAG, "failed to read entire data block. bytes read: " +
215 read + "/" + totalDataSize);
216 return null;
217 }
218 return data;
219 }
Andres Morales68d4acd2014-07-01 19:40:41 -0700220 } catch (IOException e) {
Andres Morales963295e2014-07-10 15:40:24 -0700221 Slog.e(TAG, "failed to read data", e);
222 return null;
Andres Morales68d4acd2014-07-01 19:40:41 -0700223 } finally {
224 try {
225 inputStream.close();
226 } catch (IOException e) {
Andres Morales963295e2014-07-10 15:40:24 -0700227 Slog.e(TAG, "failed to close OutputStream");
228 }
229 }
230 }
231
232 @Override
233 public void wipe() {
234 enforceOemUnlockPermission();
235
236 synchronized (mLock) {
237 int ret = nativeWipe(mDataBlockFile);
238
239 if (ret < 0) {
240 Slog.e(TAG, "failed to wipe persistent partition");
Andres Morales68d4acd2014-07-01 19:40:41 -0700241 }
242 }
243 }
244
245 @Override
246 public void setOemUnlockEnabled(boolean enabled) {
Guang Zhu514c5802014-09-12 15:14:00 -0700247 // do not allow monkey to flip the flag
248 if (ActivityManager.isUserAMonkey()) {
249 return;
250 }
Andres Morales68d4acd2014-07-01 19:40:41 -0700251 enforceOemUnlockPermission();
252 FileOutputStream outputStream;
253 try {
254 outputStream = new FileOutputStream(new File(mDataBlockFile));
255 } catch (FileNotFoundException e) {
Andres Morales963295e2014-07-10 15:40:24 -0700256 Slog.e(TAG, "parition not available", e);
Andres Morales68d4acd2014-07-01 19:40:41 -0700257 return;
258 }
259
260 try {
261 FileChannel channel = outputStream.getChannel();
262
Andres Morales963295e2014-07-10 15:40:24 -0700263 channel.position(getBlockDeviceSize() - 1);
Andres Morales68d4acd2014-07-01 19:40:41 -0700264
265 ByteBuffer data = ByteBuffer.allocate(1);
266 data.put(enabled ? (byte) 1 : (byte) 0);
267 data.flip();
268
Andres Morales963295e2014-07-10 15:40:24 -0700269 synchronized (mOemLock) {
270 channel.write(data);
Andres Morales68d4acd2014-07-01 19:40:41 -0700271 }
Andres Morales963295e2014-07-10 15:40:24 -0700272 } catch (IOException e) {
273 Slog.e(TAG, "unable to access persistent partition", e);
274 } finally {
275 IoUtils.closeQuietly(outputStream);
Andres Morales68d4acd2014-07-01 19:40:41 -0700276 }
277 }
278
279 @Override
280 public boolean getOemUnlockEnabled() {
281 enforceOemUnlockPermission();
282 DataInputStream inputStream;
283 try {
284 inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
285 } catch (FileNotFoundException e) {
Andres Morales963295e2014-07-10 15:40:24 -0700286 Slog.e(TAG, "partition not available");
Andres Morales68d4acd2014-07-01 19:40:41 -0700287 return false;
288 }
289
290 try {
Andres Morales963295e2014-07-10 15:40:24 -0700291 inputStream.skip(getBlockDeviceSize() - 1);
292 synchronized (mOemLock) {
293 return inputStream.readByte() != 0;
294 }
Andres Morales68d4acd2014-07-01 19:40:41 -0700295 } catch (IOException e) {
Andres Morales963295e2014-07-10 15:40:24 -0700296 Slog.e(TAG, "unable to access persistent partition", e);
Andres Morales68d4acd2014-07-01 19:40:41 -0700297 return false;
298 } finally {
Andres Morales963295e2014-07-10 15:40:24 -0700299 IoUtils.closeQuietly(inputStream);
Andres Morales68d4acd2014-07-01 19:40:41 -0700300 }
301 }
302
303 @Override
304 public int getDataBlockSize() {
305 enforceUid(Binder.getCallingUid());
306
307 DataInputStream inputStream;
308 try {
309 inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
310 } catch (FileNotFoundException e) {
Andres Morales963295e2014-07-10 15:40:24 -0700311 Slog.e(TAG, "partition not available");
Andres Morales68d4acd2014-07-01 19:40:41 -0700312 return 0;
313 }
314
315 try {
Andres Morales963295e2014-07-10 15:40:24 -0700316 synchronized (mLock) {
317 return getTotalDataSizeLocked(inputStream);
318 }
Andres Morales68d4acd2014-07-01 19:40:41 -0700319 } catch (IOException e) {
Andres Morales963295e2014-07-10 15:40:24 -0700320 Slog.e(TAG, "error reading data block size");
Andres Morales68d4acd2014-07-01 19:40:41 -0700321 return 0;
322 } finally {
Andres Morales963295e2014-07-10 15:40:24 -0700323 IoUtils.closeQuietly(inputStream);
Andres Morales68d4acd2014-07-01 19:40:41 -0700324 }
325 }
Andres Morales963295e2014-07-10 15:40:24 -0700326
327 @Override
328 public long getMaximumDataBlockSize() {
329 long actualSize = getBlockDeviceSize() - HEADER_SIZE - 1;
330 return actualSize <= MAX_DATA_BLOCK_SIZE ? actualSize : MAX_DATA_BLOCK_SIZE;
331 }
332
Andres Morales68d4acd2014-07-01 19:40:41 -0700333 };
334}