blob: 2896f601c62714c4595aa21d3753dbc17383c057 [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;
20import android.content.Context;
21import android.content.pm.PackageManager;
22import android.os.Binder;
23import android.os.IBinder;
24import android.os.RemoteException;
25import android.os.SystemProperties;
Andres Morales6429f312014-08-04 16:35:15 -070026import android.os.UserHandle;
Andres Morales68d4acd2014-07-01 19:40:41 -070027import android.service.persistentdata.IPersistentDataBlockService;
Andres Morales963295e2014-07-10 15:40:24 -070028import android.util.Slog;
Andres Morales68d4acd2014-07-01 19:40:41 -070029import com.android.internal.R;
Andres Morales963295e2014-07-10 15:40:24 -070030import libcore.io.IoUtils;
Andres Morales68d4acd2014-07-01 19:40:41 -070031
32import java.io.DataInputStream;
33import java.io.DataOutputStream;
34import java.io.File;
35import java.io.FileInputStream;
36import java.io.FileNotFoundException;
37import java.io.FileOutputStream;
38import java.io.IOException;
39import java.nio.ByteBuffer;
40import java.nio.channels.FileChannel;
41
42/**
43 * Service for reading and writing blocks to a persistent partition.
Andres Morales963295e2014-07-10 15:40:24 -070044 * This data will live across factory resets not initiated via the Settings UI.
45 * When a device is factory reset through Settings this data is wiped.
Andres Morales68d4acd2014-07-01 19:40:41 -070046 *
47 * Allows writing one block at a time. Namely, each time
48 * {@link android.service.persistentdata.IPersistentDataBlockService}.write(byte[] data)
49 * is called, it will overwite the data that was previously written on the block.
50 *
51 * Clients can query the size of the currently written block via
52 * {@link android.service.persistentdata.IPersistentDataBlockService}.getTotalDataSize().
53 *
54 * Clients can any number of bytes from the currently written block up to its total size by invoking
55 * {@link android.service.persistentdata.IPersistentDataBlockService}.read(byte[] data)
56 */
57public class PersistentDataBlockService extends SystemService {
58 private static final String TAG = PersistentDataBlockService.class.getSimpleName();
59
60 private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst";
61 private static final int HEADER_SIZE = 8;
Andres Morales963295e2014-07-10 15:40:24 -070062 // Magic number to mark block device as adhering to the format consumed by this service
63 private static final int PARTITION_TYPE_MARKER = 0x1990;
64 // Limit to 100k as blocks larger than this might cause strain on Binder.
65 // TODO(anmorales): Consider splitting up too-large blocks in PersistentDataBlockManager
66 private static final int MAX_DATA_BLOCK_SIZE = 1024 * 100;
Andres Morales68d4acd2014-07-01 19:40:41 -070067
68 private final Context mContext;
69 private final String mDataBlockFile;
Andres Morales963295e2014-07-10 15:40:24 -070070 private final Object mLock = new Object();
Andres Morales6429f312014-08-04 16:35:15 -070071
72 private int mAllowedAppId = -1;
Andres Morales963295e2014-07-10 15:40:24 -070073 /*
74 * Separate lock for OEM unlock related operations as they can happen in parallel with regular
75 * block operations.
76 */
77 private final Object mOemLock = new Object();
78
79 private long mBlockDeviceSize;
Andres Morales68d4acd2014-07-01 19:40:41 -070080
81 public PersistentDataBlockService(Context context) {
82 super(context);
83 mContext = context;
84 mDataBlockFile = SystemProperties.get(PERSISTENT_DATA_BLOCK_PROP);
Andres Morales963295e2014-07-10 15:40:24 -070085 mBlockDeviceSize = -1; // Load lazily
Andres Morales6429f312014-08-04 16:35:15 -070086 mAllowedAppId = getAllowedAppId(UserHandle.USER_OWNER);
87 }
88
89
90 private int getAllowedAppId(int userHandle) {
91 String allowedPackage = mContext.getResources()
Andres Morales68d4acd2014-07-01 19:40:41 -070092 .getString(R.string.config_persistentDataPackageName);
93 PackageManager pm = mContext.getPackageManager();
94 int allowedUid = -1;
95 try {
Andres Morales6429f312014-08-04 16:35:15 -070096 allowedUid = pm.getPackageUid(allowedPackage, userHandle);
Andres Morales68d4acd2014-07-01 19:40:41 -070097 } catch (PackageManager.NameNotFoundException e) {
98 // not expected
Andres Morales963295e2014-07-10 15:40:24 -070099 Slog.e(TAG, "not able to find package " + allowedPackage, e);
Andres Morales68d4acd2014-07-01 19:40:41 -0700100 }
Andres Morales6429f312014-08-04 16:35:15 -0700101 return UserHandle.getAppId(allowedUid);
Andres Morales68d4acd2014-07-01 19:40:41 -0700102 }
103
104 @Override
105 public void onStart() {
106 publishBinderService(Context.PERSISTENT_DATA_BLOCK_SERVICE, mService);
107 }
108
109 private void enforceOemUnlockPermission() {
110 mContext.enforceCallingOrSelfPermission(
111 Manifest.permission.OEM_UNLOCK_STATE,
112 "Can't access OEM unlock state");
113 }
114
115 private void enforceUid(int callingUid) {
Andres Morales6429f312014-08-04 16:35:15 -0700116 if (UserHandle.getAppId(callingUid) != mAllowedAppId) {
Andres Morales68d4acd2014-07-01 19:40:41 -0700117 throw new SecurityException("uid " + callingUid + " not allowed to access PST");
118 }
119 }
120
Andres Morales963295e2014-07-10 15:40:24 -0700121 private int getTotalDataSizeLocked(DataInputStream inputStream) throws IOException {
Andres Morales68d4acd2014-07-01 19:40:41 -0700122 int totalDataSize;
123 int blockId = inputStream.readInt();
Andres Morales963295e2014-07-10 15:40:24 -0700124 if (blockId == PARTITION_TYPE_MARKER) {
Andres Morales68d4acd2014-07-01 19:40:41 -0700125 totalDataSize = inputStream.readInt();
126 } else {
127 totalDataSize = 0;
128 }
129 return totalDataSize;
130 }
131
Andres Morales963295e2014-07-10 15:40:24 -0700132 private long getBlockDeviceSize() {
133 synchronized (mLock) {
134 if (mBlockDeviceSize == -1) {
135 mBlockDeviceSize = nativeGetBlockDeviceSize(mDataBlockFile);
Andres Morales68d4acd2014-07-01 19:40:41 -0700136 }
137 }
138
139 return mBlockDeviceSize;
140 }
141
Andres Morales963295e2014-07-10 15:40:24 -0700142 private native long nativeGetBlockDeviceSize(String path);
143 private native int nativeWipe(String path);
Andres Morales68d4acd2014-07-01 19:40:41 -0700144
145 private final IBinder mService = new IPersistentDataBlockService.Stub() {
146 @Override
147 public int write(byte[] data) throws RemoteException {
148 enforceUid(Binder.getCallingUid());
149
150 // Need to ensure we don't write over the last byte
Andres Morales963295e2014-07-10 15:40:24 -0700151 long maxBlockSize = getBlockDeviceSize() - HEADER_SIZE - 1;
152 if (data.length > maxBlockSize) {
153 // partition is ~500k so shouldn't be a problem to downcast
154 return (int) -maxBlockSize;
Andres Morales68d4acd2014-07-01 19:40:41 -0700155 }
156
157 DataOutputStream outputStream;
158 try {
159 outputStream = new DataOutputStream(new FileOutputStream(new File(mDataBlockFile)));
160 } catch (FileNotFoundException e) {
Andres Morales963295e2014-07-10 15:40:24 -0700161 Slog.e(TAG, "partition not available?", e);
Andres Morales68d4acd2014-07-01 19:40:41 -0700162 return -1;
163 }
164
165 ByteBuffer headerAndData = ByteBuffer.allocate(data.length + HEADER_SIZE);
Andres Morales963295e2014-07-10 15:40:24 -0700166 headerAndData.putInt(PARTITION_TYPE_MARKER);
Andres Morales68d4acd2014-07-01 19:40:41 -0700167 headerAndData.putInt(data.length);
168 headerAndData.put(data);
169
170 try {
Andres Morales963295e2014-07-10 15:40:24 -0700171 synchronized (mLock) {
172 outputStream.write(headerAndData.array());
173 return data.length;
174 }
Andres Morales68d4acd2014-07-01 19:40:41 -0700175 } catch (IOException e) {
Andres Morales963295e2014-07-10 15:40:24 -0700176 Slog.e(TAG, "failed writing to the persistent data block", e);
Andres Morales68d4acd2014-07-01 19:40:41 -0700177 return -1;
178 } finally {
179 try {
180 outputStream.close();
181 } catch (IOException e) {
Andres Morales963295e2014-07-10 15:40:24 -0700182 Slog.e(TAG, "failed closing output stream", e);
Andres Morales68d4acd2014-07-01 19:40:41 -0700183 }
184 }
185 }
186
187 @Override
Andres Morales963295e2014-07-10 15:40:24 -0700188 public byte[] read() {
Andres Morales68d4acd2014-07-01 19:40:41 -0700189 enforceUid(Binder.getCallingUid());
190
191 DataInputStream inputStream;
192 try {
193 inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
194 } catch (FileNotFoundException e) {
Andres Morales963295e2014-07-10 15:40:24 -0700195 Slog.e(TAG, "partition not available?", e);
196 return null;
Andres Morales68d4acd2014-07-01 19:40:41 -0700197 }
198
199 try {
Andres Morales963295e2014-07-10 15:40:24 -0700200 synchronized (mLock) {
201 int totalDataSize = getTotalDataSizeLocked(inputStream);
202
203 if (totalDataSize == 0) {
204 return new byte[0];
205 }
206
207 byte[] data = new byte[totalDataSize];
208 int read = inputStream.read(data, 0, totalDataSize);
209 if (read < totalDataSize) {
210 // something went wrong, not returning potentially corrupt data
211 Slog.e(TAG, "failed to read entire data block. bytes read: " +
212 read + "/" + totalDataSize);
213 return null;
214 }
215 return data;
216 }
Andres Morales68d4acd2014-07-01 19:40:41 -0700217 } catch (IOException e) {
Andres Morales963295e2014-07-10 15:40:24 -0700218 Slog.e(TAG, "failed to read data", e);
219 return null;
Andres Morales68d4acd2014-07-01 19:40:41 -0700220 } finally {
221 try {
222 inputStream.close();
223 } catch (IOException e) {
Andres Morales963295e2014-07-10 15:40:24 -0700224 Slog.e(TAG, "failed to close OutputStream");
225 }
226 }
227 }
228
229 @Override
230 public void wipe() {
231 enforceOemUnlockPermission();
232
233 synchronized (mLock) {
234 int ret = nativeWipe(mDataBlockFile);
235
236 if (ret < 0) {
237 Slog.e(TAG, "failed to wipe persistent partition");
Andres Morales68d4acd2014-07-01 19:40:41 -0700238 }
239 }
240 }
241
242 @Override
243 public void setOemUnlockEnabled(boolean enabled) {
244 enforceOemUnlockPermission();
245 FileOutputStream outputStream;
246 try {
247 outputStream = new FileOutputStream(new File(mDataBlockFile));
248 } catch (FileNotFoundException e) {
Andres Morales963295e2014-07-10 15:40:24 -0700249 Slog.e(TAG, "parition not available", e);
Andres Morales68d4acd2014-07-01 19:40:41 -0700250 return;
251 }
252
253 try {
254 FileChannel channel = outputStream.getChannel();
255
Andres Morales963295e2014-07-10 15:40:24 -0700256 channel.position(getBlockDeviceSize() - 1);
Andres Morales68d4acd2014-07-01 19:40:41 -0700257
258 ByteBuffer data = ByteBuffer.allocate(1);
259 data.put(enabled ? (byte) 1 : (byte) 0);
260 data.flip();
261
Andres Morales963295e2014-07-10 15:40:24 -0700262 synchronized (mOemLock) {
263 channel.write(data);
Andres Morales68d4acd2014-07-01 19:40:41 -0700264 }
Andres Morales963295e2014-07-10 15:40:24 -0700265 } catch (IOException e) {
266 Slog.e(TAG, "unable to access persistent partition", e);
267 } finally {
268 IoUtils.closeQuietly(outputStream);
Andres Morales68d4acd2014-07-01 19:40:41 -0700269 }
270 }
271
272 @Override
273 public boolean getOemUnlockEnabled() {
274 enforceOemUnlockPermission();
275 DataInputStream inputStream;
276 try {
277 inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
278 } catch (FileNotFoundException e) {
Andres Morales963295e2014-07-10 15:40:24 -0700279 Slog.e(TAG, "partition not available");
Andres Morales68d4acd2014-07-01 19:40:41 -0700280 return false;
281 }
282
283 try {
Andres Morales963295e2014-07-10 15:40:24 -0700284 inputStream.skip(getBlockDeviceSize() - 1);
285 synchronized (mOemLock) {
286 return inputStream.readByte() != 0;
287 }
Andres Morales68d4acd2014-07-01 19:40:41 -0700288 } catch (IOException e) {
Andres Morales963295e2014-07-10 15:40:24 -0700289 Slog.e(TAG, "unable to access persistent partition", e);
Andres Morales68d4acd2014-07-01 19:40:41 -0700290 return false;
291 } finally {
Andres Morales963295e2014-07-10 15:40:24 -0700292 IoUtils.closeQuietly(inputStream);
Andres Morales68d4acd2014-07-01 19:40:41 -0700293 }
294 }
295
296 @Override
297 public int getDataBlockSize() {
298 enforceUid(Binder.getCallingUid());
299
300 DataInputStream inputStream;
301 try {
302 inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
303 } catch (FileNotFoundException e) {
Andres Morales963295e2014-07-10 15:40:24 -0700304 Slog.e(TAG, "partition not available");
Andres Morales68d4acd2014-07-01 19:40:41 -0700305 return 0;
306 }
307
308 try {
Andres Morales963295e2014-07-10 15:40:24 -0700309 synchronized (mLock) {
310 return getTotalDataSizeLocked(inputStream);
311 }
Andres Morales68d4acd2014-07-01 19:40:41 -0700312 } catch (IOException e) {
Andres Morales963295e2014-07-10 15:40:24 -0700313 Slog.e(TAG, "error reading data block size");
Andres Morales68d4acd2014-07-01 19:40:41 -0700314 return 0;
315 } finally {
Andres Morales963295e2014-07-10 15:40:24 -0700316 IoUtils.closeQuietly(inputStream);
Andres Morales68d4acd2014-07-01 19:40:41 -0700317 }
318 }
Andres Morales963295e2014-07-10 15:40:24 -0700319
320 @Override
321 public long getMaximumDataBlockSize() {
322 long actualSize = getBlockDeviceSize() - HEADER_SIZE - 1;
323 return actualSize <= MAX_DATA_BLOCK_SIZE ? actualSize : MAX_DATA_BLOCK_SIZE;
324 }
325
Andres Morales68d4acd2014-07-01 19:40:41 -0700326 };
327}