am 08da5c1f: Merge "vold: replace strsep by strtok_r"

* commit '08da5c1f17afefe3c9f4f4d4456c5757dede62e1':
  vold: replace strsep by strtok_r
diff --git a/Android.mk b/Android.mk
index ce1a603..e353a4f 100644
--- a/Android.mk
+++ b/Android.mk
@@ -7,38 +7,59 @@
 
 LOCAL_PATH:= $(call my-dir)
 
+common_src_files := \
+	VolumeManager.cpp \
+	CommandListener.cpp \
+	VoldCommand.cpp \
+	NetlinkManager.cpp \
+	NetlinkHandler.cpp \
+	Volume.cpp \
+	DirectVolume.cpp \
+	logwrapper.c \
+	Process.cpp \
+	Fat.cpp \
+	Loop.cpp \
+	Devmapper.cpp \
+	ResponseCode.cpp \
+	Xwarp.cpp
+
+common_c_includes := \
+	$(KERNEL_HEADERS) \
+	external/openssl/include
+
+common_shared_libraries := \
+	libsysutils \
+	libcutils \
+	libdiskconfig \
+	libcrypto
+
 include $(CLEAR_VARS)
 
-LOCAL_SRC_FILES:=                                      \
-                  main.cpp                             \
-                  VolumeManager.cpp                    \
-                  CommandListener.cpp                  \
-                  VoldCommand.cpp                      \
-                  NetlinkManager.cpp                   \
-                  NetlinkHandler.cpp                   \
-                  Volume.cpp                           \
-                  DirectVolume.cpp                     \
-                  logwrapper.c                         \
-                  Process.cpp                          \
-                  Fat.cpp                              \
-                  Loop.cpp                             \
-                  Devmapper.cpp                        \
-                  ResponseCode.cpp                     \
-                  Xwarp.cpp
+LOCAL_MODULE := libvold
+
+LOCAL_SRC_FILES := $(common_src_files)
+
+LOCAL_C_INCLUDES := $(common_c_includes)
+
+LOCAL_SHARED_LIBRARIES := $(common_shared_libraries)
+
+LOCAL_MODULE_TAGS := eng tests
+
+include $(BUILD_STATIC_LIBRARY)
+
+include $(CLEAR_VARS)
 
 LOCAL_MODULE:= vold
 
-LOCAL_C_INCLUDES :=                          \
-                    $(KERNEL_HEADERS)        \
-                    external/openssl/include
+LOCAL_SRC_FILES := \
+	main.cpp \
+	$(common_src_files)
+
+LOCAL_C_INCLUDES := $(common_c_includes)
 
 LOCAL_CFLAGS := 
 
-LOCAL_SHARED_LIBRARIES :=               \
-                          libsysutils   \
-                          libcutils     \
-                          libdiskconfig \
-                          libcrypto
+LOCAL_SHARED_LIBRARIES := $(common_shared_libraries)
 
 include $(BUILD_EXECUTABLE)
 
diff --git a/CommandListener.cpp b/CommandListener.cpp
index 917b2fa..b0fc551 100644
--- a/CommandListener.cpp
+++ b/CommandListener.cpp
@@ -41,6 +41,7 @@
     registerCmd(new DumpCmd());
     registerCmd(new VolumeCmd());
     registerCmd(new AsecCmd());
+    registerCmd(new ObbCmd());
     registerCmd(new ShareCmd());
     registerCmd(new StorageCmd());
     registerCmd(new XwarpCmd());
@@ -397,6 +398,70 @@
     return 0;
 }
 
+CommandListener::ObbCmd::ObbCmd() :
+                 VoldCommand("obb") {
+}
+
+int CommandListener::ObbCmd::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], "list")) {
+        dumpArgs(argc, argv, -1);
+
+        rc = vm->listMountedObbs(cli);
+    } else if (!strcmp(argv[1], "mount")) {
+            dumpArgs(argc, argv, 3);
+            if (argc != 5) {
+                cli->sendMsg(ResponseCode::CommandSyntaxError,
+                        "Usage: obb mount <filename> <key> <ownerUid>", false);
+                return 0;
+            }
+            rc = vm->mountObb(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: obb unmount <source file> [force]", false);
+            return 0;
+        }
+        bool force = false;
+        if (argc > 3 && !strcmp(argv[3], "force")) {
+            force = true;
+        }
+        rc = vm->unmountObb(argv[2], force);
+    } else if (!strcmp(argv[1], "path")) {
+        dumpArgs(argc, argv, -1);
+        if (argc != 3) {
+            cli->sendMsg(ResponseCode::CommandSyntaxError, "Usage: obb path <source file>", false);
+            return 0;
+        }
+        char path[255];
+
+        if (!(rc = vm->getObbMountPath(argv[2], path, sizeof(path)))) {
+            cli->sendMsg(ResponseCode::AsecPathResult, path, false);
+            return 0;
+        }
+    } else {
+        dumpArgs(argc, argv, -1);
+        cli->sendMsg(ResponseCode::CommandSyntaxError, "Unknown obb cmd", false);
+    }
+
+    if (!rc) {
+        cli->sendMsg(ResponseCode::CommandOkay, "obb operation succeeded", false);
+    } else {
+        rc = ResponseCode::convertFromErrno();
+        cli->sendMsg(rc, "obb operation failed", true);
+    }
+
+    return 0;
+}
+
 CommandListener::XwarpCmd::XwarpCmd() :
                  VoldCommand("xwarp") {
 }
diff --git a/CommandListener.h b/CommandListener.h
index ca4fc97..75c7e81 100644
--- a/CommandListener.h
+++ b/CommandListener.h
@@ -56,6 +56,13 @@
         int runCommand(SocketClient *c, int argc, char ** argv);
     };
 
+    class ObbCmd : public VoldCommand {
+    public:
+        ObbCmd();
+        virtual ~ObbCmd() {}
+        int runCommand(SocketClient *c, int argc, char ** argv);
+    };
+
     class StorageCmd : public VoldCommand {
     public:
         StorageCmd();
diff --git a/DirectVolume.cpp b/DirectVolume.cpp
index 0d781d0..3191cc7 100644
--- a/DirectVolume.cpp
+++ b/DirectVolume.cpp
@@ -65,6 +65,14 @@
     return MKDEV(mDiskMajor, mDiskMinor);
 }
 
+dev_t DirectVolume::getShareDevice() {
+    if (mPartIdx != -1) {
+        return MKDEV(mDiskMajor, mPartIdx);
+    } else {
+        return MKDEV(mDiskMajor, mDiskMinor);
+    }
+}
+
 void DirectVolume::handleVolumeShared() {
     setState(Volume::State_Shared);
 }
diff --git a/DirectVolume.h b/DirectVolume.h
index 2a78236..4bf14ff 100644
--- a/DirectVolume.h
+++ b/DirectVolume.h
@@ -30,7 +30,6 @@
     static const int MAX_PARTITIONS = 4;
 protected:
     PathCollection *mPaths;
-    int            mPartIdx;
     int            mDiskMajor;
     int            mDiskMinor;
     int            mPartMinors[MAX_PARTITIONS];
@@ -45,6 +44,7 @@
 
     int handleBlockEvent(NetlinkEvent *evt);
     dev_t getDiskDevice();
+    dev_t getShareDevice();
     void handleVolumeShared();
     void handleVolumeUnshared();
 
diff --git a/Fat.cpp b/Fat.cpp
index 7a86aac..4754c66 100644
--- a/Fat.cpp
+++ b/Fat.cpp
@@ -93,14 +93,15 @@
 }
 
 int Fat::doMount(const char *fsPath, const char *mountPoint,
-                 bool ro, bool remount, int ownerUid, int ownerGid,
-                 int permMask, bool createLost) {
+                 bool ro, bool remount, bool executable,
+                 int ownerUid, int ownerGid, int permMask, bool createLost) {
     int rc;
     unsigned long flags;
     char mountData[255];
 
-    flags = MS_NODEV | MS_NOEXEC | MS_NOSUID | MS_DIRSYNC;
+    flags = MS_NODEV | MS_NOSUID | MS_DIRSYNC;
 
+    flags |= (executable ? 0 : MS_NOEXEC);
     flags |= (ro ? MS_RDONLY : 0);
     flags |= (remount ? MS_REMOUNT : 0);
 
diff --git a/Fat.h b/Fat.h
index f056090..e02d88c 100644
--- a/Fat.h
+++ b/Fat.h
@@ -22,8 +22,9 @@
 class Fat {
 public:
     static int check(const char *fsPath);
-    static int doMount(const char *fsPath, const char *mountPoint, bool ro,
-                       bool remount, int ownerUid, int ownerGid, int permMask,
+    static int doMount(const char *fsPath, const char *mountPoint,
+                       bool ro, bool remount, bool executable,
+                       int ownerUid, int ownerGid, int permMask,
                        bool createLost);
     static int format(const char *fsPath, unsigned int numSectors);
 };
diff --git a/Loop.cpp b/Loop.cpp
index 374cac0..98015e2 100644
--- a/Loop.cpp
+++ b/Loop.cpp
@@ -38,7 +38,7 @@
     char filename[256];
 
     for (i = 0; i < LOOP_MAX; i++) {
-        struct loop_info li;
+        struct loop_info64 li;
         int rc;
 
         sprintf(filename, "/dev/block/loop%d", i);
@@ -52,7 +52,7 @@
             return -1;
         }
 
-        rc = ioctl(fd, LOOP_GET_STATUS, &li);
+        rc = ioctl(fd, LOOP_GET_STATUS64, &li);
         close(fd);
         if (rc < 0 && errno == ENXIO) {
             continue;
@@ -64,9 +64,10 @@
             return -1;
         }
         char *tmp = NULL;
-        asprintf(&tmp, "%s %d %d:%d %lu %d:%d %d 0x%x {%s}", filename, li.lo_number,
+        asprintf(&tmp, "%s %d %lld:%lld %llu %lld:%lld %lld 0x%x {%s} {%s}", filename, li.lo_number,
                 MAJOR(li.lo_device), MINOR(li.lo_device), li.lo_inode, MAJOR(li.lo_rdevice),
-                        MINOR(li.lo_rdevice), li.lo_offset, li.lo_flags, li.lo_name);
+                        MINOR(li.lo_rdevice), li.lo_offset, li.lo_flags, li.lo_crypt_name,
+                        li.lo_file_name);
         c->sendMsg(0, tmp, false);
         free(tmp);
     }
@@ -81,7 +82,7 @@
     memset(buffer, 0, len);
 
     for (i = 0; i < LOOP_MAX; i++) {
-        struct loop_info li;
+        struct loop_info64 li;
         int rc;
 
         sprintf(filename, "/dev/block/loop%d", i);
@@ -95,7 +96,7 @@
             return -1;
         }
 
-        rc = ioctl(fd, LOOP_GET_STATUS, &li);
+        rc = ioctl(fd, LOOP_GET_STATUS64, &li);
         close(fd);
         if (rc < 0 && errno == ENXIO) {
             continue;
@@ -106,7 +107,7 @@
                  strerror(errno));
             return -1;
         }
-        if (!strncmp(li.lo_name, id, LO_NAME_SIZE)) {
+        if (!strncmp((const char*) li.lo_crypt_name, id, LO_NAME_SIZE)) {
             break;
         }
     }
@@ -184,12 +185,13 @@
         return -1;
     }
 
-    struct loop_info li;
+    struct loop_info64 li;
 
     memset(&li, 0, sizeof(li));
-    strncpy(li.lo_name, id, LO_NAME_SIZE);
+    strncpy((char*) li.lo_crypt_name, id, LO_NAME_SIZE);
+    strncpy((char*) li.lo_file_name, loopFile, LO_NAME_SIZE);
 
-    if (ioctl(fd, LOOP_SET_STATUS, &li) < 0) {
+    if (ioctl(fd, LOOP_SET_STATUS64, &li) < 0) {
         SLOGE("Error setting loopback status (%s)", strerror(errno));
         close(file_fd);
         close(fd);
diff --git a/NetlinkHandler.cpp b/NetlinkHandler.cpp
index 818db81..f47d364 100644
--- a/NetlinkHandler.cpp
+++ b/NetlinkHandler.cpp
@@ -54,6 +54,8 @@
         vm->handleBlockEvent(evt);
     } else if (!strcmp(subsys, "switch")) {
         vm->handleSwitchEvent(evt);
+    } else if (!strcmp(subsys, "usb_composite")) {
+        vm->handleUsbCompositeEvent(evt);
     } else if (!strcmp(subsys, "battery")) {
     } else if (!strcmp(subsys, "power_supply")) {
     }
diff --git a/Volume.cpp b/Volume.cpp
index 4ac086b..ecf7dcd 100644
--- a/Volume.cpp
+++ b/Volume.cpp
@@ -73,6 +73,11 @@
  */
 const char *Volume::ASECDIR           = "/mnt/asec";
 
+/*
+ * Path to where OBBs are mounted
+ */
+const char *Volume::LOOPDIR           = "/mnt/obb";
+
 static const char *stateToStr(int state) {
     if (state == Volume::State_Init)
         return "Initializing";
@@ -105,6 +110,7 @@
     mMountpoint = strdup(mount_point);
     mState = Volume::State_Init;
     mCurrentlyMountedKdev = -1;
+    mPartIdx = -1;
 }
 
 Volume::~Volume() {
@@ -139,6 +145,10 @@
     return MKDEV(0, 0);
 };
 
+dev_t Volume::getShareDevice() {
+    return getDiskDevice();
+}
+
 void Volume::handleVolumeShared() {
 }
 
@@ -201,27 +211,32 @@
         return -1;
     }
 
+    bool formatEntireDevice = (mPartIdx == -1);
     char devicePath[255];
     dev_t diskNode = getDiskDevice();
-    dev_t partNode = MKDEV(MAJOR(diskNode), 1); // XXX: Hmmm
+    dev_t partNode = MKDEV(MAJOR(diskNode), (formatEntireDevice ? 1 : mPartIdx));
 
-    sprintf(devicePath, "/dev/block/vold/%d:%d",
-            MAJOR(diskNode), MINOR(diskNode));
-
-    if (mDebug) {
-        SLOGI("Formatting volume %s (%s)", getLabel(), devicePath);
-    }
     setState(Volume::State_Formatting);
 
     int ret = -1;
-    if (initializeMbr(devicePath)) {
-        SLOGE("Failed to initialize MBR (%s)", strerror(errno));
-        goto err;
+    // Only initialize the MBR if we are formatting the entire device
+    if (formatEntireDevice) {
+        sprintf(devicePath, "/dev/block/vold/%d:%d",
+                MAJOR(diskNode), MINOR(diskNode));
+
+        if (initializeMbr(devicePath)) {
+            SLOGE("Failed to initialize MBR (%s)", strerror(errno));
+            goto err;
+        }
     }
 
     sprintf(devicePath, "/dev/block/vold/%d:%d",
             MAJOR(partNode), MINOR(partNode));
 
+    if (mDebug) {
+        SLOGI("Formatting volume %s (%s)", getLabel(), devicePath);
+    }
+
     if (Fat::format(devicePath, 0)) {
         SLOGE("Failed to format (%s)", strerror(errno));
         goto err;
@@ -320,7 +335,8 @@
          * muck with it before exposing it to non priviledged users.
          */
         errno = 0;
-        if (Fat::doMount(devicePath, "/mnt/secure/staging", false, false, 1000, 1015, 0702, true)) {
+        if (Fat::doMount(devicePath, "/mnt/secure/staging", false, false, false,
+                1000, 1015, 0702, true)) {
             SLOGE("%s failed to mount via VFAT (%s)\n", devicePath, strerror(errno));
             continue;
         }
@@ -572,7 +588,6 @@
     setState(Volume::State_NoMedia);
     return -1;
 }
-
 int Volume::initializeMbr(const char *deviceNode) {
     struct disk_info dinfo;
 
diff --git a/Volume.h b/Volume.h
index 290b66d..64cd7cb 100644
--- a/Volume.h
+++ b/Volume.h
@@ -44,11 +44,14 @@
     static const char *SEC_ASECDIR;
     static const char *ASECDIR;
 
+    static const char *LOOPDIR;
+
 protected:
     char *mLabel;
     char *mMountpoint;
     VolumeManager *mVm;
     bool mDebug;
+    int mPartIdx;
 
     /*
      * The major/minor tuple of the currently mounted filesystem.
@@ -69,6 +72,7 @@
 
     virtual int handleBlockEvent(NetlinkEvent *evt);
     virtual dev_t getDiskDevice();
+    virtual dev_t getShareDevice();
     virtual void handleVolumeShared();
     virtual void handleVolumeUnshared();
 
diff --git a/VolumeManager.cpp b/VolumeManager.cpp
index 2ff5527..32b5679 100644
--- a/VolumeManager.cpp
+++ b/VolumeManager.cpp
@@ -55,7 +55,47 @@
     mVolumes = new VolumeCollection();
     mActiveContainers = new AsecIdCollection();
     mBroadcaster = NULL;
-    mUsbMassStorageConnected = false;
+    mUsbMassStorageEnabled = false;
+    mUsbConnected = false;
+    mUmsSharingCount = 0;
+    mSavedDirtyRatio = -1;
+    // set dirty ratio to 0 when UMS is active
+    mUmsDirtyRatio = 0;
+
+    readInitialState();
+}
+
+void VolumeManager::readInitialState() {
+    FILE *fp;
+    char state[255];
+
+    /*
+     * Read the initial mass storage enabled state
+     */
+    if ((fp = fopen("/sys/devices/virtual/usb_composite/usb_mass_storage/enable", "r"))) {
+        if (fgets(state, sizeof(state), fp)) {
+            mUsbMassStorageEnabled = !strncmp(state, "1", 1);
+        } else {
+            SLOGE("Failed to read usb_mass_storage enabled state (%s)", strerror(errno));
+        }
+        fclose(fp);
+    } else {
+        SLOGD("USB mass storage support is not enabled in the kernel");
+    }
+
+    /*
+     * Read the initial USB connected state
+     */
+    if ((fp = fopen("/sys/devices/virtual/switch/usb_configuration/state", "r"))) {
+        if (fgets(state, sizeof(state), fp)) {
+            mUsbConnected = !strncmp(state, "1", 1);
+        } else {
+            SLOGE("Failed to read usb_configuration switch (%s)", strerror(errno));
+        }
+        fclose(fp);
+    } else {
+        SLOGD("usb_configuration switch is not enabled in the kernel");
+    }
 }
 
 VolumeManager::~VolumeManager() {
@@ -63,26 +103,34 @@
     delete mActiveContainers;
 }
 
-#define MD5_ASCII_LENGTH ((MD5_DIGEST_LENGTH*2)+1)
-
 char *VolumeManager::asecHash(const char *id, char *buffer, size_t len) {
+    static const char* digits = "0123456789abcdef";
+
     unsigned char sig[MD5_DIGEST_LENGTH];
 
-    if (len < MD5_ASCII_LENGTH) {
-        SLOGE("Target hash buffer size < %d bytes (%d)", MD5_ASCII_LENGTH, len);
+    if (buffer == NULL) {
+        SLOGE("Destination buffer is NULL");
+        errno = ESPIPE;
+        return NULL;
+    } else if (id == NULL) {
+        SLOGE("Source buffer is NULL");
+        errno = ESPIPE;
+        return NULL;
+    } else if (len < MD5_ASCII_LENGTH_PLUS_NULL) {
+        SLOGE("Target hash buffer size < %d bytes (%d)",
+                MD5_ASCII_LENGTH_PLUS_NULL, len);
         errno = ESPIPE;
         return NULL;
     }
 
     MD5(reinterpret_cast<const unsigned char*>(id), strlen(id), sig);
 
-    memset(buffer, 0, len);
-
+    char *p = buffer;
     for (int i = 0; i < MD5_DIGEST_LENGTH; i++) {
-        char tmp[3];
-        snprintf(tmp, 3, "%.02x", sig[i]);
-        strcat(buffer, tmp);
+        *p++ = digits[sig[i] >> 4];
+        *p++ = digits[sig[i] & 0x0F];
     }
+    *p = '\0';
 
     return buffer;
 }
@@ -108,17 +156,12 @@
     return 0;
 }
 
-void VolumeManager::notifyUmsConnected(bool connected) {
+void VolumeManager::notifyUmsAvailable(bool available) {
     char msg[255];
 
-    if (connected) {
-        mUsbMassStorageConnected = true;
-    } else {
-        mUsbMassStorageConnected = false;
-    }
     snprintf(msg, sizeof(msg), "Share method ums now %s",
-             (connected ? "available" : "unavailable"));
-
+             (available ? "available" : "unavailable"));
+    SLOGD(msg);
     getBroadcaster()->sendBroadcast(ResponseCode::ShareAvailabilityChange,
                                     msg, false);
 }
@@ -133,17 +176,37 @@
         return;
     }
 
-    if (!strcmp(name, "usb_mass_storage")) {
-
-        if (!strcmp(state, "online"))  {
-            notifyUmsConnected(true);
-        } else {
-            notifyUmsConnected(false);
+    bool oldAvailable = massStorageAvailable();
+    if (!strcmp(name, "usb_configuration")) {
+        mUsbConnected = !strcmp(state, "1");
+        SLOGD("USB %s", mUsbConnected ? "connected" : "disconnected");
+        bool newAvailable = massStorageAvailable();
+        if (newAvailable != oldAvailable) {
+            notifyUmsAvailable(newAvailable);
         }
     } else {
         SLOGW("Ignoring unknown switch '%s'", name);
     }
 }
+void VolumeManager::handleUsbCompositeEvent(NetlinkEvent *evt) {
+    const char *function = evt->findParam("FUNCTION");
+    const char *enabled = evt->findParam("ENABLED");
+
+    if (!function || !enabled) {
+        SLOGW("usb_composite event missing function/enabled info");
+        return;
+    }
+
+    if (!strcmp(function, "usb_mass_storage")) {
+        bool oldAvailable = massStorageAvailable();
+        mUsbMassStorageEnabled = !strcmp(enabled, "1");
+        SLOGD("usb_mass_storage function %s", mUsbMassStorageEnabled ? "enabled" : "disabled");
+        bool newAvailable = massStorageAvailable();
+        if (newAvailable != oldAvailable) {
+            notifyUmsAvailable(newAvailable);
+        }
+    }
+}
 
 void VolumeManager::handleBlockEvent(NetlinkEvent *evt) {
     const char *devpath = evt->findParam("DEVPATH");
@@ -194,6 +257,24 @@
     return v->formatVol();
 }
 
+int VolumeManager::getObbMountPath(const char *sourceFile, char *mountPath, int mountPathLen) {
+    char idHash[33];
+    if (!asecHash(sourceFile, idHash, sizeof(idHash))) {
+        SLOGE("Hash of '%s' failed (%s)", sourceFile, strerror(errno));
+        return -1;
+    }
+
+    memset(mountPath, 0, mountPathLen);
+    snprintf(mountPath, mountPathLen, "%s/%s", Volume::LOOPDIR, idHash);
+
+    if (access(mountPath, F_OK)) {
+        errno = ENOENT;
+        return -1;
+    }
+
+    return 0;
+}
+
 int VolumeManager::getAsecMountPath(const char *id, char *buffer, int maxlen) {
     char asecFileName[255];
     snprintf(asecFileName, sizeof(asecFileName), "%s/%s.asec", Volume::SEC_ASECDIR, id);
@@ -354,7 +435,7 @@
             }
         }
 
-        if (Fat::doMount(dmDevice, mountPoint, false, false, ownerUid,
+        if (Fat::doMount(dmDevice, mountPoint, false, false, false, ownerUid,
                          0, 0000, false)) {
             SLOGE("ASEC FAT mount failed (%s)", strerror(errno));
             if (cleanupDm) {
@@ -368,7 +449,7 @@
         SLOGI("Created raw secure container %s (no filesystem)", id);
     }
 
-    mActiveContainers->push_back(strdup(id));
+    mActiveContainers->push_back(new ContainerData(strdup(id), ASEC));
     return 0;
 }
 
@@ -392,7 +473,7 @@
 
     snprintf(mountPoint, sizeof(mountPoint), "%s/%s", Volume::ASECDIR, id);
     // XXX:
-    if (Fat::doMount(loopDevice, mountPoint, true, true, 0, 0, 0227, false)) {
+    if (Fat::doMount(loopDevice, mountPoint, true, true, true, 0, 0, 0227, false)) {
         SLOGE("ASEC finalize mount failed (%s)", strerror(errno));
         return -1;
     }
@@ -446,7 +527,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];
@@ -460,37 +542,56 @@
         return -1;
     }
 
+    return unmountLoopImage(id, idHash, asecFileName, mountPoint, force);
+}
+
+int VolumeManager::unmountObb(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);
-        errno = EINVAL;
+        SLOGE("Unmount request for %s when not mounted", id);
+        errno = ENOENT;
         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) {
@@ -507,7 +608,7 @@
         }
 
         SLOGW("Failed to rmdir %s (%s)", mountPoint, strerror(errno));
-        usleep(1000 * 1000);
+        usleep(UNMOUNT_SLEEP_BETWEEN_RETRY_MS);
     }
 
     if (!retries) {
@@ -522,12 +623,13 @@
     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;
     for (it = mActiveContainers->begin(); it != mActiveContainers->end(); ++it) {
-        if (!strcmp(*it, id)) {
+        ContainerData* cd = *it;
+        if (!strcmp(cd->id, id)) {
             free(*it);
             mActiveContainers->erase(it);
             break;
@@ -682,7 +784,7 @@
         }
     }
 
-    if (Fat::doMount(dmDevice, mountPoint, true, false, ownerUid, 0,
+    if (Fat::doMount(dmDevice, mountPoint, true, false, true, ownerUid, 0,
                      0222, false)) {
 //                     0227, false)) {
         SLOGE("ASEC mount failed (%s)", strerror(errno));
@@ -693,13 +795,117 @@
         return -1;
     }
 
-    mActiveContainers->push_back(strdup(id));
+    mActiveContainers->push_back(new ContainerData(strdup(id), ASEC));
     if (mDebug) {
         SLOGD("ASEC %s mounted", id);
     }
     return 0;
 }
 
+/**
+ * Mounts an image file <code>img</code>.
+ */
+int VolumeManager::mountObb(const char *img, const char *key, int ownerUid) {
+    char mountPoint[255];
+
+    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, true, ownerUid, 0,
+                     0227, false)) {
+        SLOGE("Image mount failed (%s)", strerror(errno));
+        if (cleanupDm) {
+            Devmapper::destroy(idHash);
+        }
+        Loop::destroyByDevice(loopDevice);
+        return -1;
+    }
+
+    mActiveContainers->push_back(new ContainerData(strdup(img), OBB));
+    if (mDebug) {
+        SLOGD("Image %s mounted", img);
+    }
+    return 0;
+}
+
 int VolumeManager::mountVolume(const char *label) {
     Volume *v = lookupVolume(label);
 
@@ -711,6 +917,51 @@
     return v->mountVol();
 }
 
+int VolumeManager::listMountedObbs(SocketClient* cli) {
+    char device[256];
+    char mount_path[256];
+    char rest[256];
+    FILE *fp;
+    char line[1024];
+
+    if (!(fp = fopen("/proc/mounts", "r"))) {
+        SLOGE("Error opening /proc/mounts (%s)", strerror(errno));
+        return -1;
+    }
+
+    // Create a string to compare against that has a trailing slash
+    int loopDirLen = sizeof(Volume::LOOPDIR);
+    char loopDir[loopDirLen + 2];
+    strcpy(loopDir, Volume::LOOPDIR);
+    loopDir[loopDirLen++] = '/';
+    loopDir[loopDirLen] = '\0';
+
+    while(fgets(line, sizeof(line), fp)) {
+        line[strlen(line)-1] = '\0';
+
+        /*
+         * Should look like:
+         * /dev/block/loop0 /mnt/obb/fc99df1323fd36424f864dcb76b76d65 ...
+         */
+        sscanf(line, "%255s %255s %255s\n", device, mount_path, rest);
+
+        if (!strncmp(mount_path, loopDir, loopDirLen)) {
+            int fd = open(device, O_RDONLY);
+            if (fd >= 0) {
+                struct loop_info64 li;
+                if (ioctl(fd, LOOP_GET_STATUS64, &li) >= 0) {
+                    cli->sendMsg(ResponseCode::AsecListResult,
+                            (const char*) li.lo_file_name, false);
+                }
+                close(fd);
+            }
+        }
+    }
+
+    fclose(fp);
+    return 0;
+}
+
 int VolumeManager::shareAvailable(const char *method, bool *avail) {
 
     if (strcmp(method, "ums")) {
@@ -718,10 +969,7 @@
         return -1;
     }
 
-    if (mUsbMassStorageConnected)
-        *avail = true;
-    else
-        *avail = false;
+    *avail = massStorageAvailable();
     return 0;
 }
 
@@ -750,9 +998,9 @@
 
     if (!strcmp(cmd, "ums")) {
         if (!strcmp(arg, "connect")) {
-            notifyUmsConnected(true);
+            notifyUmsAvailable(true);
         } else if (!strcmp(arg, "disconnect")) {
-            notifyUmsConnected(false);
+            notifyUmsAvailable(false);
         } else {
             errno = EINVAL;
             return -1;
@@ -793,7 +1041,7 @@
         return -1;
     }
 
-    dev_t d = v->getDiskDevice();
+    dev_t d = v->getShareDevice();
     if ((MAJOR(d) == 0) && (MINOR(d) == 0)) {
         // This volume does not support raw disk access
         errno = EINVAL;
@@ -820,6 +1068,21 @@
 
     close(fd);
     v->handleVolumeShared();
+    if (mUmsSharingCount++ == 0) {
+        FILE* fp;
+        mSavedDirtyRatio = -1; // in case we fail
+        if ((fp = fopen("/proc/sys/vm/dirty_ratio", "r+"))) {
+            char line[16];
+            if (fgets(line, sizeof(line), fp) && sscanf(line, "%d", &mSavedDirtyRatio)) {
+                fprintf(fp, "%d\n", mUmsDirtyRatio);
+            } else {
+                SLOGE("Failed to read dirty_ratio (%s)", strerror(errno));
+            }
+            fclose(fp);
+        } else {
+            SLOGE("Failed to open /proc/sys/vm/dirty_ratio (%s)", strerror(errno));
+        }
+    }
     return 0;
 }
 
@@ -856,6 +1119,16 @@
 
     close(fd);
     v->handleVolumeUnshared();
+    if (--mUmsSharingCount == 0 && mSavedDirtyRatio != -1) {
+        FILE* fp;
+        if ((fp = fopen("/proc/sys/vm/dirty_ratio", "r+"))) {
+            fprintf(fp, "%d\n", mSavedDirtyRatio);
+            fclose(fp);
+        } else {
+            SLOGE("Failed to open /proc/sys/vm/dirty_ratio (%s)", strerror(errno));
+        }
+        mSavedDirtyRatio = -1;
+    }
     return 0;
 }
 
@@ -931,9 +1204,20 @@
 int VolumeManager::cleanupAsec(Volume *v, bool force) {
     while(mActiveContainers->size()) {
         AsecIdCollection::iterator it = mActiveContainers->begin();
-        SLOGI("Unmounting ASEC %s (dependant on %s)", *it, v->getMountpoint());
-        if (unmountAsec(*it, force)) {
-            SLOGE("Failed to unmount ASEC %s (%s)", *it, strerror(errno));
+        ContainerData* cd = *it;
+        SLOGI("Unmounting ASEC %s (dependant on %s)", cd->id, v->getMountpoint());
+        if (cd->type == ASEC) {
+            if (unmountAsec(cd->id, force)) {
+                SLOGE("Failed to unmount ASEC %s (%s)", cd->id, strerror(errno));
+                return -1;
+            }
+        } else if (cd->type == OBB) {
+            if (unmountObb(cd->id, force)) {
+                SLOGE("Failed to unmount OBB %s (%s)", cd->id, strerror(errno));
+                return -1;
+            }
+        } else {
+            SLOGE("Unknown container type %d!", cd->type);
             return -1;
         }
     }
diff --git a/VolumeManager.h b/VolumeManager.h
index 2ec9eb3..11b5ed3 100644
--- a/VolumeManager.h
+++ b/VolumeManager.h
@@ -24,7 +24,30 @@
 
 #include "Volume.h"
 
-typedef android::List<char *> AsecIdCollection;
+/* The length of an MD5 hash when encoded into ASCII hex characters */
+#define MD5_ASCII_LENGTH_PLUS_NULL ((MD5_DIGEST_LENGTH*2)+1)
+
+typedef enum { ASEC, OBB } container_type_t;
+
+class ContainerData {
+public:
+    ContainerData(char* _id, container_type_t _type)
+            : id(_id)
+            , type(_type)
+    {}
+
+    ~ContainerData() {
+        if (id != NULL) {
+            free(id);
+            id = NULL;
+        }
+    }
+
+    char *id;
+    container_type_t type;
+};
+
+typedef android::List<ContainerData*> AsecIdCollection;
 
 class VolumeManager {
 private:
@@ -35,9 +58,15 @@
 
     VolumeCollection      *mVolumes;
     AsecIdCollection      *mActiveContainers;
-    bool                   mUsbMassStorageConnected;
+    bool                   mUsbMassStorageEnabled;
+    bool                   mUsbConnected;
     bool                   mDebug;
 
+    // for adjusting /proc/sys/vm/dirty_ratio when UMS is active
+    int                    mUmsSharingCount;
+    int                    mSavedDirtyRatio;
+    int                    mUmsDirtyRatio;
+
 public:
     virtual ~VolumeManager();
 
@@ -46,6 +75,7 @@
 
     void handleBlockEvent(NetlinkEvent *evt);
     void handleSwitchEvent(NetlinkEvent *evt);
+    void handleUsbCompositeEvent(NetlinkEvent *evt);
 
     int addVolume(Volume *v);
 
@@ -58,6 +88,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);
@@ -67,10 +99,18 @@
     int renameAsec(const char *id1, const char *id2);
     int getAsecMountPath(const char *id, char *buffer, int maxlen);
 
+    /* Loopback images */
+    int listMountedObbs(SocketClient* cli);
+    int mountObb(const char *fileName, const char *key, int ownerUid);
+    int unmountObb(const char *fileName, bool force);
+    int getObbMountPath(const char *id, char *buffer, int maxlen);
+
+    /* 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: This should be moved private once switch uevents are working
-    void notifyUmsConnected(bool connected);
     // XXX: Post froyo this should be moved and cleaned up
     int cleanupAsec(Volume *v, bool force);
 
@@ -83,7 +123,11 @@
 
 private:
     VolumeManager();
+    void readInitialState();
     Volume *lookupVolume(const char *label);
     bool isMountpointMounted(const char *mp);
+
+    inline bool massStorageAvailable() const { return mUsbMassStorageEnabled && mUsbConnected; }
+    void notifyUmsAvailable(bool available);
 };
 #endif
diff --git a/logwrapper.c b/logwrapper.c
index afc6cdb..b7d2f68 100644
--- a/logwrapper.c
+++ b/logwrapper.c
@@ -51,7 +51,7 @@
         if (a == 0 && b == sizeof(buffer) - 1) {
             // buffer is full, flush
             buffer[b] = '\0';
-            LOG(LOG_INFO, tag, &buffer[a]);
+            LOG(LOG_INFO, tag, "%s", &buffer[a]);
             b = 0;
         } else if (a != b) {
             // Keep left-overs
@@ -67,13 +67,15 @@
     // Flush remaining data
     if (a != b) {
         buffer[b] = '\0';
-        LOG(LOG_INFO, tag, &buffer[a]);
+        LOG(LOG_INFO, tag, "%s", &buffer[a]);
     }
     status = 0xAAAA;
     if (wait(&status) != -1) {  // Wait for child
         if (WIFEXITED(status)) {
-            LOG(LOG_INFO, "logwrapper", "%s terminated by exit(%d)", tag,
-                    WEXITSTATUS(status));
+            if (WEXITSTATUS(status) != 0) {
+                LOG(LOG_INFO, "logwrapper", "%s terminated by exit(%d)", tag,
+                        WEXITSTATUS(status));
+            }
             return WEXITSTATUS(status);
         } else if (WIFSIGNALED(status))
             LOG(LOG_INFO, "logwrapper", "%s terminated by signal %d", tag,
@@ -118,13 +120,14 @@
 
     if (grantpt(parent_ptty) || unlockpt(parent_ptty) ||
             ((child_devname = (char*)ptsname(parent_ptty)) == 0)) {
-	LOG(LOG_ERROR, "logwrapper", "Problem with /dev/ptmx");
         close(parent_ptty);
+	LOG(LOG_ERROR, "logwrapper", "Problem with /dev/ptmx");
 	return -1;
     }
 
     pid = fork();
     if (pid < 0) {
+        close(parent_ptty);
 	LOG(LOG_ERROR, "logwrapper", "Failed to fork");
         return -errno;
     } else if (pid == 0) {
@@ -133,6 +136,7 @@
          */
         child_ptty = open(child_devname, O_RDWR);
         if (child_ptty < 0) {
+            close(parent_ptty);
 	    LOG(LOG_ERROR, "logwrapper", "Problem with child ptty");
             return -errno;
         }
@@ -145,10 +149,8 @@
 
         if (background) {
             int fd = open("/dev/cpuctl/bg_non_interactive/tasks", O_WRONLY);
-      
-            if (fd >=0 ) {
+            if (fd >= 0) {
                 char text[64];
-
                 sprintf(text, "%d", getpid());
                 if (write(fd, text, strlen(text)) < 0) {
                     LOG(LOG_WARN, "logwrapper",
diff --git a/main.cpp b/main.cpp
index fbf1273..9c45774 100644
--- a/main.cpp
+++ b/main.cpp
@@ -77,32 +77,6 @@
     }
 
     coldboot("/sys/block");
-    /*
-     * Switch uevents are broken.
-     * For now we manually bootstrap
-     * the ums switch
-     */
-    {
-        FILE *fp;
-        char state[255];
-
-        if ((fp = fopen("/sys/devices/virtual/switch/usb_mass_storage/state",
-                         "r"))) {
-            if (fgets(state, sizeof(state), fp)) {
-                if (!strncmp(state, "online", 6)) {
-                    vm->notifyUmsConnected(true);
-                } else {
-                    vm->notifyUmsConnected(false);
-                }
-            } else {
-                SLOGE("Failed to read switch state (%s)", strerror(errno));
-            }
-
-            fclose(fp);
-        } else {
-            SLOGW("No UMS switch available");
-        }
-    }
 //    coldboot("/sys/class/switch");
 
     /*
diff --git a/tests/Android.mk b/tests/Android.mk
new file mode 100644
index 0000000..8ae4b5d
--- /dev/null
+++ b/tests/Android.mk
@@ -0,0 +1,36 @@
+# Build the unit tests.
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+test_src_files := \
+	VolumeManager_test.cpp
+
+shared_libraries := \
+	liblog \
+	libstlport \
+	libcrypto
+
+static_libraries := \
+	libvold \
+	libgtest \
+	libgtest_main
+
+c_includes := \
+	external/openssl/include \
+	bionic \
+	bionic/libstdc++/include \
+	external/gtest/include \
+	external/stlport/stlport
+
+module_tags := eng tests
+
+$(foreach file,$(test_src_files), \
+    $(eval include $(CLEAR_VARS)) \
+    $(eval LOCAL_SHARED_LIBRARIES := $(shared_libraries)) \
+    $(eval LOCAL_STATIC_LIBRARIES := $(static_libraries)) \
+    $(eval LOCAL_C_INCLUDES := $(c_includes)) \
+    $(eval LOCAL_SRC_FILES := $(file)) \
+    $(eval LOCAL_MODULE := $(notdir $(file:%.cpp=%))) \
+    $(eval LOCAL_MODULE_TAGS := $(module_tags)) \
+    $(eval include $(BUILD_EXECUTABLE)) \
+)
diff --git a/tests/VolumeManager_test.cpp b/tests/VolumeManager_test.cpp
new file mode 100644
index 0000000..c0c1fa5
--- /dev/null
+++ b/tests/VolumeManager_test.cpp
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2010 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.
+ */
+
+#include <errno.h>
+
+#define LOG_TAG "VolumeManager_test"
+#include <utils/Log.h>
+#include <openssl/md5.h>
+#include "../VolumeManager.h"
+
+#include <gtest/gtest.h>
+
+namespace android {
+
+class VolumeManagerTest : public testing::Test {
+protected:
+    virtual void SetUp() {
+    }
+
+    virtual void TearDown() {
+    }
+};
+
+TEST_F(VolumeManagerTest, AsecHashTests) {
+    char buffer[MD5_ASCII_LENGTH_PLUS_NULL];
+    char* dst = reinterpret_cast<char*>(&buffer);
+
+    const char* src1 = "";
+    const char* exp1 = "d41d8cd98f00b204e9800998ecf8427e";
+
+    EXPECT_TRUE(VolumeManager::asecHash(exp1, (char*)NULL, sizeof(buffer)) == NULL && errno == ESPIPE)
+            << "Should return NULL and set errno to ESPIPE when destination buffer is NULL";
+    EXPECT_TRUE(VolumeManager::asecHash(exp1, dst, 0) == NULL && errno == ESPIPE)
+            << "Should return NULL and set errno to ESPIPE when destination buffer length is 0";
+    EXPECT_TRUE(VolumeManager::asecHash((const char*)NULL, dst, sizeof(buffer)) == NULL && errno == ESPIPE)
+            << "Should return NULL and set errno to ESPIPE when source buffer is NULL";
+
+    EXPECT_FALSE(VolumeManager::asecHash(src1, dst, sizeof(buffer)) == NULL)
+            << "Should not return NULL on valid source, destination, and destination size";
+    EXPECT_STREQ(exp1, dst)
+            << "MD5 summed output should match";
+
+    const char* src2 = "android";
+    const char* exp2 = "c31b32364ce19ca8fcd150a417ecce58";
+    EXPECT_FALSE(VolumeManager::asecHash(src2, dst, sizeof(buffer)) == NULL)
+            << "Should not return NULL on valid source, destination, and destination size";
+    EXPECT_STREQ(exp2, dst)
+            << "MD5 summed output should match";
+}
+
+}