PersistentDataBlockService.java revision 963295ea105314e28e4ca9563aa09cb7440de4c3
1/* 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 17package 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; 27import android.util.Slog; 28import com.android.internal.R; 29import libcore.io.IoUtils; 30 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. 43 * 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. 45 * 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; 61 // 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; 66 67 private final Context mContext; 68 private final String mDataBlockFile; 69 private final int mAllowedUid; 70 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; 78 79 public PersistentDataBlockService(Context context) { 80 super(context); 81 mContext = context; 82 mDataBlockFile = SystemProperties.get(PERSISTENT_DATA_BLOCK_PROP); 83 mBlockDeviceSize = -1; // Load lazily 84 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 93 Slog.e(TAG, "not able to find package " + allowedPackage, e); 94 } 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 116 private int getTotalDataSizeLocked(DataInputStream inputStream) throws IOException { 117 int totalDataSize; 118 int blockId = inputStream.readInt(); 119 if (blockId == PARTITION_TYPE_MARKER) { 120 totalDataSize = inputStream.readInt(); 121 } else { 122 totalDataSize = 0; 123 } 124 return totalDataSize; 125 } 126 127 private long getBlockDeviceSize() { 128 synchronized (mLock) { 129 if (mBlockDeviceSize == -1) { 130 mBlockDeviceSize = nativeGetBlockDeviceSize(mDataBlockFile); 131 } 132 } 133 134 return mBlockDeviceSize; 135 } 136 137 private native long nativeGetBlockDeviceSize(String path); 138 private native int nativeWipe(String path); 139 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 146 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; 150 } 151 152 DataOutputStream outputStream; 153 try { 154 outputStream = new DataOutputStream(new FileOutputStream(new File(mDataBlockFile))); 155 } catch (FileNotFoundException e) { 156 Slog.e(TAG, "partition not available?", e); 157 return -1; 158 } 159 160 ByteBuffer headerAndData = ByteBuffer.allocate(data.length + HEADER_SIZE); 161 headerAndData.putInt(PARTITION_TYPE_MARKER); 162 headerAndData.putInt(data.length); 163 headerAndData.put(data); 164 165 try { 166 synchronized (mLock) { 167 outputStream.write(headerAndData.array()); 168 return data.length; 169 } 170 } catch (IOException e) { 171 Slog.e(TAG, "failed writing to the persistent data block", e); 172 return -1; 173 } finally { 174 try { 175 outputStream.close(); 176 } catch (IOException e) { 177 Slog.e(TAG, "failed closing output stream", e); 178 } 179 } 180 } 181 182 @Override 183 public byte[] read() { 184 enforceUid(Binder.getCallingUid()); 185 186 DataInputStream inputStream; 187 try { 188 inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile))); 189 } catch (FileNotFoundException e) { 190 Slog.e(TAG, "partition not available?", e); 191 return null; 192 } 193 194 try { 195 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 } 212 } catch (IOException e) { 213 Slog.e(TAG, "failed to read data", e); 214 return null; 215 } finally { 216 try { 217 inputStream.close(); 218 } catch (IOException e) { 219 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"); 233 } 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) { 244 Slog.e(TAG, "parition not available", e); 245 return; 246 } 247 248 try { 249 FileChannel channel = outputStream.getChannel(); 250 251 channel.position(getBlockDeviceSize() - 1); 252 253 ByteBuffer data = ByteBuffer.allocate(1); 254 data.put(enabled ? (byte) 1 : (byte) 0); 255 data.flip(); 256 257 synchronized (mOemLock) { 258 channel.write(data); 259 } 260 } catch (IOException e) { 261 Slog.e(TAG, "unable to access persistent partition", e); 262 } finally { 263 IoUtils.closeQuietly(outputStream); 264 } 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) { 274 Slog.e(TAG, "partition not available"); 275 return false; 276 } 277 278 try { 279 inputStream.skip(getBlockDeviceSize() - 1); 280 synchronized (mOemLock) { 281 return inputStream.readByte() != 0; 282 } 283 } catch (IOException e) { 284 Slog.e(TAG, "unable to access persistent partition", e); 285 return false; 286 } finally { 287 IoUtils.closeQuietly(inputStream); 288 } 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) { 299 Slog.e(TAG, "partition not available"); 300 return 0; 301 } 302 303 try { 304 synchronized (mLock) { 305 return getTotalDataSizeLocked(inputStream); 306 } 307 } catch (IOException e) { 308 Slog.e(TAG, "error reading data block size"); 309 return 0; 310 } finally { 311 IoUtils.closeQuietly(inputStream); 312 } 313 } 314 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 321 }; 322} 323