diff options
author | Andres Morales <anmorales@google.com> | 2014-07-01 19:40:41 -0700 |
---|---|---|
committer | Andres Morales <anmorales@google.com> | 2014-07-09 16:27:17 -0700 |
commit | 68d4acd205e8c2da524e62734ca42847306cc029 (patch) | |
tree | abe5eda5e7337866ec1dcabd7c9ac8a704177bca /services/core/java/com/android/server/PersistentDataBlockService.java | |
parent | dbbf07a5c7f514f2168f236e1df3b2ca70d4ab2f (diff) | |
download | frameworks_base-68d4acd205e8c2da524e62734ca42847306cc029.zip frameworks_base-68d4acd205e8c2da524e62734ca42847306cc029.tar.gz frameworks_base-68d4acd205e8c2da524e62734ca42847306cc029.tar.bz2 |
Service for reading and writing blocks to PST partition
Permits apps with permission
android.permission.ACCESS_PERSISTENT_PARTITION to obtain
a read and write data blocks to the PST partition.
Only one block ever exists at one time in PST. When
a client writes another block, the previous one is
overwritten.
This permits storing a block of data that will live
across factory resets.
Change-Id: I8f23df3531f3c0512118eb4b7530eff8a8e81c83
Diffstat (limited to 'services/core/java/com/android/server/PersistentDataBlockService.java')
-rw-r--r-- | services/core/java/com/android/server/PersistentDataBlockService.java | 261 |
1 files changed, 261 insertions, 0 deletions
diff --git a/services/core/java/com/android/server/PersistentDataBlockService.java b/services/core/java/com/android/server/PersistentDataBlockService.java new file mode 100644 index 0000000..1eded6f --- /dev/null +++ b/services/core/java/com/android/server/PersistentDataBlockService.java @@ -0,0 +1,261 @@ +package com.android.server; + +import android.Manifest; +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.Binder; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.SystemProperties; +import android.service.persistentdata.IPersistentDataBlockService; +import android.util.Log; +import com.android.internal.R; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; + +/** + * Service for reading and writing blocks to a persistent partition. + * This data will live across factory resets. + * + * Allows writing one block at a time. Namely, each time + * {@link android.service.persistentdata.IPersistentDataBlockService}.write(byte[] data) + * is called, it will overwite the data that was previously written on the block. + * + * Clients can query the size of the currently written block via + * {@link android.service.persistentdata.IPersistentDataBlockService}.getTotalDataSize(). + * + * Clients can any number of bytes from the currently written block up to its total size by invoking + * {@link android.service.persistentdata.IPersistentDataBlockService}.read(byte[] data) + */ +public class PersistentDataBlockService extends SystemService { + private static final String TAG = PersistentDataBlockService.class.getSimpleName(); + + private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst"; + private static final int HEADER_SIZE = 8; + private static final int BLOCK_ID = 0x1990; + + private final Context mContext; + private final String mDataBlockFile; + private long mBlockDeviceSize; + + private final int mAllowedUid; + + public PersistentDataBlockService(Context context) { + super(context); + mContext = context; + mDataBlockFile = SystemProperties.get(PERSISTENT_DATA_BLOCK_PROP); + mBlockDeviceSize = 0; // Load lazily + String allowedPackage = context.getResources() + .getString(R.string.config_persistentDataPackageName); + PackageManager pm = mContext.getPackageManager(); + int allowedUid = -1; + try { + allowedUid = pm.getPackageUid(allowedPackage, + Binder.getCallingUserHandle().getIdentifier()); + } catch (PackageManager.NameNotFoundException e) { + // not expected + Log.e(TAG, "not able to find package " + allowedPackage, e); + } + + mAllowedUid = allowedUid; + } + + @Override + public void onStart() { + publishBinderService(Context.PERSISTENT_DATA_BLOCK_SERVICE, mService); + } + + private void enforceOemUnlockPermission() { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.OEM_UNLOCK_STATE, + "Can't access OEM unlock state"); + } + + private void enforceUid(int callingUid) { + if (callingUid != mAllowedUid) { + throw new SecurityException("uid " + callingUid + " not allowed to access PST"); + } + } + + private int getTotalDataSize(DataInputStream inputStream) throws IOException { + int totalDataSize; + int blockId = inputStream.readInt(); + if (blockId == BLOCK_ID) { + totalDataSize = inputStream.readInt(); + } else { + totalDataSize = 0; + } + return totalDataSize; + } + + private long maybeReadBlockDeviceSize() { + synchronized (this) { + if (mBlockDeviceSize == 0) { + mBlockDeviceSize = getBlockDeviceSize(mDataBlockFile); + } + } + + return mBlockDeviceSize; + } + + private native long getBlockDeviceSize(String path); + + private final IBinder mService = new IPersistentDataBlockService.Stub() { + @Override + public int write(byte[] data) throws RemoteException { + enforceUid(Binder.getCallingUid()); + + // Need to ensure we don't write over the last byte + if (data.length > maybeReadBlockDeviceSize() - HEADER_SIZE - 1) { + return -1; + } + + DataOutputStream outputStream; + try { + outputStream = new DataOutputStream(new FileOutputStream(new File(mDataBlockFile))); + } catch (FileNotFoundException e) { + Log.e(TAG, "partition not available?", e); + return -1; + } + + ByteBuffer headerAndData = ByteBuffer.allocate(data.length + HEADER_SIZE); + headerAndData.putInt(BLOCK_ID); + headerAndData.putInt(data.length); + headerAndData.put(data); + + try { + outputStream.write(headerAndData.array()); + return data.length; + } catch (IOException e) { + Log.e(TAG, "failed writing to the persistent data block", e); + return -1; + } finally { + try { + outputStream.close(); + } catch (IOException e) { + Log.e(TAG, "failed closing output stream", e); + } + } + } + + @Override + public int read(byte[] data) { + enforceUid(Binder.getCallingUid()); + + DataInputStream inputStream; + try { + inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile))); + } catch (FileNotFoundException e) { + Log.e(TAG, "partition not available?", e); + return -1; + } + + try { + int totalDataSize = getTotalDataSize(inputStream); + return inputStream.read(data, 0, + (data.length > totalDataSize) ? totalDataSize : data.length); + } catch (IOException e) { + Log.e(TAG, "failed to read data", e); + return -1; + } finally { + try { + inputStream.close(); + } catch (IOException e) { + Log.e(TAG, "failed to close OutputStream"); + } + } + } + + @Override + public void setOemUnlockEnabled(boolean enabled) { + enforceOemUnlockPermission(); + FileOutputStream outputStream; + try { + outputStream = new FileOutputStream(new File(mDataBlockFile)); + } catch (FileNotFoundException e) { + Log.e(TAG, "parition not available", e); + return; + } + + try { + FileChannel channel = outputStream.getChannel(); + + channel.position(maybeReadBlockDeviceSize() - 1); + + ByteBuffer data = ByteBuffer.allocate(1); + data.put(enabled ? (byte) 1 : (byte) 0); + data.flip(); + + channel.write(data); + } catch (IOException e) { + Log.e(TAG, "unable to access persistent partition", e); + } finally { + try { + outputStream.close(); + } catch (IOException e) { + Log.e(TAG, "failed to close OutputStream"); + } + } + } + + @Override + public boolean getOemUnlockEnabled() { + enforceOemUnlockPermission(); + DataInputStream inputStream; + try { + inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile))); + } catch (FileNotFoundException e) { + Log.e(TAG, "partition not available"); + return false; + } + + try { + inputStream.skip(maybeReadBlockDeviceSize() - 1); + return inputStream.readByte() != 0; + } catch (IOException e) { + Log.e(TAG, "unable to access persistent partition", e); + return false; + } finally { + try { + inputStream.close(); + } catch (IOException e) { + Log.e(TAG, "failed to close OutputStream"); + } + } + } + + @Override + public int getDataBlockSize() { + enforceUid(Binder.getCallingUid()); + + DataInputStream inputStream; + try { + inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile))); + } catch (FileNotFoundException e) { + Log.e(TAG, "partition not available"); + return 0; + } + + try { + return getTotalDataSize(inputStream); + } catch (IOException e) { + Log.e(TAG, "error reading data block size"); + return 0; + } finally { + try { + inputStream.close(); + } catch (IOException e) { + Log.e(TAG, "failed to close OutputStream"); + } + } + } + }; +} |