content: /*
   * Copyright (C) 2014 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package com.android.server;
  
  import static com.android.internal.util.Preconditions.checkArgument;
  
  import android.Manifest;
  import android.app.ActivityManager;
  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.os.UserHandle;
  import android.os.UserManager;
  import android.service.persistentdata.IPersistentDataBlockService;
  import android.service.persistentdata.PersistentDataBlockManager;
  import android.util.Slog;
  
  import com.android.internal.R;
  import com.android.internal.annotations.GuardedBy;
  
  import libcore.io.IoUtils;
  
  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;
  import java.security.MessageDigest;
  import java.security.NoSuchAlgorithmException;
  import java.util.Arrays;
  import java.util.concurrent.CountDownLatch;
  import java.util.concurrent.TimeUnit;
  
  /**
   * Service for reading and writing blocks to a persistent partition.
   * This data will live across factory resets not initiated via the Settings UI.
   * When a device is factory reset through Settings this data is wiped.
   *
   * Allows writing one block at a time. Namely, each time {@link IPersistentDataBlockService#write}
   * is called, it will overwrite the data that was previously written on the block.
   *
   * Clients can query the size of the currently written block via
   * {@link IPersistentDataBlockService#getDataBlockSize}
   *
   * Clients can read any number of bytes from the currently written block up to its total size by
   * invoking {@link IPersistentDataBlockService#read}
   *
   * The persistent data block is currently laid out as follows:
   * | ---------BEGINNING OF PARTITION-------------|
   * | Partition digest (32 bytes)                 |
   * | --------------------------------------------|
   * | PARTITION_TYPE_MARKER (4 bytes)             |
   * | --------------------------------------------|
   * | FRP data block length (4 bytes)             |
   * | --------------------------------------------|
   * | FRP data (variable length)                  |
   * | --------------------------------------------|
   * | ...                                         |
   * | --------------------------------------------|
   * | Test mode data block (10000 bytes)          |
   * | --------------------------------------------|
   * |     | Test mode data length (4 bytes)       |
   * | --------------------------------------------|
   * |     | Test mode data (variable length)      |
   * |     | ...                                   |
   * | --------------------------------------------|
   * | FRP credential handle block (1000 bytes)    |
   * | --------------------------------------------|
   * |     | FRP credential handle length (4 bytes)|
   * | --------------------------------------------|
   * |     | FRP credential handle (variable len)  |
   * |     | ...                                   |
   * | --------------------------------------------|
   * | OEM Unlock bit (1 byte)                     |
   * | ---------END OF PARTITION-------------------|
   *
   * TODO: now that the persistent partition contains several blocks, next time someone wants a new
   * block, we should look at adding more generic block definitions and get rid of the various raw
   * XXX_RESERVED_SIZE and XXX_DATA_SIZE constants. That will ensure the code is easier to maintain
   * and less likely to introduce out-of-bounds read/write.
   */
  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;
      // Magic number to mark block device as adhering to the format consumed by this service
      private static final int PARTITION_TYPE_MARKER = 0x19901873;
      /** Size of the block reserved for FRP credential, including 4 bytes for the size header. */
      private static final int FRP_CREDENTIAL_RESERVED_SIZE = 1000;
      /** Maximum size of the FRP credential handle that can be stored. */
      private static final int MAX_FRP_CREDENTIAL_HANDLE_SIZE = FRP_CREDENTIAL_RESERVED_SIZE - 4;
      /**
       * Size of the block reserved for Test Harness Mode data, including 4 bytes for the size header.
       */
      private static final int TEST_MODE_RESERVED_SIZE = 10000;
      /** Maximum size of the Test Harness Mode data that can be stored. */
      private static final int MAX_TEST_MODE_DATA_SIZE = TEST_MODE_RESERVED_SIZE - 4;
      // Limit to 100k as blocks larger than this might cause strain on Binder.
      private static final int MAX_DATA_BLOCK_SIZE = 1024 * 100;
  
      public static final int DIGEST_SIZE_BYTES = 32;
      private static final String OEM_UNLOCK_PROP = "sys.oem_unlock_allowed";
      private static final String FLASH_LOCK_PROP = "ro.boot.flash.locked";
      private static final String FLASH_LOCK_LOCKED = "1";
      private static final String FLASH_LOCK_UNLOCKED = "0";
  
      private final Context mContext;
      private final String mDataBlockFile;
      private final Object mLock = new Object();
      private final CountDownLatch mInitDoneSignal = new CountDownLatch(1);
  
      private int mAllowedUid = -1;
      private long mBlockDeviceSize;
  
      @GuardedBy("mLock")
      private boolean mIsWritable = true;
  
      public PersistentDataBlockService(Context context) {
          super(context);
          mContext = context;
          mDataBlockFile = SystemProperties.get(PERSISTENT_DATA_BLOCK_PROP);
          mBlockDeviceSize = -1; // Load lazily
      }
  
      private int getAllowedUid(int userHandle) {
          String allowedPackage = mContext.getResources()
                  .getString(R.string.config_persistentDataPackageName);
          PackageManager pm = mContext.getPackageManager();
          int allowedUid = -1;
          try {
              allowedUid = pm.getPackageUidAsUser(allowedPackage,
                      PackageManager.MATCH_SYSTEM_ONLY, userHandle);
          } catch (PackageManager.NameNotFoundException e) {
              // not expected
              Slog.e(TAG, "not able to find package " + allowedPackage, e);
          }
          return allowedUid;
      }
  
      @Override
      public void onStart() {
          // Do init on a separate thread, will join in PHASE_ACTIVITY_MANAGER_READY
          SystemServerInitThreadPool.get().submit(() -> {
              mAllowedUid = getAllowedUid(UserHandle.USER_SYSTEM);
              enforceChecksumValidity();
              formatIfOemUnlockEnabled();
              publishBinderService(Context.PERSISTENT_DATA_BLOCK_SERVICE, mService);
              mInitDoneSignal.countDown();
          }, TAG + ".onStart");
      }
  
      @Override
      public void onBootPhase(int phase) {
          // Wait for initialization in onStart to finish
          if (phase == PHASE_SYSTEM_SERVICES_READY) {
              try {
                  if (!mInitDoneSignal.await(10, TimeUnit.SECONDS)) {
                      throw new IllegalStateException("Service " + TAG + " init timeout");
                  }
              } catch (InterruptedException e) {
                  Thread.currentThread().interrupt();
                  throw new IllegalStateException("Service " + TAG + " init interrupted", e);
              }
              LocalServices.addService(PersistentDataBlockManagerInternal.class, mInternalService);
          }
          super.onBootPhase(phase);
      }
  
      private void formatIfOemUnlockEnabled() {
          boolean enabled = doGetOemUnlockEnabled();
          if (enabled) {
              synchronized (mLock) {
                  formatPartitionLocked(true);
              }
          }
  
          SystemProperties.set(OEM_UNLOCK_PROP, enabled ? "1" : "0");
      }
  
      private void enforceOemUnlockReadPermission() {
          if (mContext.checkCallingOrSelfPermission(Manifest.permission.READ_OEM_UNLOCK_STATE)
                  == PackageManager.PERMISSION_DENIED
                  && mContext.checkCallingOrSelfPermission(Manifest.permission.OEM_UNLOCK_STATE)
                  == PackageManager.PERMISSION_DENIED) {
              throw new SecurityException("Can't access OEM unlock state. Requires "
                      + "READ_OEM_UNLOCK_STATE or OEM_UNLOCK_STATE permission.");
          }
      }
  
      private void enforceOemUnlockWritePermission() {
          mContext.enforceCallingOrSelfPermission(
                  Manifest.permission.OEM_UNLOCK_STATE,
                  "Can't modify OEM unlock state");
      }
  
      private void enforceUid(int callingUid) {
          if (callingUid != mAllowedUid) {
              throw new SecurityException("uid " + callingUid + " not allowed to access PST");
          }
      }
  
      private void enforceIsAdmin() {
          final int userId = UserHandle.getCallingUserId();
          final boolean isAdmin = UserManager.get(mContext).isUserAdmin(userId);
          if (!isAdmin) {
              throw new SecurityException(
                      "Only the Admin user is allowed to change OEM unlock state");
          }
      }
  
      private void enforceUserRestriction(String userRestriction) {
          if (UserManager.get(mContext).hasUserRestriction(userRestriction)) {
              throw new SecurityException(
                      "OEM unlock is disallowed by user restriction: " + userRestriction);
          }
      }
  
      private int getTotalDataSizeLocked(DataInputStream inputStream) throws IOException {
          // skip over checksum
          inputStream.skipBytes(DIGEST_SIZE_BYTES);
  
          int totalDataSize;
          int blockId = inputStream.readInt();
          if (blockId == PARTITION_TYPE_MARKER) {
              totalDataSize = inputStream.readInt();
          } else {
              totalDataSize = 0;
          }
          return totalDataSize;
      }
  
      private long getBlockDeviceSize() {
          synchronized (mLock) {
              if (mBlockDeviceSize == -1) {
                  mBlockDeviceSize = nativeGetBlockDeviceSize(mDataBlockFile);
              }
          }
  
          return mBlockDeviceSize;
      }
  
      private long getFrpCredentialDataOffset() {
          return getBlockDeviceSize() - 1 - FRP_CREDENTIAL_RESERVED_SIZE;
      }
  
      private long getTestHarnessModeDataOffset() {
          return getFrpCredentialDataOffset() - TEST_MODE_RESERVED_SIZE;
      }
  
      private boolean enforceChecksumValidity() {
          byte[] storedDigest = new byte[DIGEST_SIZE_BYTES];
  
          synchronized (mLock) {
              byte[] digest = computeDigestLocked(storedDigest);
              if (digest == null || !Arrays.equals(storedDigest, digest)) {
                  Slog.i(TAG, "Formatting FRP partition...");
                  formatPartitionLocked(false);
                  return false;
              }
          }
  
          return true;
      }
  
      private boolean computeAndWriteDigestLocked() {
          byte[] digest = computeDigestLocked(null);
          if (digest != null) {
              DataOutputStream outputStream;
              try {
                  outputStream = new DataOutputStream(
                          new FileOutputStream(new File(mDataBlockFile)));
              } catch (FileNotFoundException e) {
                  Slog.e(TAG, "partition not available?", e);
                  return false;
              }
  
              try {
                  outputStream.write(digest, 0, DIGEST_SIZE_BYTES);
                  outputStream.flush();
              } catch (IOException e) {
                  Slog.e(TAG, "failed to write block checksum", e);
                  return false;
              } finally {
                  IoUtils.closeQuietly(outputStream);
              }
              return true;
          } else {
              return false;
          }
      }
  
      private byte[] computeDigestLocked(byte[] storedDigest) {
          DataInputStream inputStream;
          try {
              inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
          } catch (FileNotFoundException e) {
              Slog.e(TAG, "partition not available?", e);
              return null;
          }
  
          MessageDigest md;
          try {
              md = MessageDigest.getInstance("SHA-256");
          } catch (NoSuchAlgorithmException e) {
              // won't ever happen -- every implementation is required to support SHA-256
              Slog.e(TAG, "SHA-256 not supported?", e);
              IoUtils.closeQuietly(inputStream);
              return null;
          }
  
          try {
              if (storedDigest != null && storedDigest.length == DIGEST_SIZE_BYTES) {
                  inputStream.read(storedDigest);
              } else {
                  inputStream.skipBytes(DIGEST_SIZE_BYTES);
              }
  
              int read;
              byte[] data = new byte[1024];
              md.update(data, 0, DIGEST_SIZE_BYTES); // include 0 checksum in digest
              while ((read = inputStream.read(data)) != -1) {
                  md.update(data, 0, read);
              }
          } catch (IOException e) {
              Slog.e(TAG, "failed to read partition", e);
              return null;
          } finally {
              IoUtils.closeQuietly(inputStream);
          }
  
          return md.digest();
      }
  
      private void formatPartitionLocked(boolean setOemUnlockEnabled) {
          DataOutputStream outputStream;
          try {
              outputStream = new DataOutputStream(new FileOutputStream(new File(mDataBlockFile)));
          } catch (FileNotFoundException e) {
              Slog.e(TAG, "partition not available?", e);
              return;
          }
  
          byte[] data = new byte[DIGEST_SIZE_BYTES];
          try {
              outputStream.write(data, 0, DIGEST_SIZE_BYTES);
              outputStream.writeInt(PARTITION_TYPE_MARKER);
              outputStream.writeInt(0); // data size
              outputStream.flush();
          } catch (IOException e) {
              Slog.e(TAG, "failed to format block", e);
              return;
          } finally {
              IoUtils.closeQuietly(outputStream);
          }
  
          doSetOemUnlockEnabledLocked(setOemUnlockEnabled);
          computeAndWriteDigestLocked();
      }
  
      private void doSetOemUnlockEnabledLocked(boolean enabled) {
          FileOutputStream outputStream;
          try {
              outputStream = new FileOutputStream(new File(mDataBlockFile));
          } catch (FileNotFoundException e) {
              Slog.e(TAG, "partition not available", e);
              return;
          }
  
          try {
              FileChannel channel = outputStream.getChannel();
  
              channel.position(getBlockDeviceSize() - 1);
  
              ByteBuffer data = ByteBuffer.allocate(1);
              data.put(enabled ? (byte) 1 : (byte) 0);
              data.flip();
              channel.write(data);
              outputStream.flush();
          } catch (IOException e) {
              Slog.e(TAG, "unable to access persistent partition", e);
              return;
          } finally {
              SystemProperties.set(OEM_UNLOCK_PROP, enabled ? "1" : "0");
              IoUtils.closeQuietly(outputStream);
          }
      }
  
      private boolean doGetOemUnlockEnabled() {
          DataInputStream inputStream;
          try {
              inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
          } catch (FileNotFoundException e) {
              Slog.e(TAG, "partition not available");
              return false;
          }
  
          try {
              synchronized (mLock) {
                  inputStream.skip(getBlockDeviceSize() - 1);
                  return inputStream.readByte() != 0;
              }
          } catch (IOException e) {
              Slog.e(TAG, "unable to access persistent partition", e);
              return false;
          } finally {
              IoUtils.closeQuietly(inputStream);
          }
      }
  
      private long doGetMaximumDataBlockSize() {
          long actualSize = getBlockDeviceSize() - HEADER_SIZE - DIGEST_SIZE_BYTES
                  - TEST_MODE_RESERVED_SIZE - FRP_CREDENTIAL_RESERVED_SIZE - 1;
          return actualSize <= MAX_DATA_BLOCK_SIZE ? actualSize : MAX_DATA_BLOCK_SIZE;
      }
  
      private native long nativeGetBlockDeviceSize(String path);
      private native int nativeWipe(String path);
  
      private final IBinder mService = new IPersistentDataBlockService.Stub() {
  
          /**
           * Write the data to the persistent data block.
           *
           * @return a positive integer of the number of bytes that were written if successful,
           * otherwise a negative integer indicating there was a problem
           */
          @Override
          public int write(byte[] data) throws RemoteException {
              enforceUid(Binder.getCallingUid());
  
              // Need to ensure we don't write over the last byte
              long maxBlockSize = doGetMaximumDataBlockSize();
              if (data.length > maxBlockSize) {
                  // partition is ~500k so shouldn't be a problem to downcast
                  return (int) -maxBlockSize;
              }
  
              DataOutputStream outputStream;
              try {
                  outputStream = new DataOutputStream(new FileOutputStream(new File(mDataBlockFile)));
              } catch (FileNotFoundException e) {
                  Slog.e(TAG, "partition not available?", e);
                  return -1;
              }
  
              ByteBuffer headerAndData = ByteBuffer.allocate(data.length + HEADER_SIZE);
              headerAndData.putInt(PARTITION_TYPE_MARKER);
              headerAndData.putInt(data.length);
              headerAndData.put(data);
  
              synchronized (mLock) {
                  if (!mIsWritable) {
                      IoUtils.closeQuietly(outputStream);
                      return -1;
                  }
  
                  try {
                      byte[] checksum = new byte[DIGEST_SIZE_BYTES];
                      outputStream.write(checksum, 0, DIGEST_SIZE_BYTES);
                      outputStream.write(headerAndData.array());
                      outputStream.flush();
                  } catch (IOException e) {
                      Slog.e(TAG, "failed writing to the persistent data block", e);
                      return -1;
                  } finally {
                      IoUtils.closeQuietly(outputStream);
                  }
  
                  if (computeAndWriteDigestLocked()) {
                      return data.length;
                  } else {
                      return -1;
                  }
              }
          }
  
          @Override
          public byte[] read() {
              enforceUid(Binder.getCallingUid());
              if (!enforceChecksumValidity()) {
                  return new byte[0];
              }
  
              DataInputStream inputStream;
              try {
                  inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
              } catch (FileNotFoundException e) {
                  Slog.e(TAG, "partition not available?", e);
                  return null;
              }
  
              try {
                  synchronized (mLock) {
                      int totalDataSize = getTotalDataSizeLocked(inputStream);
  
                      if (totalDataSize == 0) {
                          return new byte[0];
                      }
  
                      byte[] data = new byte[totalDataSize];
                      int read = inputStream.read(data, 0, totalDataSize);
                      if (read < totalDataSize) {
                          // something went wrong, not returning potentially corrupt data
                          Slog.e(TAG, "failed to read entire data block. bytes read: " +
                                  read + "/" + totalDataSize);
                          return null;
                      }
                      return data;
                  }
              } catch (IOException e) {
                  Slog.e(TAG, "failed to read data", e);
                  return null;
              } finally {
                  try {
                      inputStream.close();
                  } catch (IOException e) {
                      Slog.e(TAG, "failed to close OutputStream");
                  }
              }
          }
  
          @Override
          public void wipe() {
              enforceOemUnlockWritePermission();
  
              synchronized (mLock) {
                  int ret = nativeWipe(mDataBlockFile);
  
                  if (ret < 0) {
                      Slog.e(TAG, "failed to wipe persistent partition");
                  } else {
                      mIsWritable = false;
                      Slog.i(TAG, "persistent partition now wiped and unwritable");
                  }
              }
          }
  
          @Override
          public void setOemUnlockEnabled(boolean enabled) throws SecurityException {
              // do not allow monkey to flip the flag
              if (ActivityManager.isUserAMonkey()) {
                  return;
              }
  
              enforceOemUnlockWritePermission();
              enforceIsAdmin();
  
              if (enabled) {
                  // Do not allow oem unlock to be enabled if it's disallowed by a user restriction.
                  enforceUserRestriction(UserManager.DISALLOW_OEM_UNLOCK);
                  enforceUserRestriction(UserManager.DISALLOW_FACTORY_RESET);
              }
              synchronized (mLock) {
                  doSetOemUnlockEnabledLocked(enabled);
                  computeAndWriteDigestLocked();
              }
          }
  
          @Override
          public boolean getOemUnlockEnabled() {
              enforceOemUnlockReadPermission();
              return doGetOemUnlockEnabled();
          }
  
          @Override
          public int getFlashLockState() {
              enforceOemUnlockReadPermission();
              String locked = SystemProperties.get(FLASH_LOCK_PROP);
              switch (locked) {
                  case FLASH_LOCK_LOCKED:
                      return PersistentDataBlockManager.FLASH_LOCK_LOCKED;
                  case FLASH_LOCK_UNLOCKED:
                      return PersistentDataBlockManager.FLASH_LOCK_UNLOCKED;
                  default:
                      return PersistentDataBlockManager.FLASH_LOCK_UNKNOWN;
              }
          }
  
          @Override
          public int getDataBlockSize() {
              enforcePersistentDataBlockAccess();
  
              DataInputStream inputStream;
              try {
                  inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
              } catch (FileNotFoundException e) {
                  Slog.e(TAG, "partition not available");
                  return 0;
              }
  
              try {
                  synchronized (mLock) {
                      return getTotalDataSizeLocked(inputStream);
                  }
              } catch (IOException e) {
                  Slog.e(TAG, "error reading data block size");
                  return 0;
              } finally {
                  IoUtils.closeQuietly(inputStream);
              }
          }
  
          private void enforcePersistentDataBlockAccess() {
              if (mContext.checkCallingPermission(Manifest.permission.ACCESS_PDB_STATE)
                      != PackageManager.PERMISSION_GRANTED) {
                  enforceUid(Binder.getCallingUid());
              }
          }
  
          @Override
          public long getMaximumDataBlockSize() {
              enforceUid(Binder.getCallingUid());
              return doGetMaximumDataBlockSize();
          }
  
          @Override
          public boolean hasFrpCredentialHandle() {
              enforcePersistentDataBlockAccess();
              try {
                  return mInternalService.getFrpCredentialHandle() != null;
              } catch (IllegalStateException e) {
                  Slog.e(TAG, "error reading frp handle", e);
                  throw new UnsupportedOperationException("cannot read frp credential");
              }
          }
      };
  
      private PersistentDataBlockManagerInternal mInternalService =
              new PersistentDataBlockManagerInternal() {
  
          @Override
          public void setFrpCredentialHandle(byte[] handle) {
              writeInternal(handle, getFrpCredentialDataOffset(), MAX_FRP_CREDENTIAL_HANDLE_SIZE);
          }
  
          @Override
          public byte[] getFrpCredentialHandle() {
              return readInternal(getFrpCredentialDataOffset(), MAX_FRP_CREDENTIAL_HANDLE_SIZE);
          }
  
          @Override
          public void setTestHarnessModeData(byte[] data) {
              writeInternal(data, getTestHarnessModeDataOffset(), MAX_TEST_MODE_DATA_SIZE);
          }
  
          @Override
          public byte[] getTestHarnessModeData() {
              byte[] data = readInternal(getTestHarnessModeDataOffset(), MAX_TEST_MODE_DATA_SIZE);
              if (data == null) {
                  return new byte[0];
              }
              return data;
          }
  
          @Override
          public void clearTestHarnessModeData() {
              int size = Math.min(MAX_TEST_MODE_DATA_SIZE, getTestHarnessModeData().length) + 4;
              writeDataBuffer(getTestHarnessModeDataOffset(), ByteBuffer.allocate(size));
          }
  
          private void writeInternal(byte[] data, long offset, int dataLength) {
              checkArgument(data == null || data.length > 0, "data must be null or non-empty");
              checkArgument(
                      data == null || data.length <= dataLength,
                      "data must not be longer than " + dataLength);
  
              ByteBuffer dataBuffer = ByteBuffer.allocate(dataLength + 4);
              dataBuffer.putInt(data == null ? 0 : data.length);
              if (data != null) {
                  dataBuffer.put(data);
              }
              dataBuffer.flip();
  
              writeDataBuffer(offset, dataBuffer);
          }
  
          private void writeDataBuffer(long offset, ByteBuffer dataBuffer) {
              FileOutputStream outputStream;
              try {
                  outputStream = new FileOutputStream(new File(mDataBlockFile));
              } catch (FileNotFoundException e) {
                  Slog.e(TAG, "partition not available", e);
                  return;
              }
              synchronized (mLock) {
                  if (!mIsWritable) {
                      IoUtils.closeQuietly(outputStream);
                      return;
                  }
                  try {
                      FileChannel channel = outputStream.getChannel();
                      channel.position(offset);
                      channel.write(dataBuffer);
                      outputStream.flush();
                  } catch (IOException e) {
                      Slog.e(TAG, "unable to access persistent partition", e);
                      return;
                  } finally {
                      IoUtils.closeQuietly(outputStream);
                  }
  
                  computeAndWriteDigestLocked();
              }
          }
  
          private byte[] readInternal(long offset, int maxLength) {
              if (!enforceChecksumValidity()) {
                  throw new IllegalStateException("invalid checksum");
              }
  
              DataInputStream inputStream;
              try {
                  inputStream = new DataInputStream(
                          new FileInputStream(new File(mDataBlockFile)));
              } catch (FileNotFoundException e) {
                  throw new IllegalStateException("persistent partition not available");
              }
  
              try {
                  synchronized (mLock) {
                      inputStream.skip(offset);
                      int length = inputStream.readInt();
                      if (length <= 0 || length > maxLength) {
                          return null;
                      }
                      byte[] bytes = new byte[length];
                      inputStream.readFully(bytes);
                      return bytes;
                  }
              } catch (IOException e) {
                  throw new IllegalStateException("persistent partition not readable", e);
              } finally {
                  IoUtils.closeQuietly(inputStream);
              }
          }
  
          @Override
          public void forceOemUnlockEnabled(boolean enabled) {
              synchronized (mLock) {
                  doSetOemUnlockEnabledLocked(enabled);
                  computeAndWriteDigestLocked();
              }
          }
      };
  }
  