vl-android.c: Support EXT4 cache partitions.

Kernel 3.10 dropped support for yaffs2 partitions, which forces
the cache partition to be ext4 too, just like the system and
data one.

This patch refactors the code in vl-android.c that configures
the NAND devices corresponding to each partition image to do
the following:

- Use a single function to perform the configuration, based
  on a small set of parameters (android_nand_add_device).

- Ensure that the cache partition uses 'ext4' if kernel 3.10
  or higher is used, or 'yaffs2' otherwise.

Change-Id: I7352dc436d5677c508b89591d92c066b4227ce3a
diff --git a/vl-android.c b/vl-android.c
index e31ed7a..e8a3779 100644
--- a/vl-android.c
+++ b/vl-android.c
@@ -54,6 +54,7 @@
 #include "android/log-rotate.h"
 #include "modem_driver.h"
 #include "android/filesystems/ext4_utils.h"
+#include "android/filesystems/partition_types.h"
 #include "android/gps.h"
 #include "android/hw-kmsg.h"
 #include "android/hw-pipe-net.h"
@@ -1866,6 +1867,192 @@
     return -1;  /* shouldn't happen */
 }
 
+
+// Add a NAND partition image to the hardware configuration.
+// |part_name| is a string indicating the type of partition, i.e. "system",
+// "userdata" or "cache".
+// |part_type| is an enum describing the type of partition. If it is
+// DISK_PARTITION_TYPE_PROBE, then try to auto-detect the type directly
+// from the content of |part_file| or |part_init_file|.
+// |part_size| is the partition size in bytes.
+// |part_file| is the partition file path, can be NULL if |path_init_file|
+// is not NULL.
+// |part_init_file| is an optional path to the initialization partition file.
+// |is_ext4| is true if the partition is formatted as EXT4, false for YAFFS2.
+//
+// The NAND partition will be backed by |path_file|, except in the following
+// cases:
+//    - |part_file| is NULL, or its value is "<temp>", indicating that a
+//      new temporary image file must be used instead.
+//
+//    - |part_file| is not NULL, but the function fails to lock the file,
+//      indicating it's already used by another instance. A warning should
+//      be printed to warn the user, and a new temporary image should be
+//      used.
+//
+// If |part_file| is not NULL and can be locked, if the partition image does
+// not exit, then the file must be created as an empty partition.
+//
+// When a new partition image is created, what happens depends on the
+// value of |is_ext4|:
+//
+//    - If |is_ext4| is false, a simple empty file is created, since that's
+//      enough to create an empty YAFFS2 partition.
+//
+//    - If |is_ext4| is true, an "empty ext4" partition image is created
+//      instead, which will _not_ be backed by an empty file.
+//
+// If |part_init_file| is not NULL, its content will be used to erase
+// the content of the main partition image. This is automatically handled
+// by the NAND code though.
+//
+void android_nand_add_image(const char* part_name,
+                            AndroidPartitionType part_type,
+                            uint64_t part_size,
+                            const char* part_file,
+                            const char* part_init_file)
+{
+    char tmp[PATH_MAX * 2 + 32];
+
+    // Sanitize parameters, an empty string must be the same as NULL.
+    if (part_file && !*part_file) {
+        part_file = NULL;
+    }
+    if (part_init_file && !*part_init_file) {
+        part_init_file = NULL;
+    }
+
+    // Sanity checks.
+    if (part_size == 0) {
+        PANIC("Invalid %s partition size 0x%" PRIx64, part_size);
+    }
+
+    if (part_init_file && !path_exists(part_init_file)) {
+        PANIC("Missing initial %s image: %s", part_name, part_init_file);
+    }
+
+    // As a special case, a |part_file| of '<temp>' means a temporary
+    // partition is needed.
+    if (part_file && !strcmp(part_file, "<temp>")) {
+        part_file = NULL;
+    }
+
+    // Verify partition type, or probe it if needed.
+    {
+        const char* image_file = NULL;
+        if (part_init_file && path_exists(part_init_file)) {
+            image_file = part_init_file;
+        } else if (part_file && path_exists(part_file)) {
+            image_file = part_file;
+        }
+
+        if (part_type == ANDROID_PARTITION_TYPE_UNKNOWN) {
+            if (!image_file) {
+                PANIC("Cannot determine type of %s partition: no image files!",
+                    part_name);
+            }
+            VERBOSE_PRINT(init, "Probing %s image file for partition type: %s",
+                        part_name, image_file);
+
+            part_type = androidPartitionType_probeFile(image_file);
+
+            VERBOSE_PRINT(init, "Found %s image format: %s",
+                        part_name, androidPartitionType_toString(part_type));
+        } else {
+            if (!image_file) {
+                PANIC("Cannot find any %s file!", part_name);
+            }
+
+            AndroidPartitionType image_type =
+                    androidPartitionType_probeFile(image_file);
+            if (image_type == ANDROID_PARTITION_TYPE_UNKNOWN) {
+                PANIC("Cannot determine %s partition type of: %s", part_name,
+                    image_file);
+            }
+
+            if (image_type != part_type) {
+                PANIC("Invalid %s partition image type: %s (expected %s)",
+                    part_name,
+                    androidPartitionType_toString(image_type),
+                    androidPartitionType_toString(part_type));
+            }
+        }
+    }
+
+    snprintf(tmp, sizeof tmp, "%s,size=0x%" PRIx64, part_name, part_size);
+
+    bool need_temp_partition = true;
+    bool need_make_empty = false;
+
+    if (part_file && *part_file) {
+        if (filelock_create(part_file) == NULL) {
+            fprintf(stderr,
+                    "WARNING: %s image already in use, changes will not persist!\n",
+                    part_name);
+        } else {
+            need_temp_partition = false;
+
+            if (!path_exists(part_file) && !part_init_file) {
+                need_make_empty = true;
+            }
+        }
+    }
+
+    // Do we need a temporary partition image ?
+    if (need_temp_partition) {
+        TempFile* temp_file = tempfile_create();
+        if (temp_file == NULL) {
+            PANIC("Could not create temp file for %s partition image: %s\n",
+                   part_name);
+        }
+        part_file = tempfile_path(temp_file);
+        VERBOSE_PRINT(init,
+                      "Mapping '%s' partition image to %s",
+                      part_name,
+                      part_file);
+
+        need_make_empty = true;
+    }
+
+    pstrcat(tmp, sizeof tmp, ",file=");
+    pstrcat(tmp, sizeof tmp, part_file);
+
+    // Do we need to make the partition image empty?
+    if (need_make_empty && !part_init_file) {
+        int ret = androidPartitionType_makeEmptyFile(part_type,
+                                                     part_size,
+                                                     part_file);
+        if (ret < 0) {
+            PANIC("Could not create %s image file at %s: %s",
+                  part_name,
+                  part_file,
+                  strerror(-ret));
+        }
+    }
+
+    if (part_init_file) {
+        if (!path_exists(part_init_file)) {
+            PANIC("Invalid initial %s image path: %s",
+                  part_name,
+                  part_init_file);
+        }
+        pstrcat(tmp, sizeof tmp, ",initfile=");
+        pstrcat(tmp, sizeof tmp, part_init_file);
+    }
+
+    if (part_type == ANDROID_PARTITION_TYPE_EXT4) {
+        // Using a nand device to approximate a block device until full
+        // support is added.
+        pstrcat(tmp, sizeof tmp,",pagesize=512,extrasize=0");
+    }
+
+    VERBOSE_PRINT(init, "%s partition format: %s", part_name,
+                  androidPartitionType_toString(part_type));
+
+    nand_add_dev(tmp);
+}
+
+
 int main(int argc, char **argv, char **envp)
 {
     const char *gdbstub_dev = NULL;
@@ -2852,100 +3039,35 @@
         }
     }
 
-    bool systemImageIsExt4 = false;
-    bool dataImageIsExt4 = false;
-    bool cacheImageIsExt4 = false;
-
     /* Initialize system partition image */
-    {
-        char        tmp[PATH_MAX+32];
-        const char* sysImage = android_hw->disk_systemPartition_path;
-        const char* initImage = android_hw->disk_systemPartition_initPath;
-        uint64_t    sysBytes = android_hw->disk_systemPartition_size;
-
-        if (sysBytes == 0) {
-            PANIC("Invalid system partition size: %" PRIu64, sysBytes);
-        }
-
-        snprintf(tmp,sizeof(tmp),"system,size=0x%" PRIx64, sysBytes);
-
-        const char* imageFile = NULL;
-        if (sysImage && *sysImage) {
-            imageFile = sysImage;
-            if (filelock_create(sysImage) == NULL) {
-                fprintf(stderr,"WARNING: System image already in use, changes will not persist!\n");
-                /* If there is no file= parameters, nand_add_dev will create
-                 * a temporary file to back the partition image. */
-            } else {
-                pstrcat(tmp,sizeof(tmp),",file=");
-                pstrcat(tmp,sizeof(tmp),sysImage);
-            }
-        }
-        if (initImage && *initImage) {
-            imageFile = initImage;
-            if (!path_exists(initImage)) {
-                PANIC("Invalid initial system image path: %s", initImage);
-            }
-            pstrcat(tmp,sizeof(tmp),",initfile=");
-            pstrcat(tmp,sizeof(tmp),initImage);
-        } else {
-            PANIC("Missing initial system image path!");
-        }
-        systemImageIsExt4 = imageFile && android_pathIsExt4PartitionImage(imageFile);
-        if (systemImageIsExt4) {
-            /* Using a nand device to approximate a block device until full
-             * support is added */
-            pstrcat(tmp,sizeof(tmp),",pagesize=512,extrasize=0");
-        }
-        VERBOSE_PRINT(init, "System partition format: %s",
-               systemImageIsExt4 ? "ext4" : "yaffs2");
-        nand_add_dev(tmp);
-    }
+    android_nand_add_image("system",
+                            ANDROID_PARTITION_TYPE_UNKNOWN,
+                            android_hw->disk_systemPartition_size,
+                            android_hw->disk_systemPartition_path,
+                            android_hw->disk_systemPartition_initPath);
 
     /* Initialize data partition image */
-    {
-        char        tmp[PATH_MAX+32];
-        const char* dataImage = android_hw->disk_dataPartition_path;
-        const char* initImage = android_hw->disk_dataPartition_initPath;
-        uint64_t    dataBytes = android_hw->disk_dataPartition_size;
+    android_nand_add_image("userdata",
+                            ANDROID_PARTITION_TYPE_UNKNOWN,
+                            android_hw->disk_dataPartition_size,
+                            android_hw->disk_dataPartition_path,
+                            android_hw->disk_dataPartition_initPath);
 
-        if (dataBytes == 0) {
-            PANIC("Invalid data partition size: %" PRIu64, dataBytes);
-        }
+    /* Initialize cache partition image, if any. Its type depends on the
+     * kernel version. For anything >= 3.10, it must be EXT4, or
+     * YAFFS2 otherwise.
+     */
+    if (android_hw->disk_cachePartition != 0) {
+        AndroidPartitionType cache_part_type =
+                (androidHwConfig_getKernelYaffs2Support(android_hw) >= 1) ?
+                        ANDROID_PARTITION_TYPE_YAFFS2 :
+                        ANDROID_PARTITION_TYPE_EXT4;
 
-        snprintf(tmp,sizeof(tmp),"userdata,size=0x%" PRIx64, dataBytes);
-
-        const char* imageFile = dataImage;
-        if (dataImage && *dataImage) {
-            if (filelock_create(dataImage) == NULL) {
-                fprintf(stderr, "WARNING: Data partition already in use. Changes will not persist!\n");
-                /* Note: if there is no file= parameters, nand_add_dev() will
-                 *       create a temporary file to back the partition image. */
-            } else {
-                /* Create the file if needed */
-                if (!path_exists(dataImage)) {
-                    if (path_empty_file(dataImage) < 0) {
-                        PANIC("Could not create data image file %s: %s", dataImage, strerror(errno));
-                    }
-                }
-                pstrcat(tmp, sizeof(tmp), ",file=");
-                pstrcat(tmp, sizeof(tmp), dataImage);
-            }
-        }
-        if (initImage && *initImage) {
-            imageFile = initImage;
-            pstrcat(tmp, sizeof(tmp), ",initfile=");
-            pstrcat(tmp, sizeof(tmp), initImage);
-        }
-        dataImageIsExt4 = imageFile && android_pathIsExt4PartitionImage(imageFile);
-        if (dataImageIsExt4) {
-            /* Using a nand device to approximate a block device until full
-             * support is added */
-            pstrcat(tmp, sizeof(tmp), ",pagesize=512,extrasize=0");
-        }
-        VERBOSE_PRINT(init, "Data partition format: %s",
-               dataImageIsExt4 ? "ext4" : "yaffs2");
-        nand_add_dev(tmp);
+        android_nand_add_image("cache",
+                                cache_part_type,
+                                android_hw->disk_cachePartition_size,
+                                android_hw->disk_cachePartition_path,
+                                NULL);
     }
 
     /* Init SD-Card stuff. For Android, it is always hda */
@@ -3162,52 +3284,6 @@
     }
 #endif  // CONFIG_ANDROID_MEMCHECK
 
-    /* Initialize cache partition, if any */
-    if (android_hw->disk_cachePartition != 0) {
-        char        tmp[PATH_MAX+32];
-        const char* partPath = android_hw->disk_cachePartition_path;
-        uint64_t    partSize = android_hw->disk_cachePartition_size;
-
-        // Kernels 3.10 and above do not support YAFFS2 anymore, which
-        // forces the cache partition to use EXT4 instead.
-        bool needExt4CachePartition =
-                (androidHwConfig_getKernelYaffs2Support(android_hw) >= 1);
-
-        snprintf(tmp,sizeof(tmp),"cache,size=0x%" PRIx64, partSize);
-
-        if (partPath && *partPath && strcmp(partPath, "<temp>") != 0) {
-            if (filelock_create(partPath) == NULL) {
-                fprintf(stderr, "WARNING: Cache partition already in use. Changes will not persist!\n");
-                /* Note: if there is no file= parameters, nand_add_dev() will
-                 *       create a temporary file to back the partition image. */
-            } else {
-                /* Create the file if needed */
-                if (!path_exists(partPath)) {
-                    int ret;
-                    if (needExt4CachePartition) {
-                        ret = android_createEmptyExt4Image(partPath, partSize, "cache");
-                    } else {
-                        ret = path_empty_file(partPath);
-                    }
-                    if (ret < 0) {
-                        PANIC("Could not create cache image file %s: %s", partPath, strerror(errno));
-                    }
-                }
-                pstrcat(tmp, sizeof(tmp), ",file=");
-                pstrcat(tmp, sizeof(tmp), partPath);
-            }
-        }
-        cacheImageIsExt4 = partPath && android_pathIsExt4PartitionImage(partPath);
-        if (cacheImageIsExt4) {
-            /* Using a nand device to approximate a block device until full
-             * support is added */
-            pstrcat(tmp, sizeof(tmp), ",pagesize=512,extrasize=0");
-        }
-        VERBOSE_PRINT(init, "Cache partition format: %s",
-               cacheImageIsExt4 ? "ext4" : "yaffs2");
-        nand_add_dev(tmp);
-    }
-
     /* qemu.gles will be read by the OpenGL ES emulation libraries.
      * If set to 0, the software GL ES renderer will be used as a fallback.
      * If the parameter is undefined, this means the system image runs