Add image mounting commands for OBB files

Allow the mounting of OBB filesystem images if they're encrypted with
twofish and in FAT filesystem format.

Change-Id: I54804e598f46b1f3a784ffe517ebd9d7626de7aa
diff --git a/CommandListener.cpp b/CommandListener.cpp
index 917b2fa..fead4f8 100644
--- a/CommandListener.cpp
+++ b/CommandListener.cpp
@@ -41,6 +41,7 @@
     registerCmd(new DumpCmd());
     registerCmd(new VolumeCmd());
     registerCmd(new AsecCmd());
+    registerCmd(new ImageCmd());
     registerCmd(new ShareCmd());
     registerCmd(new StorageCmd());
     registerCmd(new XwarpCmd());
@@ -397,6 +398,54 @@
     return 0;
 }
 
+CommandListener::ImageCmd::ImageCmd() :
+                 VoldCommand("image") {
+}
+
+int CommandListener::ImageCmd::runCommand(SocketClient *cli,
+                                                      int argc, char **argv) {
+    if (argc < 2) {
+        cli->sendMsg(ResponseCode::CommandSyntaxError, "Missing Argument", false);
+        return 0;
+    }
+
+    VolumeManager *vm = VolumeManager::Instance();
+    int rc = 0;
+
+    if (!strcmp(argv[1], "mount")) {
+            dumpArgs(argc, argv, 3);
+            if (argc != 5) {
+                cli->sendMsg(ResponseCode::CommandSyntaxError,
+                        "Usage: image mount <filename> <key> <ownerUid>", false);
+                return 0;
+            }
+            rc = vm->mountImage(argv[2], argv[3], atoi(argv[4]));
+    } else if (!strcmp(argv[1], "unmount")) {
+        dumpArgs(argc, argv, -1);
+        if (argc < 3) {
+            cli->sendMsg(ResponseCode::CommandSyntaxError, "Usage: image unmount <container-id> [force]", false);
+            return 0;
+        }
+        bool force = false;
+        if (argc > 3 && !strcmp(argv[3], "force")) {
+            force = true;
+        }
+        rc = vm->unmountImage(argv[2], force);
+    } else {
+        dumpArgs(argc, argv, -1);
+        cli->sendMsg(ResponseCode::CommandSyntaxError, "Unknown image cmd", false);
+    }
+
+    if (!rc) {
+        cli->sendMsg(ResponseCode::CommandOkay, "image operation succeeded", false);
+    } else {
+        rc = ResponseCode::convertFromErrno();
+        cli->sendMsg(rc, "image operation failed", true);
+    }
+
+    return 0;
+}
+
 CommandListener::XwarpCmd::XwarpCmd() :
                  VoldCommand("xwarp") {
 }
diff --git a/CommandListener.h b/CommandListener.h
index ca4fc97..23933bc 100644
--- a/CommandListener.h
+++ b/CommandListener.h
@@ -56,6 +56,13 @@
         int runCommand(SocketClient *c, int argc, char ** argv);
     };
 
+    class ImageCmd : public VoldCommand {
+    public:
+        ImageCmd();
+        virtual ~ImageCmd() {}
+        int runCommand(SocketClient *c, int argc, char ** argv);
+    };
+
     class StorageCmd : public VoldCommand {
     public:
         StorageCmd();
diff --git a/Volume.cpp b/Volume.cpp
index 2f3cad5..f43d375 100644
--- a/Volume.cpp
+++ b/Volume.cpp
@@ -73,6 +73,11 @@
  */
 const char *Volume::ASECDIR           = "/mnt/asec";
 
+/*
+ * Path to where loop devices are mounted
+ */
+const char *Volume::LOOPDIR           = "/mnt/loop";
+
 static const char *stateToStr(int state) {
     if (state == Volume::State_Init)
         return "Initializing";
diff --git a/Volume.h b/Volume.h
index 290b66d..383c2e4 100644
--- a/Volume.h
+++ b/Volume.h
@@ -44,6 +44,8 @@
     static const char *SEC_ASECDIR;
     static const char *ASECDIR;
 
+    static const char *LOOPDIR;
+
 protected:
     char *mLabel;
     char *mMountpoint;
diff --git a/VolumeManager.cpp b/VolumeManager.cpp
index 7ce25b0..2127eab 100644
--- a/VolumeManager.cpp
+++ b/VolumeManager.cpp
@@ -505,7 +505,8 @@
     return -1;
 }
 
-#define ASEC_UNMOUNT_RETRIES 5
+#define UNMOUNT_RETRIES 5
+#define UNMOUNT_SLEEP_BETWEEN_RETRY_MS (1000 * 1000)
 int VolumeManager::unmountAsec(const char *id, bool force) {
     char asecFileName[255];
     char mountPoint[255];
@@ -519,37 +520,56 @@
         return -1;
     }
 
+    return unmountLoopImage(id, idHash, asecFileName, mountPoint, force);
+}
+
+int VolumeManager::unmountImage(const char *fileName, bool force) {
+    char mountPoint[255];
+
+    char idHash[33];
+    if (!asecHash(fileName, idHash, sizeof(idHash))) {
+        SLOGE("Hash of '%s' failed (%s)", fileName, strerror(errno));
+        return -1;
+    }
+
+    snprintf(mountPoint, sizeof(mountPoint), "%s/%s", Volume::LOOPDIR, idHash);
+
+    return unmountLoopImage(fileName, idHash, fileName, mountPoint, force);
+}
+
+int VolumeManager::unmountLoopImage(const char *id, const char *idHash,
+        const char *fileName, const char *mountPoint, bool force) {
     if (!isMountpointMounted(mountPoint)) {
-        SLOGE("Unmount request for ASEC %s when not mounted", id);
+        SLOGE("Unmount request for %s when not mounted", id);
         errno = EINVAL;
         return -1;
     }
 
     int i, rc;
-    for (i = 1; i <= ASEC_UNMOUNT_RETRIES; i++) {
+    for (i = 1; i <= UNMOUNT_RETRIES; i++) {
         rc = umount(mountPoint);
         if (!rc) {
             break;
         }
         if (rc && (errno == EINVAL || errno == ENOENT)) {
-            SLOGI("Secure container %s unmounted OK", id);
+            SLOGI("Container %s unmounted OK", id);
             rc = 0;
             break;
         }
-        SLOGW("ASEC %s unmount attempt %d failed (%s)",
+        SLOGW("%s unmount attempt %d failed (%s)",
               id, i, strerror(errno));
 
         int action = 0; // default is to just complain
 
         if (force) {
-            if (i > (ASEC_UNMOUNT_RETRIES - 2))
+            if (i > (UNMOUNT_RETRIES - 2))
                 action = 2; // SIGKILL
-            else if (i > (ASEC_UNMOUNT_RETRIES - 3))
+            else if (i > (UNMOUNT_RETRIES - 3))
                 action = 1; // SIGHUP
         }
 
         Process::killProcessesWithOpenFiles(mountPoint, action);
-        usleep(1000 * 1000);
+        usleep(UNMOUNT_SLEEP_BETWEEN_RETRY_MS);
     }
 
     if (rc) {
@@ -566,7 +586,7 @@
         }
 
         SLOGW("Failed to rmdir %s (%s)", mountPoint, strerror(errno));
-        usleep(1000 * 1000);
+        usleep(UNMOUNT_SLEEP_BETWEEN_RETRY_MS);
     }
 
     if (!retries) {
@@ -581,7 +601,7 @@
     if (!Loop::lookupActive(idHash, loopDevice, sizeof(loopDevice))) {
         Loop::destroyByDevice(loopDevice);
     } else {
-        SLOGW("Failed to find loop device for {%s} (%s)", asecFileName, strerror(errno));
+        SLOGW("Failed to find loop device for {%s} (%s)", fileName, strerror(errno));
     }
 
     AsecIdCollection::iterator it;
@@ -759,6 +779,124 @@
     return 0;
 }
 
+/**
+ * Mounts an image file <code>img</code>.
+ */
+int VolumeManager::mountImage(const char *img, const char *key, int ownerUid) {
+    char mountPoint[255];
+
+#if 0
+    struct stat imgStat;
+    if (stat(img, &imgStat) != 0) {
+        SLOGE("Could not stat '%s': %s", img, strerror(errno));
+        return -1;
+    }
+
+    if (imgStat.st_uid != ownerUid) {
+        SLOGW("Image UID does not match requestor UID (%d != %d)",
+                imgStat.st_uid, ownerUid);
+        return -1;
+    }
+#endif
+
+    char idHash[33];
+    if (!asecHash(img, idHash, sizeof(idHash))) {
+        SLOGE("Hash of '%s' failed (%s)", img, strerror(errno));
+        return -1;
+    }
+
+    snprintf(mountPoint, sizeof(mountPoint), "%s/%s", Volume::LOOPDIR, idHash);
+
+    if (isMountpointMounted(mountPoint)) {
+        SLOGE("Image %s already mounted", img);
+        errno = EBUSY;
+        return -1;
+    }
+
+    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);
+        }
+    }
+
+    char dmDevice[255];
+    bool cleanupDm = false;
+    int fd;
+    unsigned int nr_sec = 0;
+
+    if ((fd = open(loopDevice, O_RDWR)) < 0) {
+        SLOGE("Failed to open loopdevice (%s)", strerror(errno));
+        Loop::destroyByDevice(loopDevice);
+        return -1;
+    }
+
+    if (ioctl(fd, BLKGETSIZE, &nr_sec)) {
+        SLOGE("Failed to get loop size (%s)", strerror(errno));
+        Loop::destroyByDevice(loopDevice);
+        close(fd);
+        return -1;
+    }
+
+    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 (mkdir(mountPoint, 0755)) {
+        if (errno != EEXIST) {
+            SLOGE("Mountpoint creation failed (%s)", strerror(errno));
+            if (cleanupDm) {
+                Devmapper::destroy(idHash);
+            }
+            Loop::destroyByDevice(loopDevice);
+            return -1;
+        }
+    }
+
+    if (Fat::doMount(dmDevice, mountPoint, true, false, ownerUid, 0,
+                     0227, false)) {
+        SLOGE("Image mount failed (%s)", strerror(errno));
+        if (cleanupDm) {
+            Devmapper::destroy(idHash);
+        }
+        Loop::destroyByDevice(loopDevice);
+        return -1;
+    }
+
+    mActiveContainers->push_back(strdup(img));
+    if (mDebug) {
+        SLOGD("Image %s mounted", img);
+    }
+    return 0;
+}
+
 int VolumeManager::mountVolume(const char *label) {
     Volume *v = lookupVolume(label);
 
diff --git a/VolumeManager.h b/VolumeManager.h
index f807beb..31870e5 100644
--- a/VolumeManager.h
+++ b/VolumeManager.h
@@ -63,6 +63,8 @@
     int shareEnabled(const char *path, const char *method, bool *enabled);
     int simulate(const char *cmd, const char *arg);
     int formatVolume(const char *label);
+
+    /* ASEC */
     int createAsec(const char *id, unsigned numSectors, const char *fstype,
                    const char *key, int ownerUid);
     int finalizeAsec(const char *id);
@@ -72,6 +74,14 @@
     int renameAsec(const char *id1, const char *id2);
     int getAsecMountPath(const char *id, char *buffer, int maxlen);
 
+    /* Loopback images */
+    int mountImage(const char *fileName, const char *key, int ownerUid);
+    int unmountImage(const char *fileName, bool force);
+
+    /* Shared between ASEC and Loopback images */
+    int unmountLoopImage(const char *containerId, const char *loopId,
+            const char *fileName, const char *mountPoint, bool force);
+
     void setDebug(bool enable);
 
     // XXX: Post froyo this should be moved and cleaned up