Added support for ext4 ASEC resizing.

ASECs formatted as ext4 can now be resized using vdc asec resize.
Refactored some common code.
Requires resize2fs.

Change-Id: Ie78bb6015114a7bc4af42b16d1f299322ffc1e2a
Signed-off-by: Daniel Rosenberg <drosen@google.com>
diff --git a/CommandListener.cpp b/CommandListener.cpp
index 3e984a1..d2b061f 100644
--- a/CommandListener.cpp
+++ b/CommandListener.cpp
@@ -359,6 +359,14 @@
         unsigned int numSectors = (atoi(argv[3]) * (1024 * 1024)) / 512;
         const bool isExternal = (atoi(argv[7]) == 1);
         rc = vm->createAsec(argv[2], numSectors, argv[4], argv[5], atoi(argv[6]), isExternal);
+    } else if (!strcmp(argv[1], "resize")) {
+        dumpArgs(argc, argv, -1);
+        if (argc != 5) {
+            cli->sendMsg(ResponseCode::CommandSyntaxError, "Usage: asec resize <container-id> <size_mb> <key>", false);
+            return 0;
+        }
+        unsigned int numSectors = (atoi(argv[3]) * (1024 * 1024)) / 512;
+        rc = vm->resizeAsec(argv[2], numSectors, argv[4]);
     } else if (!strcmp(argv[1], "finalize")) {
         dumpArgs(argc, argv, -1);
         if (argc != 3) {
diff --git a/Devmapper.cpp b/Devmapper.cpp
index 6f43ac0..700e538 100644
--- a/Devmapper.cpp
+++ b/Devmapper.cpp
@@ -125,8 +125,8 @@
     io->flags = flags;
     if (name) {
         size_t ret = strlcpy(io->name, name, sizeof(io->name));
-	if (ret >= sizeof(io->name))
-		abort();
+        if (ret >= sizeof(io->name))
+            abort();
     }
 }
 
diff --git a/Ext4.cpp b/Ext4.cpp
index acf1777..dc31fd0 100644
--- a/Ext4.cpp
+++ b/Ext4.cpp
@@ -43,7 +43,8 @@
 #include "Ext4.h"
 #include "VoldUtil.h"
 
-#define MKEXT4FS_PATH "/system/bin/make_ext4fs";
+#define MKEXT4FS_PATH "/system/bin/make_ext4fs"
+#define RESIZE2FS_PATH "/system/bin/resize2fs"
 
 int Ext4::doMount(const char *fsPath, const char *mountPoint, bool ro, bool remount,
         bool executable) {
@@ -67,6 +68,49 @@
     return rc;
 }
 
+int Ext4::resize(const char *fspath, unsigned int numSectors) {
+    const char *args[4];
+    char* size_str;
+    int rc;
+    int status;
+
+    args[0] = RESIZE2FS_PATH;
+    args[1] = "-f";
+    args[2] = fspath;
+    if (asprintf(&size_str, "%ds", numSectors) < 0)
+    {
+      SLOGE("Filesystem (ext4) resize failed to set size");
+      return -1;
+    }
+    args[3] = size_str;
+    rc = android_fork_execvp(ARRAY_SIZE(args), (char **)args, &status, false,
+            true);
+    free(size_str);
+    if (rc != 0) {
+        SLOGE("Filesystem (ext4) resize failed due to logwrap error");
+        errno = EIO;
+        return -1;
+    }
+
+    if (!WIFEXITED(status)) {
+        SLOGE("Filesystem (ext4) resize did not exit properly");
+        errno = EIO;
+        return -1;
+    }
+
+    status = WEXITSTATUS(status);
+
+    if (status == 0) {
+        SLOGI("Filesystem (ext4) resized OK");
+        return 0;
+    } else {
+        SLOGE("Resize (ext4) failed (unknown exit code %d)", status);
+        errno = EIO;
+        return -1;
+    }
+    return 0;
+}
+
 int Ext4::format(const char *fsPath, unsigned int numSectors, const char *mountpoint) {
     int fd;
     const char *args[7];
diff --git a/Ext4.h b/Ext4.h
index 54fbec3..c768f5a 100644
--- a/Ext4.h
+++ b/Ext4.h
@@ -24,6 +24,7 @@
     static int doMount(const char *fsPath, const char *mountPoint, bool ro, bool remount,
             bool executable);
     static int format(const char *fsPath, unsigned int numSectors, const char *mountpoint);
+    static int resize(const char *fsPath, unsigned int numSectors);
 };
 
 #endif
diff --git a/Loop.cpp b/Loop.cpp
index 3f0ee1e..8672d93 100644
--- a/Loop.cpp
+++ b/Loop.cpp
@@ -249,6 +249,34 @@
     return 0;
 }
 
+int Loop::resizeImageFile(const char *file, unsigned int numSectors) {
+    int fd;
+
+    if ((fd = open(file, O_RDWR)) < 0) {
+        SLOGE("Error opening imagefile (%s)", strerror(errno));
+        return -1;
+    }
+
+    SLOGD("Attempting to increase size of %s to %d sectors.", file, numSectors);
+
+    if (fallocate(fd, 0, 0, numSectors * 512)) {
+        if (errno == ENOSYS) {
+            SLOGW("fallocate not found. Falling back to ftruncate.");
+            if (ftruncate(fd, numSectors * 512) < 0) {
+                SLOGE("Error truncating imagefile (%s)", strerror(errno));
+                close(fd);
+                return -1;
+            }
+        } else {
+            SLOGE("Error allocating space (%s)", strerror(errno));
+            close(fd);
+            return -1;
+        }
+    }
+    close(fd);
+    return 0;
+}
+
 int Loop::lookupInfo(const char *loopDevice, struct asec_superblock *sb, unsigned int *nr_sec) {
     int fd;
     struct asec_superblock buffer;
diff --git a/Loop.h b/Loop.h
index d717cf0..45e6115 100644
--- a/Loop.h
+++ b/Loop.h
@@ -32,6 +32,7 @@
     static int destroyByDevice(const char *loopDevice);
     static int destroyByFile(const char *loopFile);
     static int createImageFile(const char *file, unsigned int numSectors);
+    static int resizeImageFile(const char *file, unsigned int numSectors);
 
     static int dumpState(SocketClient *c);
 };
diff --git a/VolumeManager.cpp b/VolumeManager.cpp
index 9889653..796cb11 100644
--- a/VolumeManager.cpp
+++ b/VolumeManager.cpp
@@ -24,6 +24,7 @@
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <sys/mount.h>
+#include <sys/ioctl.h>
 #include <dirent.h>
 
 #include <linux/kdev_t.h>
@@ -57,6 +58,101 @@
 #define ROUND_UP_POWER_OF_2(number, po2) (((!!(number & ((1U << po2) - 1))) << po2)\
                                          + (number & (~((1U << po2) - 1))))
 
+/* writes superblock at end of file or device given by name */
+static int writeSuperBlock(const char* name, struct asec_superblock *sb, unsigned int numImgSectors) {
+    int sbfd = open(name, O_RDWR);
+    if (sbfd < 0) {
+        SLOGE("Failed to open %s for superblock write (%s)", name, strerror(errno));
+        return -1;
+    }
+
+    if (lseek(sbfd, (numImgSectors * 512), SEEK_SET) < 0) {
+        SLOGE("Failed to lseek for superblock (%s)", strerror(errno));
+        close(sbfd);
+        return -1;
+    }
+
+    if (write(sbfd, sb, sizeof(struct asec_superblock)) != sizeof(struct asec_superblock)) {
+        SLOGE("Failed to write superblock (%s)", strerror(errno));
+        close(sbfd);
+        return -1;
+    }
+    close(sbfd);
+    return 0;
+}
+
+static int adjustSectorNumExt4(unsigned numSectors) {
+    return ROUND_UP_POWER_OF_2(numSectors, 3);
+}
+
+static int adjustSectorNumFAT(unsigned numSectors) {
+    /*
+    * Add some headroom
+    */
+    unsigned fatSize = (((numSectors * 4) / 512) + 1) * 2;
+    numSectors += fatSize + 2;
+    /*
+    * FAT is aligned to 32 kb with 512b sectors.
+    */
+    return ROUND_UP_POWER_OF_2(numSectors, 6);
+}
+
+static int setupLoopDevice(char* buffer, size_t len, const char* asecFileName, const char* idHash, bool debug) {
+    if (Loop::lookupActive(idHash, buffer, len)) {
+        if (Loop::create(idHash, asecFileName, buffer, len)) {
+            SLOGE("ASEC loop device creation failed for %s (%s)", asecFileName, strerror(errno));
+            return -1;
+        }
+        if (debug) {
+            SLOGD("New loop device created at %s", buffer);
+        }
+    } else {
+        if (debug) {
+            SLOGD("Found active loopback for %s at %s", asecFileName, buffer);
+        }
+    }
+    return 0;
+}
+
+static int setupDevMapperDevice(char* buffer, size_t len, const char* loopDevice, const char* asecFileName, const char* key, const char* idHash , int numImgSectors, bool* createdDMDevice, bool debug) {
+    if (strcmp(key, "none")) {
+        if (Devmapper::lookupActive(idHash, buffer, len)) {
+            if (Devmapper::create(idHash, loopDevice, key, numImgSectors,
+                                  buffer, len)) {
+                SLOGE("ASEC device mapping failed for %s (%s)", asecFileName, strerror(errno));
+                return -1;
+            }
+            if (debug) {
+                SLOGD("New devmapper instance created at %s", buffer);
+            }
+        } else {
+            if (debug) {
+                SLOGD("Found active devmapper for %s at %s", asecFileName, buffer);
+            }
+        }
+        *createdDMDevice = true;
+    } else {
+        strcpy(buffer, loopDevice);
+        *createdDMDevice = false;
+    }
+    return 0;
+}
+
+static void waitForDevMapper(const char *dmDevice) {
+    /*
+     * Wait for the device mapper node to be created. Sometimes it takes a
+     * while. Wait for up to 1 second. We could also inspect incoming uevents,
+     * but that would take more effort.
+     */
+    int tries = 25;
+    while (tries--) {
+        if (!access(dmDevice, F_OK) || errno != ENOENT) {
+            break;
+        }
+        usleep(40 * 1000);
+    }
+}
+
 VolumeManager *VolumeManager::sInstance = NULL;
 
 VolumeManager *VolumeManager::Instance() {
@@ -333,19 +429,11 @@
         return -1;
     }
 
-    /*
-     * Add some headroom
-     * This is likely off by a bit, and we may want to do something different for ext4
-     */
-    unsigned fatSize = (((numSectors * 4) / 512) + 1) * 2;
-    unsigned numImgSectors = numSectors + fatSize + 2;
-    /*
-     * ext4 is aligned to 4kb. fat is aligned to 32kb. Sectors are 512b.
-     */
+    unsigned numImgSectors;
     if (usingExt4)
-        numImgSectors = ROUND_UP_POWER_OF_2(numImgSectors, 3);
+        numImgSectors = adjustSectorNumExt4(numSectors);
     else
-        numImgSectors = ROUND_UP_POWER_OF_2(numImgSectors, 6);
+        numImgSectors = adjustSectorNumFAT(numSectors);
 
     // Add +1 for our superblock which is at the end
     if (Loop::createImageFile(asecFileName, numImgSectors + 1)) {
@@ -389,10 +477,7 @@
     /*
      * Drop down the superblock at the end of the file
      */
-
-    int sbfd = open(loopDevice, O_RDWR);
-    if (sbfd < 0) {
-        SLOGE("Failed to open new DM device for superblock write (%s)", strerror(errno));
+    if (writeSuperBlock(loopDevice, &sb, numImgSectors)) {
         if (cleanupDm) {
             Devmapper::destroy(idHash);
         }
@@ -401,29 +486,6 @@
         return -1;
     }
 
-    if (lseek(sbfd, (numImgSectors * 512), SEEK_SET) < 0) {
-        close(sbfd);
-        SLOGE("Failed to lseek for superblock (%s)", strerror(errno));
-        if (cleanupDm) {
-            Devmapper::destroy(idHash);
-        }
-        Loop::destroyByDevice(loopDevice);
-        unlink(asecFileName);
-        return -1;
-    }
-
-    if (write(sbfd, &sb, sizeof(sb)) != sizeof(sb)) {
-        close(sbfd);
-        SLOGE("Failed to write superblock (%s)", strerror(errno));
-        if (cleanupDm) {
-            Devmapper::destroy(idHash);
-        }
-        Loop::destroyByDevice(loopDevice);
-        unlink(asecFileName);
-        return -1;
-    }
-    close(sbfd);
-
     if (wantFilesystem) {
         int formatStatus;
         char mountPoint[255];
@@ -503,6 +565,146 @@
     return 0;
 }
 
+int VolumeManager::resizeAsec(const char *id, unsigned numSectors, const char *key) {
+    char asecFileName[255];
+    char mountPoint[255];
+    bool cleanupDm = false;
+
+    if (!isLegalAsecId(id)) {
+        SLOGE("resizeAsec: Invalid asec id \"%s\"", id);
+        errno = EINVAL;
+        return -1;
+    }
+
+    if (findAsec(id, asecFileName, sizeof(asecFileName))) {
+        SLOGE("Couldn't find ASEC %s", id);
+        return -1;
+    }
+
+    int written = snprintf(mountPoint, sizeof(mountPoint), "%s/%s", Volume::ASECDIR, id);
+    if ((written < 0) || (size_t(written) >= sizeof(mountPoint))) {
+       SLOGE("ASEC resize failed for %s: couldn't construct mountpoint", id);
+       return -1;
+    }
+
+    if (isMountpointMounted(mountPoint)) {
+       SLOGE("ASEC %s mounted. Unmount before resizing", id);
+       errno = EBUSY;
+       return -1;
+    }
+
+    struct asec_superblock sb;
+    int fd;
+    unsigned int oldNumSec = 0;
+
+    if ((fd = open(asecFileName, O_RDONLY)) < 0) {
+        SLOGE("Failed to open ASEC file (%s)", strerror(errno));
+        return -1;
+    }
+
+    struct stat info;
+    if (fstat(fd, &info) < 0) {
+        SLOGE("Failed to get file size (%s)", strerror(errno));
+        close(fd);
+        return -1;
+    }
+
+    oldNumSec = info.st_size / 512;
+
+    unsigned numImgSectors;
+    if (sb.c_opts & ASEC_SB_C_OPTS_EXT4)
+        numImgSectors = adjustSectorNumExt4(numSectors);
+    else
+        numImgSectors = adjustSectorNumFAT(numSectors);
+    /*
+     *  add one block for the superblock
+     */
+    SLOGD("Resizing from %d sectors to %d sectors", oldNumSec, numImgSectors + 1);
+    if (oldNumSec >= numImgSectors + 1) {
+        SLOGE("Only growing is currently supported.");
+        close(fd);
+        return -1;
+    }
+
+    /*
+     * Try to read superblock.
+     */
+    memset(&sb, 0, sizeof(struct asec_superblock));
+    if (lseek(fd, ((oldNumSec - 1) * 512), SEEK_SET) < 0) {
+        SLOGE("lseek failed (%s)", strerror(errno));
+        close(fd);
+        return -1;
+    }
+    if (read(fd, &sb, sizeof(struct asec_superblock)) != sizeof(struct asec_superblock)) {
+        SLOGE("superblock read failed (%s)", strerror(errno));
+        close(fd);
+        return -1;
+    }
+    close(fd);
+
+    if (mDebug) {
+        SLOGD("Container sb magic/ver (%.8x/%.2x)", sb.magic, sb.ver);
+    }
+    if (sb.magic != ASEC_SB_MAGIC || sb.ver != ASEC_SB_VER) {
+        SLOGE("Bad container magic/version (%.8x/%.2x)", sb.magic, sb.ver);
+        errno = EMEDIUMTYPE;
+        return -1;
+    }
+
+    if (!(sb.c_opts & ASEC_SB_C_OPTS_EXT4)) {
+        SLOGE("Only ext4 partitions are supported for resize");
+        errno = EINVAL;
+        return -1;
+    }
+
+    if (Loop::resizeImageFile(asecFileName, numImgSectors + 1)) {
+        SLOGE("Resize of ASEC image file failed. Could not resize %s", id);
+        return -1;
+    }
+
+    /*
+     * Drop down a copy of the superblock at the end of the file
+     */
+    if (writeSuperBlock(asecFileName, &sb, numImgSectors))
+        goto fail;
+
+    char idHash[33];
+    if (!asecHash(id, idHash, sizeof(idHash))) {
+        SLOGE("Hash of '%s' failed (%s)", id, strerror(errno));
+        goto fail;
+    }
+
+    char loopDevice[255];
+    if (setupLoopDevice(loopDevice, sizeof(loopDevice), asecFileName, idHash, mDebug))
+        goto fail;
+
+    char dmDevice[255];
+
+    if (setupDevMapperDevice(dmDevice, sizeof(dmDevice), loopDevice, asecFileName, key, idHash, numImgSectors, &cleanupDm, mDebug)) {
+        Loop::destroyByDevice(loopDevice);
+        goto fail;
+    }
+
+    /*
+     * Wait for the device mapper node to be created.
+     */
+    waitForDevMapper(dmDevice);
+
+    if (Ext4::resize(dmDevice, numImgSectors)) {
+        SLOGE("Unable to resize %s (%s)", id, strerror(errno));
+        if (cleanupDm) {
+            Devmapper::destroy(idHash);
+        }
+        Loop::destroyByDevice(loopDevice);
+        goto fail;
+    }
+
+    return 0;
+fail:
+    Loop::resizeImageFile(asecFileName, oldNumSec);
+    return -1;
+}
+
 int VolumeManager::finalizeAsec(const char *id) {
     char asecFileName[255];
     char loopDevice[255];
@@ -1060,19 +1262,8 @@
     }
 
     char loopDevice[255];
-    if (Loop::lookupActive(idHash, loopDevice, sizeof(loopDevice))) {
-        if (Loop::create(idHash, asecFileName, loopDevice, sizeof(loopDevice))) {
-            SLOGE("ASEC loop device creation failed (%s)", strerror(errno));
-            return -1;
-        }
-        if (mDebug) {
-            SLOGD("New loop device created at %s", loopDevice);
-        }
-    } else {
-        if (mDebug) {
-            SLOGD("Found active loopback for %s at %s", asecFileName, loopDevice);
-        }
-    }
+    if (setupLoopDevice(loopDevice, sizeof(loopDevice), asecFileName, idHash, mDebug))
+        return -1;
 
     char dmDevice[255];
     bool cleanupDm = false;
@@ -1095,25 +1286,9 @@
     }
     nr_sec--; // We don't want the devmapping to extend onto our superblock
 
-    if (strcmp(key, "none")) {
-        if (Devmapper::lookupActive(idHash, dmDevice, sizeof(dmDevice))) {
-            if (Devmapper::create(idHash, loopDevice, key, nr_sec,
-                                  dmDevice, sizeof(dmDevice))) {
-                SLOGE("ASEC device mapping failed (%s)", strerror(errno));
-                Loop::destroyByDevice(loopDevice);
-                return -1;
-            }
-            if (mDebug) {
-                SLOGD("New devmapper instance created at %s", dmDevice);
-            }
-        } else {
-            if (mDebug) {
-                SLOGD("Found active devmapper for %s at %s", asecFileName, dmDevice);
-            }
-        }
-        cleanupDm = true;
-    } else {
-        strcpy(dmDevice, loopDevice);
+    if (setupDevMapperDevice(dmDevice, sizeof(dmDevice), loopDevice, asecFileName, key, idHash , nr_sec, &cleanupDm, mDebug)) {
+        Loop::destroyByDevice(loopDevice);
+        return -1;
     }
 
     if (mkdir(mountPoint, 0000)) {
@@ -1128,17 +1303,9 @@
     }
 
     /*
-     * The device mapper node needs to be created. Sometimes it takes a
-     * while. Wait for up to 1 second. We could also inspect incoming uevents,
-     * but that would take more effort.
+     * Wait for the device mapper node to be created.
      */
-    int tries = 25;
-    while (tries--) {
-        if (!access(dmDevice, F_OK) || errno != ENOENT) {
-            break;
-        }
-        usleep(40 * 1000);
-    }
+    waitForDevMapper(dmDevice);
 
     int result;
     if (sb.c_opts & ASEC_SB_C_OPTS_EXT4) {
@@ -1201,19 +1368,8 @@
     }
 
     char loopDevice[255];
-    if (Loop::lookupActive(idHash, loopDevice, sizeof(loopDevice))) {
-        if (Loop::create(idHash, img, loopDevice, sizeof(loopDevice))) {
-            SLOGE("Image loop device creation failed (%s)", strerror(errno));
-            return -1;
-        }
-        if (mDebug) {
-            SLOGD("New loop device created at %s", loopDevice);
-        }
-    } else {
-        if (mDebug) {
-            SLOGD("Found active loopback for %s at %s", img, loopDevice);
-        }
-    }
+    if (setupLoopDevice(loopDevice, sizeof(loopDevice), img, idHash, mDebug))
+        return -1;
 
     char dmDevice[255];
     bool cleanupDm = false;
@@ -1235,25 +1391,9 @@
 
     close(fd);
 
-    if (strcmp(key, "none")) {
-        if (Devmapper::lookupActive(idHash, dmDevice, sizeof(dmDevice))) {
-            if (Devmapper::create(idHash, loopDevice, key, nr_sec,
-                                  dmDevice, sizeof(dmDevice))) {
-                SLOGE("ASEC device mapping failed (%s)", strerror(errno));
-                Loop::destroyByDevice(loopDevice);
-                return -1;
-            }
-            if (mDebug) {
-                SLOGD("New devmapper instance created at %s", dmDevice);
-            }
-        } else {
-            if (mDebug) {
-                SLOGD("Found active devmapper for %s at %s", img, dmDevice);
-            }
-        }
-        cleanupDm = true;
-    } else {
-        strcpy(dmDevice, loopDevice);
+    if (setupDevMapperDevice(dmDevice, sizeof(loopDevice), loopDevice, img,key, idHash , nr_sec, &cleanupDm, mDebug)) {
+        Loop::destroyByDevice(loopDevice);
+        return -1;
     }
 
     if (mkdir(mountPoint, 0755)) {
diff --git a/VolumeManager.h b/VolumeManager.h
index cc8958d..07fd00e 100644
--- a/VolumeManager.h
+++ b/VolumeManager.h
@@ -91,6 +91,7 @@
             const char **directory = NULL) const;
     int createAsec(const char *id, unsigned numSectors, const char *fstype,
                    const char *key, const int ownerUid, bool isExternal);
+    int resizeAsec(const char *id, unsigned numSectors, const char *key);
     int finalizeAsec(const char *id);
 
     /**