Permit settings to "wipe" the persistent partition

One of the requirements is that when the user does a
factory reset through settings, all data on the
persistent partition should be cleared.

This adds one last API method that allows settings
to wipe the partition.

Bug: 14288780
Change-Id: Ib87ee741d1e5195814516ae1d66eb7c4cf754dcf
diff --git a/core/java/android/service/persistentdata/IPersistentDataBlockService.aidl b/core/java/android/service/persistentdata/IPersistentDataBlockService.aidl
index 06e776d..52db223 100644
--- a/core/java/android/service/persistentdata/IPersistentDataBlockService.aidl
+++ b/core/java/android/service/persistentdata/IPersistentDataBlockService.aidl
@@ -1,3 +1,19 @@
+/*
+ * 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 android.service.persistentdata;
 
 import android.os.ParcelFileDescriptor;
@@ -12,8 +28,10 @@
  */
 interface IPersistentDataBlockService {
     int write(in byte[] data);
-    int read(out byte[] data);
+    byte[] read();
+    void wipe();
     int getDataBlockSize();
+    long getMaximumDataBlockSize();
 
     void setOemUnlockEnabled(boolean enabled);
     boolean getOemUnlockEnabled();
diff --git a/core/java/android/service/persistentdata/PersistentDataBlockManager.java b/core/java/android/service/persistentdata/PersistentDataBlockManager.java
index afcf717..42a2e57 100644
--- a/core/java/android/service/persistentdata/PersistentDataBlockManager.java
+++ b/core/java/android/service/persistentdata/PersistentDataBlockManager.java
@@ -1,3 +1,19 @@
+/*
+ * 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 android.service.persistentdata;
 
 import android.os.RemoteException;
@@ -7,14 +23,16 @@
  * Interface for reading and writing data blocks to a persistent partition.
  *
  * Allows writing one block at a time. Namely, each time
- * {@link android.service.persistentdata.PersistentDataBlockManager}.write(byte[] data)
+ * {@link PersistentDataBlockManager#write(byte[])}
  * 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.PersistentDataBlockManager}.getTotalDataSize().
+ * {@link PersistentDataBlockManager#getDataBlockSize()}.
  *
- * Clients can any number of bytes from the currently written block up to its total size by invoking
- * {@link android.service.persistentdata.PersistentDataBlockManager}.read(byte[] data).
+ * Clients can query the maximum size for a block via
+ *
+ * Clients can read the currently written block by invoking
+ * {@link PersistentDataBlockManager#read()}.
  *
  * @hide
  */
@@ -30,41 +48,69 @@
      * Writes {@code data} to the persistent partition. Previously written data
      * will be overwritten. This data will persist across factory resets.
      *
+     * Returns the number of bytes written or -1 on error. If the block is too big
+     * to fit on the partition, returns -MAX_BLOCK_SIZE.
+     *
      * @param data the data to write
      */
-    public void write(byte[] data) {
+    public int write(byte[] data) {
         try {
-            sService.write(data);
+            return sService.write(data);
         } catch (RemoteException e) {
             onError("writing data");
-        }
-    }
-
-    /**
-     * Tries to read {@code data.length} bytes into {@code data}. Call {@code getDataBlockSize()}
-     * to determine the total size of the block currently residing in the persistent partition.
-     *
-     * @param data the buffer in which to read the data
-     * @return the actual number of bytes read
-     */
-    public int read(byte[] data) {
-        try {
-            return sService.read(data);
-        } catch (RemoteException e) {
-            onError("reading data");
             return -1;
         }
     }
 
     /**
+     * Returns the data block stored on the persistent partition.
+     */
+    public byte[] read() {
+        try {
+            return sService.read();
+        } catch (RemoteException e) {
+            onError("reading data");
+            return null;
+        }
+    }
+
+    /**
      * Retrieves the size of the block currently written to the persistent partition.
+     *
+     * Return -1 on error.
      */
     public int getDataBlockSize() {
         try {
             return sService.getDataBlockSize();
         } catch (RemoteException e) {
             onError("getting data block size");
-            return 0;
+            return -1;
+        }
+    }
+
+    /**
+     * Retrieves the maximum size allowed for a data block.
+     *
+     * Returns -1 on error.
+     */
+    public long getMaximumDataBlockSize() {
+        try {
+            return sService.getMaximumDataBlockSize();
+        } catch (RemoteException e) {
+            onError("getting maximum data block size");
+            return -1;
+        }
+    }
+
+    /**
+     * Zeroes the previously written block in its entirety. Calling this method
+     * will erase all data written to the persistent data partition.
+     */
+    public void wipe() {
+        try {
+            sService.wipe();
+        } catch (RemoteException e) {
+            onError("wiping persistent partition");
         }
     }
 
diff --git a/services/core/java/com/android/server/PersistentDataBlockService.java b/services/core/java/com/android/server/PersistentDataBlockService.java
index 1eded6f..f71a18a 100644
--- a/services/core/java/com/android/server/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/PersistentDataBlockService.java
@@ -1,3 +1,19 @@
+/*
+ * 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 android.Manifest;
@@ -8,8 +24,9 @@
 import android.os.RemoteException;
 import android.os.SystemProperties;
 import android.service.persistentdata.IPersistentDataBlockService;
-import android.util.Log;
+import android.util.Slog;
 import com.android.internal.R;
+import libcore.io.IoUtils;
 
 import java.io.DataInputStream;
 import java.io.DataOutputStream;
@@ -23,7 +40,8 @@
 
 /**
  * Service for reading and writing blocks to a persistent partition.
- * This data will live across factory resets.
+ * 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 android.service.persistentdata.IPersistentDataBlockService}.write(byte[] data)
@@ -40,19 +58,29 @@
 
     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;
+    // Magic number to mark block device as adhering to the format consumed by this service
+    private static final int PARTITION_TYPE_MARKER = 0x1990;
+    // Limit to 100k as blocks larger than this might cause strain on Binder.
+    // TODO(anmorales): Consider splitting up too-large blocks in PersistentDataBlockManager
+    private static final int MAX_DATA_BLOCK_SIZE = 1024 * 100;
 
     private final Context mContext;
     private final String mDataBlockFile;
-    private long mBlockDeviceSize;
-
     private final int mAllowedUid;
+    private final Object mLock = new Object();
+    /*
+     * Separate lock for OEM unlock related operations as they can happen in parallel with regular
+     * block operations.
+     */
+    private final Object mOemLock = new Object();
+
+    private long mBlockDeviceSize;
 
     public PersistentDataBlockService(Context context) {
         super(context);
         mContext = context;
         mDataBlockFile = SystemProperties.get(PERSISTENT_DATA_BLOCK_PROP);
-        mBlockDeviceSize = 0; // Load lazily
+        mBlockDeviceSize = -1; // Load lazily
         String allowedPackage = context.getResources()
                 .getString(R.string.config_persistentDataPackageName);
         PackageManager pm = mContext.getPackageManager();
@@ -62,7 +90,7 @@
                     Binder.getCallingUserHandle().getIdentifier());
         } catch (PackageManager.NameNotFoundException e) {
             // not expected
-            Log.e(TAG, "not able to find package " + allowedPackage, e);
+            Slog.e(TAG, "not able to find package " + allowedPackage, e);
         }
 
         mAllowedUid = allowedUid;
@@ -85,10 +113,10 @@
         }
     }
 
-    private int getTotalDataSize(DataInputStream inputStream) throws IOException {
+    private int getTotalDataSizeLocked(DataInputStream inputStream) throws IOException {
         int totalDataSize;
         int blockId = inputStream.readInt();
-        if (blockId == BLOCK_ID) {
+        if (blockId == PARTITION_TYPE_MARKER) {
             totalDataSize = inputStream.readInt();
         } else {
             totalDataSize = 0;
@@ -96,17 +124,18 @@
         return totalDataSize;
     }
 
-    private long maybeReadBlockDeviceSize() {
-        synchronized (this) {
-            if (mBlockDeviceSize == 0) {
-                mBlockDeviceSize = getBlockDeviceSize(mDataBlockFile);
+    private long getBlockDeviceSize() {
+        synchronized (mLock) {
+            if (mBlockDeviceSize == -1) {
+                mBlockDeviceSize = nativeGetBlockDeviceSize(mDataBlockFile);
             }
         }
 
         return mBlockDeviceSize;
     }
 
-    private native long getBlockDeviceSize(String path);
+    private native long nativeGetBlockDeviceSize(String path);
+    private native int nativeWipe(String path);
 
     private final IBinder mService = new IPersistentDataBlockService.Stub() {
         @Override
@@ -114,62 +143,93 @@
             enforceUid(Binder.getCallingUid());
 
             // Need to ensure we don't write over the last byte
-            if (data.length > maybeReadBlockDeviceSize() - HEADER_SIZE - 1) {
-                return -1;
+            long maxBlockSize = getBlockDeviceSize() - HEADER_SIZE - 1;
+            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) {
-                Log.e(TAG, "partition not available?", e);
+                Slog.e(TAG, "partition not available?", e);
                 return -1;
             }
 
             ByteBuffer headerAndData = ByteBuffer.allocate(data.length + HEADER_SIZE);
-            headerAndData.putInt(BLOCK_ID);
+            headerAndData.putInt(PARTITION_TYPE_MARKER);
             headerAndData.putInt(data.length);
             headerAndData.put(data);
 
             try {
-                outputStream.write(headerAndData.array());
-                return data.length;
+                synchronized (mLock) {
+                    outputStream.write(headerAndData.array());
+                    return data.length;
+                }
             } catch (IOException e) {
-                Log.e(TAG, "failed writing to the persistent data block", e);
+                Slog.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);
+                    Slog.e(TAG, "failed closing output stream", e);
                 }
             }
         }
 
         @Override
-        public int read(byte[] data) {
+        public byte[] read() {
             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;
+                Slog.e(TAG, "partition not available?", e);
+                return null;
             }
 
             try {
-                int totalDataSize = getTotalDataSize(inputStream);
-                return inputStream.read(data, 0,
-                        (data.length > totalDataSize) ? totalDataSize : data.length);
+                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) {
-                Log.e(TAG, "failed to read data", e);
-                return -1;
+                Slog.e(TAG, "failed to read data", e);
+                return null;
             } finally {
                 try {
                     inputStream.close();
                 } catch (IOException e) {
-                    Log.e(TAG, "failed to close OutputStream");
+                    Slog.e(TAG, "failed to close OutputStream");
+                }
+            }
+        }
+
+        @Override
+        public void wipe() {
+            enforceOemUnlockPermission();
+
+            synchronized (mLock) {
+                int ret = nativeWipe(mDataBlockFile);
+
+                if (ret < 0) {
+                    Slog.e(TAG, "failed to wipe persistent partition");
                 }
             }
         }
@@ -181,28 +241,26 @@
             try {
                 outputStream = new FileOutputStream(new File(mDataBlockFile));
             } catch (FileNotFoundException e) {
-                Log.e(TAG, "parition not available", e);
+                Slog.e(TAG, "parition not available", e);
                 return;
             }
 
             try {
                 FileChannel channel = outputStream.getChannel();
 
-                channel.position(maybeReadBlockDeviceSize() - 1);
+                channel.position(getBlockDeviceSize() - 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");
+                synchronized (mOemLock) {
+                    channel.write(data);
                 }
+            } catch (IOException e) {
+                Slog.e(TAG, "unable to access persistent partition", e);
+            } finally {
+                IoUtils.closeQuietly(outputStream);
             }
         }
 
@@ -213,22 +271,20 @@
             try {
                 inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
             } catch (FileNotFoundException e) {
-                Log.e(TAG, "partition not available");
+                Slog.e(TAG, "partition not available");
                 return false;
             }
 
             try {
-                inputStream.skip(maybeReadBlockDeviceSize() - 1);
-                return inputStream.readByte() != 0;
+                inputStream.skip(getBlockDeviceSize() - 1);
+                synchronized (mOemLock) {
+                    return inputStream.readByte() != 0;
+                }
             } catch (IOException e) {
-                Log.e(TAG, "unable to access persistent partition", e);
+                Slog.e(TAG, "unable to access persistent partition", e);
                 return false;
             } finally {
-                try {
-                    inputStream.close();
-                } catch (IOException e) {
-                    Log.e(TAG, "failed to close OutputStream");
-                }
+                IoUtils.closeQuietly(inputStream);
             }
         }
 
@@ -240,22 +296,27 @@
             try {
                 inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
             } catch (FileNotFoundException e) {
-                Log.e(TAG, "partition not available");
+                Slog.e(TAG, "partition not available");
                 return 0;
             }
 
             try {
-                return getTotalDataSize(inputStream);
+                synchronized (mLock) {
+                    return getTotalDataSizeLocked(inputStream);
+                }
             } catch (IOException e) {
-                Log.e(TAG, "error reading data block size");
+                Slog.e(TAG, "error reading data block size");
                 return 0;
             } finally {
-                try {
-                    inputStream.close();
-                } catch (IOException e) {
-                    Log.e(TAG, "failed to close OutputStream");
-                }
+                IoUtils.closeQuietly(inputStream);
             }
         }
+
+        @Override
+        public long getMaximumDataBlockSize() {
+            long actualSize = getBlockDeviceSize() - HEADER_SIZE - 1;
+            return actualSize <= MAX_DATA_BLOCK_SIZE ? actualSize : MAX_DATA_BLOCK_SIZE;
+        }
+
     };
 }
diff --git a/services/core/jni/com_android_server_PersistentDataBlockService.cpp b/services/core/jni/com_android_server_PersistentDataBlockService.cpp
index 673a6e2..e842eeb 100644
--- a/services/core/jni/com_android_server_PersistentDataBlockService.cpp
+++ b/services/core/jni/com_android_server_PersistentDataBlockService.cpp
@@ -21,9 +21,13 @@
 #include <utils/misc.h>
 #include <sys/ioctl.h>
 #include <sys/mount.h>
+#include <utils/Log.h>
+
 
 #include <inttypes.h>
 #include <fcntl.h>
+#include <errno.h>
+#include <string.h>
 
 namespace android {
 
@@ -40,7 +44,39 @@
         return size;
     }
 
-    static jlong com_android_server_PeristentDataBlockService_getBlockDeviceSize(JNIEnv *env, jclass, jstring jpath) {
+    int wipe_block_device(int fd)
+    {
+        uint64_t range[2];
+        int ret;
+        uint64_t len = get_block_device_size(fd);
+
+        range[0] = 0;
+        range[1] = len;
+
+        if (range[1] == 0)
+            return 0;
+
+        ret = ioctl(fd, BLKSECDISCARD, &range);
+        if (ret < 0) {
+            ALOGE("Something went wrong secure discarding block: %s\n", strerror(errno));
+            range[0] = 0;
+            range[1] = len;
+            ret = ioctl(fd, BLKDISCARD, &range);
+            if (ret < 0) {
+                ALOGE("Discard failed: %s\n", strerror(errno));
+                return -1;
+            } else {
+                ALOGE("Wipe via secure discard failed, used non-secure discard instead\n");
+                return 0;
+            }
+
+        }
+
+        return ret;
+    }
+
+    static jlong com_android_server_PersistentDataBlockService_getBlockDeviceSize(JNIEnv *env, jclass, jstring jpath)
+    {
         const char *path = env->GetStringUTFChars(jpath, 0);
         int fd = open(path, O_RDONLY);
 
@@ -50,9 +86,20 @@
         return get_block_device_size(fd);
     }
 
+    static int com_android_server_PersistentDataBlockService_wipe(JNIEnv *env, jclass, jstring jpath) {
+        const char *path = env->GetStringUTFChars(jpath, 0);
+        int fd = open(path, O_WRONLY);
+
+        if (fd < 0)
+            return 0;
+
+        return wipe_block_device(fd);
+    }
+
     static JNINativeMethod sMethods[] = {
          /* name, signature, funcPtr */
-        {"getBlockDeviceSize", "(Ljava/lang/String;)J", (void*)com_android_server_PeristentDataBlockService_getBlockDeviceSize},
+        {"nativeGetBlockDeviceSize", "(Ljava/lang/String;)J", (void*)com_android_server_PersistentDataBlockService_getBlockDeviceSize},
+        {"nativeWipe", "(Ljava/lang/String;)I", (void*)com_android_server_PersistentDataBlockService_wipe},
     };
 
     int register_android_server_PersistentDataBlockService(JNIEnv* env)