blob: f71a18ab438c932f8893f1400bd1f246340cbc82 [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;
26import android.service.persistentdata.IPersistentDataBlockService;
Andres Morales963295e2014-07-10 15:40:24 -070027import android.util.Slog;
Andres Morales68d4acd2014-07-01 19:40:41 -070028import com.android.internal.R;
Andres Morales963295e2014-07-10 15:40:24 -070029import libcore.io.IoUtils;
Andres Morales68d4acd2014-07-01 19:40:41 -070030
31import java.io.DataInputStream;
32import java.io.DataOutputStream;
33import java.io.File;
34import java.io.FileInputStream;
35import java.io.FileNotFoundException;
36import java.io.FileOutputStream;
37import java.io.IOException;
38import java.nio.ByteBuffer;
39import java.nio.channels.FileChannel;
40
41/**
42 * Service for reading and writing blocks to a persistent partition.
Andres Morales963295e2014-07-10 15:40:24 -070043 * This data will live across factory resets not initiated via the Settings UI.
44 * When a device is factory reset through Settings this data is wiped.
Andres Morales68d4acd2014-07-01 19:40:41 -070045 *
46 * Allows writing one block at a time. Namely, each time
47 * {@link android.service.persistentdata.IPersistentDataBlockService}.write(byte[] data)
48 * is called, it will overwite the data that was previously written on the block.
49 *
50 * Clients can query the size of the currently written block via
51 * {@link android.service.persistentdata.IPersistentDataBlockService}.getTotalDataSize().
52 *
53 * Clients can any number of bytes from the currently written block up to its total size by invoking
54 * {@link android.service.persistentdata.IPersistentDataBlockService}.read(byte[] data)
55 */
56public class PersistentDataBlockService extends SystemService {
57 private static final String TAG = PersistentDataBlockService.class.getSimpleName();
58
59 private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst";
60 private static final int HEADER_SIZE = 8;
Andres Morales963295e2014-07-10 15:40:24 -070061 // Magic number to mark block device as adhering to the format consumed by this service
62 private static final int PARTITION_TYPE_MARKER = 0x1990;
63 // Limit to 100k as blocks larger than this might cause strain on Binder.
64 // TODO(anmorales): Consider splitting up too-large blocks in PersistentDataBlockManager
65 private static final int MAX_DATA_BLOCK_SIZE = 1024 * 100;
Andres Morales68d4acd2014-07-01 19:40:41 -070066
67 private final Context mContext;
68 private final String mDataBlockFile;
Andres Morales68d4acd2014-07-01 19:40:41 -070069 private final int mAllowedUid;
Andres Morales963295e2014-07-10 15:40:24 -070070 private final Object mLock = new Object();
71 /*
72 * Separate lock for OEM unlock related operations as they can happen in parallel with regular
73 * block operations.
74 */
75 private final Object mOemLock = new Object();
76
77 private long mBlockDeviceSize;
Andres Morales68d4acd2014-07-01 19:40:41 -070078
79 public PersistentDataBlockService(Context context) {
80 super(context);
81 mContext = context;
82 mDataBlockFile = SystemProperties.get(PERSISTENT_DATA_BLOCK_PROP);
Andres Morales963295e2014-07-10 15:40:24 -070083 mBlockDeviceSize = -1; // Load lazily
Andres Morales68d4acd2014-07-01 19:40:41 -070084 String allowedPackage = context.getResources()
85 .getString(R.string.config_persistentDataPackageName);
86 PackageManager pm = mContext.getPackageManager();
87 int allowedUid = -1;
88 try {
89 allowedUid = pm.getPackageUid(allowedPackage,
90 Binder.getCallingUserHandle().getIdentifier());
91 } catch (PackageManager.NameNotFoundException e) {
92 // not expected
Andres Morales963295e2014-07-10 15:40:24 -070093 Slog.e(TAG, "not able to find package " + allowedPackage, e);
Andres Morales68d4acd2014-07-01 19:40:41 -070094 }
95
96 mAllowedUid = allowedUid;
97 }
98
99 @Override
100 public void onStart() {
101 publishBinderService(Context.PERSISTENT_DATA_BLOCK_SERVICE, mService);
102 }
103
104 private void enforceOemUnlockPermission() {
105 mContext.enforceCallingOrSelfPermission(
106 Manifest.permission.OEM_UNLOCK_STATE,
107 "Can't access OEM unlock state");
108 }
109
110 private void enforceUid(int callingUid) {
111 if (callingUid != mAllowedUid) {
112 throw new SecurityException("uid " + callingUid + " not allowed to access PST");
113 }
114 }
115
Andres Morales963295e2014-07-10 15:40:24 -0700116 private int getTotalDataSizeLocked(DataInputStream inputStream) throws IOException {
Andres Morales68d4acd2014-07-01 19:40:41 -0700117 int totalDataSize;
118 int blockId = inputStream.readInt();
Andres Morales963295e2014-07-10 15:40:24 -0700119 if (blockId == PARTITION_TYPE_MARKER) {
Andres Morales68d4acd2014-07-01 19:40:41 -0700120 totalDataSize = inputStream.readInt();
121 } else {
122 totalDataSize = 0;
123 }
124 return totalDataSize;
125 }
126
Andres Morales963295e2014-07-10 15:40:24 -0700127 private long getBlockDeviceSize() {
128 synchronized (mLock) {
129 if (mBlockDeviceSize == -1) {
130 mBlockDeviceSize = nativeGetBlockDeviceSize(mDataBlockFile);
Andres Morales68d4acd2014-07-01 19:40:41 -0700131 }
132 }
133
134 return mBlockDeviceSize;
135 }
136
Andres Morales963295e2014-07-10 15:40:24 -0700137 private native long nativeGetBlockDeviceSize(String path);
138 private native int nativeWipe(String path);
Andres Morales68d4acd2014-07-01 19:40:41 -0700139
140 private final IBinder mService = new IPersistentDataBlockService.Stub() {
141 @Override
142 public int write(byte[] data) throws RemoteException {
143 enforceUid(Binder.getCallingUid());
144
145 // Need to ensure we don't write over the last byte
Andres Morales963295e2014-07-10 15:40:24 -0700146 long maxBlockSize = getBlockDeviceSize() - HEADER_SIZE - 1;
147 if (data.length > maxBlockSize) {
148 // partition is ~500k so shouldn't be a problem to downcast
149 return (int) -maxBlockSize;
Andres Morales68d4acd2014-07-01 19:40:41 -0700150 }
151
152 DataOutputStream outputStream;
153 try {
154 outputStream = new DataOutputStream(new FileOutputStream(new File(mDataBlockFile)));
155 } catch (FileNotFoundException e) {
Andres Morales963295e2014-07-10 15:40:24 -0700156 Slog.e(TAG, "partition not available?", e);
Andres Morales68d4acd2014-07-01 19:40:41 -0700157 return -1;
158 }
159
160 ByteBuffer headerAndData = ByteBuffer.allocate(data.length + HEADER_SIZE);
Andres Morales963295e2014-07-10 15:40:24 -0700161 headerAndData.putInt(PARTITION_TYPE_MARKER);
Andres Morales68d4acd2014-07-01 19:40:41 -0700162 headerAndData.putInt(data.length);
163 headerAndData.put(data);
164
165 try {
Andres Morales963295e2014-07-10 15:40:24 -0700166 synchronized (mLock) {
167 outputStream.write(headerAndData.array());
168 return data.length;
169 }
Andres Morales68d4acd2014-07-01 19:40:41 -0700170 } catch (IOException e) {
Andres Morales963295e2014-07-10 15:40:24 -0700171 Slog.e(TAG, "failed writing to the persistent data block", e);
Andres Morales68d4acd2014-07-01 19:40:41 -0700172 return -1;
173 } finally {
174 try {
175 outputStream.close();
176 } catch (IOException e) {
Andres Morales963295e2014-07-10 15:40:24 -0700177 Slog.e(TAG, "failed closing output stream", e);
Andres Morales68d4acd2014-07-01 19:40:41 -0700178 }
179 }
180 }
181
182 @Override
Andres Morales963295e2014-07-10 15:40:24 -0700183 public byte[] read() {
Andres Morales68d4acd2014-07-01 19:40:41 -0700184 enforceUid(Binder.getCallingUid());
185
186 DataInputStream inputStream;
187 try {
188 inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
189 } catch (FileNotFoundException e) {
Andres Morales963295e2014-07-10 15:40:24 -0700190 Slog.e(TAG, "partition not available?", e);
191 return null;
Andres Morales68d4acd2014-07-01 19:40:41 -0700192 }
193
194 try {
Andres Morales963295e2014-07-10 15:40:24 -0700195 synchronized (mLock) {
196 int totalDataSize = getTotalDataSizeLocked(inputStream);
197
198 if (totalDataSize == 0) {
199 return new byte[0];
200 }
201
202 byte[] data = new byte[totalDataSize];
203 int read = inputStream.read(data, 0, totalDataSize);
204 if (read < totalDataSize) {
205 // something went wrong, not returning potentially corrupt data
206 Slog.e(TAG, "failed to read entire data block. bytes read: " +
207 read + "/" + totalDataSize);
208 return null;
209 }
210 return data;
211 }
Andres Morales68d4acd2014-07-01 19:40:41 -0700212 } catch (IOException e) {
Andres Morales963295e2014-07-10 15:40:24 -0700213 Slog.e(TAG, "failed to read data", e);
214 return null;
Andres Morales68d4acd2014-07-01 19:40:41 -0700215 } finally {
216 try {
217 inputStream.close();
218 } catch (IOException e) {
Andres Morales963295e2014-07-10 15:40:24 -0700219 Slog.e(TAG, "failed to close OutputStream");
220 }
221 }
222 }
223
224 @Override
225 public void wipe() {
226 enforceOemUnlockPermission();
227
228 synchronized (mLock) {
229 int ret = nativeWipe(mDataBlockFile);
230
231 if (ret < 0) {
232 Slog.e(TAG, "failed to wipe persistent partition");
Andres Morales68d4acd2014-07-01 19:40:41 -0700233 }
234 }
235 }
236
237 @Override
238 public void setOemUnlockEnabled(boolean enabled) {
239 enforceOemUnlockPermission();
240 FileOutputStream outputStream;
241 try {
242 outputStream = new FileOutputStream(new File(mDataBlockFile));
243 } catch (FileNotFoundException e) {
Andres Morales963295e2014-07-10 15:40:24 -0700244 Slog.e(TAG, "parition not available", e);
Andres Morales68d4acd2014-07-01 19:40:41 -0700245 return;
246 }
247
248 try {
249 FileChannel channel = outputStream.getChannel();
250
Andres Morales963295e2014-07-10 15:40:24 -0700251 channel.position(getBlockDeviceSize() - 1);
Andres Morales68d4acd2014-07-01 19:40:41 -0700252
253 ByteBuffer data = ByteBuffer.allocate(1);
254 data.put(enabled ? (byte) 1 : (byte) 0);
255 data.flip();
256
Andres Morales963295e2014-07-10 15:40:24 -0700257 synchronized (mOemLock) {
258 channel.write(data);
Andres Morales68d4acd2014-07-01 19:40:41 -0700259 }
Andres Morales963295e2014-07-10 15:40:24 -0700260 } catch (IOException e) {
261 Slog.e(TAG, "unable to access persistent partition", e);
262 } finally {
263 IoUtils.closeQuietly(outputStream);
Andres Morales68d4acd2014-07-01 19:40:41 -0700264 }
265 }
266
267 @Override
268 public boolean getOemUnlockEnabled() {
269 enforceOemUnlockPermission();
270 DataInputStream inputStream;
271 try {
272 inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
273 } catch (FileNotFoundException e) {
Andres Morales963295e2014-07-10 15:40:24 -0700274 Slog.e(TAG, "partition not available");
Andres Morales68d4acd2014-07-01 19:40:41 -0700275 return false;
276 }
277
278 try {
Andres Morales963295e2014-07-10 15:40:24 -0700279 inputStream.skip(getBlockDeviceSize() - 1);
280 synchronized (mOemLock) {
281 return inputStream.readByte() != 0;
282 }
Andres Morales68d4acd2014-07-01 19:40:41 -0700283 } catch (IOException e) {
Andres Morales963295e2014-07-10 15:40:24 -0700284 Slog.e(TAG, "unable to access persistent partition", e);
Andres Morales68d4acd2014-07-01 19:40:41 -0700285 return false;
286 } finally {
Andres Morales963295e2014-07-10 15:40:24 -0700287 IoUtils.closeQuietly(inputStream);
Andres Morales68d4acd2014-07-01 19:40:41 -0700288 }
289 }
290
291 @Override
292 public int getDataBlockSize() {
293 enforceUid(Binder.getCallingUid());
294
295 DataInputStream inputStream;
296 try {
297 inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
298 } catch (FileNotFoundException e) {
Andres Morales963295e2014-07-10 15:40:24 -0700299 Slog.e(TAG, "partition not available");
Andres Morales68d4acd2014-07-01 19:40:41 -0700300 return 0;
301 }
302
303 try {
Andres Morales963295e2014-07-10 15:40:24 -0700304 synchronized (mLock) {
305 return getTotalDataSizeLocked(inputStream);
306 }
Andres Morales68d4acd2014-07-01 19:40:41 -0700307 } catch (IOException e) {
Andres Morales963295e2014-07-10 15:40:24 -0700308 Slog.e(TAG, "error reading data block size");
Andres Morales68d4acd2014-07-01 19:40:41 -0700309 return 0;
310 } finally {
Andres Morales963295e2014-07-10 15:40:24 -0700311 IoUtils.closeQuietly(inputStream);
Andres Morales68d4acd2014-07-01 19:40:41 -0700312 }
313 }
Andres Morales963295e2014-07-10 15:40:24 -0700314
315 @Override
316 public long getMaximumDataBlockSize() {
317 long actualSize = getBlockDeviceSize() - HEADER_SIZE - 1;
318 return actualSize <= MAX_DATA_BLOCK_SIZE ? actualSize : MAX_DATA_BLOCK_SIZE;
319 }
320
Andres Morales68d4acd2014-07-01 19:40:41 -0700321 };
322}