Support for private (adopted) volumes.

This adds support for private volumes which is just a filesystem
wrapped in a dm-crypt layer.  For now we're using the exact same
configuration as internal encryption (aes-cbc-essiv:sha256), but we
don't store any key material on the removable media.  Instead, we
store the key on internal storage, and use the GPT partition GUID
to identify which key should be used.

This means that private external storage is effectively as secure as
the internal storage of the device.  That is, if the internal storage
is encrypted, then our external storage key is also encrypted.

When partitioning disks, we now support a "private" mode which has
a PrivateVolume partition, and a currently unused 16MB metadata
partition reserved for future use.  It also supports a "mixed" mode
which creates both a PublicVolume and PrivateVolume on the same
disk.  Mixed mode is currently experimental.

For now, just add ext4 support to PrivateVolume; we'll look at f2fs
in a future change.  Add VolumeBase lifecycle for setting up crypto
mappings, and extract blkid logic into shared method.  Sprinkle some
more "static" around the cryptfs code to improve invariants.

Bug: 19993667
Change-Id: Ibd1df6250735b706959a1eb9d9f7219ea85912a0
diff --git a/Android.mk b/Android.mk
index d087216..3aa12a5 100644
--- a/Android.mk
+++ b/Android.mk
@@ -21,6 +21,7 @@
 	Disk.cpp \
 	VolumeBase.cpp \
 	PublicVolume.cpp \
+	PrivateVolume.cpp \
 	EmulatedVolume.cpp \
 	Utils.cpp \
 
diff --git a/Disk.cpp b/Disk.cpp
index 084f95e..3bf9333 100644
--- a/Disk.cpp
+++ b/Disk.cpp
@@ -16,6 +16,7 @@
 
 #include "Disk.h"
 #include "PublicVolume.h"
+#include "PrivateVolume.h"
 #include "Utils.h"
 #include "VolumeBase.h"
 #include "VolumeManager.h"
@@ -26,6 +27,7 @@
 #include <base/logging.h>
 #include <diskconfig/diskconfig.h>
 
+#include <vector>
 #include <fcntl.h>
 #include <inttypes.h>
 #include <stdio.h>
@@ -35,6 +37,7 @@
 #include <sys/mount.h>
 
 using android::base::ReadFileToString;
+using android::base::WriteStringToFile;
 using android::base::StringPrintf;
 
 namespace android {
@@ -52,6 +55,8 @@
 static const char* kGptAndroidMeta = "19A710A2-B3CA-11E4-B026-10604B889DCF";
 static const char* kGptAndroidExt = "193D1EA4-B3CA-11E4-B075-10604B889DCF";
 
+static const char* kKeyPath = "/data/misc/vold";
+
 enum class Table {
     kUnknown,
     kMbr,
@@ -105,18 +110,44 @@
     return OK;
 }
 
+static std::string BuildKeyPath(const std::string& partGuid) {
+    return StringPrintf("%s/ext_%s.key", kKeyPath, partGuid.c_str());
+}
+
 void Disk::createPublicVolume(dev_t device) {
-    auto vol = new PublicVolume(device);
+    auto vol = std::shared_ptr<VolumeBase>(new PublicVolume(device));
     vol->create();
 
-    mVolumes.push_back(std::shared_ptr<VolumeBase>(vol));
+    mVolumes.push_back(vol);
     VolumeManager::Instance()->getBroadcaster()->sendBroadcast(
             ResponseCode::DiskVolumeCreated,
             StringPrintf("%s %s", getId().c_str(), vol->getId().c_str()).c_str(), false);
 }
 
-void Disk::createPrivateVolume(dev_t device) {
-    // TODO: create and add
+void Disk::createPrivateVolume(dev_t device, const std::string& partGuid) {
+    std::string tmp;
+    std::string normalizedGuid;
+    if (HexToStr(partGuid, tmp)) {
+        LOG(WARNING) << "Invalid GUID " << partGuid;
+        return;
+    }
+    StrToHex(tmp, normalizedGuid);
+
+    std::string keyRaw;
+    if (!ReadFileToString(BuildKeyPath(normalizedGuid), &keyRaw)) {
+        PLOG(ERROR) << "Failed to load key for GUID " << normalizedGuid;
+        return;
+    }
+
+    LOG(DEBUG) << "Found key for GUID " << normalizedGuid;
+
+    auto vol = std::shared_ptr<VolumeBase>(new PrivateVolume(device, keyRaw));
+    vol->create();
+
+    mVolumes.push_back(vol);
+    VolumeManager::Instance()->getBroadcaster()->sendBroadcast(
+            ResponseCode::DiskVolumeCreated,
+            StringPrintf("%s %s", getId().c_str(), vol->getId().c_str()).c_str(), false);
 }
 
 void Disk::destroyAllVolumes() {
@@ -246,7 +277,7 @@
                 if (!strcasecmp(typeGuid, kGptBasicData)) {
                     createPublicVolume(partDevice);
                 } else if (!strcasecmp(typeGuid, kGptAndroidExt)) {
-                    createPrivateVolume(partDevice);
+                    createPrivateVolume(partDevice, partGuid);
                 }
             }
         }
@@ -262,6 +293,13 @@
     return OK;
 }
 
+status_t Disk::unmountAll() {
+    for (auto vol : mVolumes) {
+        vol->unmount();
+    }
+    return OK;
+}
+
 status_t Disk::partitionPublic() {
     // TODO: improve this code
     destroyAllVolumes();
@@ -304,13 +342,84 @@
 }
 
 status_t Disk::partitionPrivate() {
-    destroyAllVolumes();
-    return -ENOTSUP;
+    return partitionMixed(0);
 }
 
 status_t Disk::partitionMixed(int8_t ratio) {
+    int status = 0;
+
     destroyAllVolumes();
-    return -ENOTSUP;
+
+    // First nuke any existing partition table
+    std::vector<std::string> cmd;
+    cmd.push_back(kSgdiskPath);
+    cmd.push_back("--zap-all");
+    cmd.push_back(mDevPath);
+
+    if (ForkExecvp(cmd, &status, false, true)) {
+        LOG(ERROR) << "Failed to zap; status " << status;
+        return -EIO;
+    }
+
+    // We've had some success above, so generate both the private partition
+    // GUID and encryption key and persist them.
+    std::string partGuidRaw;
+    std::string keyRaw;
+    if (ReadRandomBytes(16, partGuidRaw) || ReadRandomBytes(16, keyRaw)) {
+        LOG(ERROR) << "Failed to generate GUID or key";
+        return -EIO;
+    }
+
+    std::string partGuid;
+    StrToHex(partGuidRaw, partGuid);
+
+    if (!WriteStringToFile(keyRaw, BuildKeyPath(partGuid))) {
+        LOG(ERROR) << "Failed to persist key";
+        return -EIO;
+    } else {
+        LOG(DEBUG) << "Persisted key for GUID " << partGuid;
+    }
+
+    // Now let's build the new GPT table. We heavily rely on sgdisk to
+    // force optimal alignment on the created partitions.
+    cmd.clear();
+    cmd.push_back(kSgdiskPath);
+
+    // If requested, create a public partition first. Mixed-mode partitioning
+    // like this is an experimental feature.
+    if (ratio > 0) {
+        if (ratio < 10 || ratio > 90) {
+            LOG(ERROR) << "Mixed partition ratio must be between 10-90%";
+            return -EINVAL;
+        }
+
+        uint64_t splitMb = ((mSize / 100) * ratio) / 1024 / 1024;
+        cmd.push_back(StringPrintf("--new=0:0:+%" PRId64 "M", splitMb));
+        cmd.push_back(StringPrintf("--typecode=0:%s", kGptBasicData));
+        cmd.push_back("--change-name=0:shared");
+    }
+
+    // Define a metadata partition which is designed for future use; there
+    // should only be one of these per physical device, even if there are
+    // multiple private volumes.
+    cmd.push_back("--new=0:0:+16M");
+    cmd.push_back(StringPrintf("--typecode=0:%s", kGptAndroidMeta));
+    cmd.push_back("--change-name=0:android_meta");
+
+    // Define a single private partition filling the rest of disk.
+    cmd.push_back("--new=0:0:-0");
+    cmd.push_back(StringPrintf("--typecode=0:%s", kGptAndroidExt));
+    cmd.push_back(StringPrintf("--partition-guid=0:%s", partGuid.c_str()));
+    cmd.push_back("--change-name=0:android_ext");
+
+    cmd.push_back(mDevPath);
+
+    if (ForkExecvp(cmd, &status, false, true)) {
+        LOG(ERROR) << "Failed to partition; status " << status;
+        return -EIO;
+    }
+
+    return OK;
 }
 
 int Disk::getMaxMinors() {
diff --git a/Disk.h b/Disk.h
index f3825fe..82ef1f5 100644
--- a/Disk.h
+++ b/Disk.h
@@ -68,6 +68,8 @@
     status_t readMetadata();
     status_t readPartitions();
 
+    status_t unmountAll();
+
     status_t partitionPublic();
     status_t partitionPrivate();
     status_t partitionMixed(int8_t ratio);
@@ -97,7 +99,7 @@
     bool mCreated;
 
     void createPublicVolume(dev_t device);
-    void createPrivateVolume(dev_t device);
+    void createPrivateVolume(dev_t device, const std::string& partGuid);
 
     void destroyAllVolumes();
 
diff --git a/EmulatedVolume.cpp b/EmulatedVolume.cpp
index 0a1418b..5dcab72 100644
--- a/EmulatedVolume.cpp
+++ b/EmulatedVolume.cpp
@@ -53,7 +53,7 @@
 
 status_t EmulatedVolume::doMount() {
     if (fs_prepare_dir(mFusePath.c_str(), 0700, AID_ROOT, AID_ROOT)) {
-        PLOG(ERROR) << "Failed to create mount point " << mFusePath;
+        PLOG(ERROR) << getId() << " failed to create mount point " << mFusePath;
         return -errno;
     }
 
@@ -75,7 +75,7 @@
     }
 
     if (mFusePid == -1) {
-        PLOG(ERROR) << "Failed to fork";
+        PLOG(ERROR) << getId() << " failed to fork";
         return -errno;
     }
 
@@ -93,7 +93,7 @@
     ForceUnmount(mRawPath);
 
     if (TEMP_FAILURE_RETRY(rmdir(mFusePath.c_str()))) {
-        PLOG(ERROR) << "Failed to rmdir mount point " << mFusePath;
+        PLOG(ERROR) << getId() << " failed to rmdir mount point " << mFusePath;
         return -errno;
     }
 
diff --git a/Ext4.cpp b/Ext4.cpp
index f5a964a..42d6cd3 100644
--- a/Ext4.cpp
+++ b/Ext4.cpp
@@ -23,6 +23,8 @@
 #include <dirent.h>
 #include <errno.h>
 #include <fcntl.h>
+#include <vector>
+#include <string>
 
 #include <sys/types.h>
 #include <sys/stat.h>
@@ -35,17 +37,92 @@
 
 #define LOG_TAG "Vold"
 
+#include <base/stringprintf.h>
 #include <cutils/log.h>
 #include <cutils/properties.h>
-
 #include <logwrap/logwrap.h>
 
 #include "Ext4.h"
+#include "Utils.h"
 #include "VoldUtil.h"
 
-#define MKEXT4FS_PATH "/system/bin/make_ext4fs"
 #define RESIZE2FS_PATH "/system/bin/resize2fs"
 
+using android::base::StringPrintf;
+
+static const char* kMkfsPath = "/system/bin/make_ext4fs";
+
+static const char* kFsckPath = "/system/bin/e2fsck";
+static const char* kFsckLogFile = "/dev/fscklogs/log";
+
+int Ext4::check(const char *fsPath, const char *mountPoint) {
+    // The following is shamelessly borrowed from fs_mgr.c, so it should be
+    // kept in sync with any changes over there.
+
+    char* blk_device = (char*) fsPath;
+    char* target = (char*) mountPoint;
+
+    int status;
+    int ret;
+    long tmpmnt_flags = MS_NOATIME | MS_NOEXEC | MS_NOSUID;
+    char *tmpmnt_opts = (char*) "nomblk_io_submit,errors=remount-ro";
+    char *e2fsck_argv[] = {
+        (char*) kFsckPath,
+        (char*) "-y",
+        blk_device
+    };
+
+    /*
+     * First try to mount and unmount the filesystem.  We do this because
+     * the kernel is more efficient than e2fsck in running the journal and
+     * processing orphaned inodes, and on at least one device with a
+     * performance issue in the emmc firmware, it can take e2fsck 2.5 minutes
+     * to do what the kernel does in about a second.
+     *
+     * After mounting and unmounting the filesystem, run e2fsck, and if an
+     * error is recorded in the filesystem superblock, e2fsck will do a full
+     * check.  Otherwise, it does nothing.  If the kernel cannot mount the
+     * filesytsem due to an error, e2fsck is still run to do a full check
+     * fix the filesystem.
+     */
+    ret = mount(blk_device, target, "ext4", tmpmnt_flags, tmpmnt_opts);
+    if (!ret) {
+        int i;
+        for (i = 0; i < 5; i++) {
+            // Try to umount 5 times before continuing on.
+            // Should we try rebooting if all attempts fail?
+            int result = umount(target);
+            if (result == 0) {
+                break;
+            }
+            ALOGW("%s(): umount(%s)=%d: %s\n", __func__, target, result, strerror(errno));
+            sleep(1);
+        }
+    }
+
+    /*
+     * Some system images do not have e2fsck for licensing reasons
+     * (e.g. recent SDK system images). Detect these and skip the check.
+     */
+    if (access(kFsckPath, X_OK)) {
+        ALOGD("Not running %s on %s (executable not in system image)\n",
+                kFsckPath, blk_device);
+    } else {
+        ALOGD("Running %s on %s\n", kFsckPath, blk_device);
+
+        ret = android_fork_execvp(ARRAY_SIZE(e2fsck_argv), e2fsck_argv,
+                                    &status, false, true);
+
+        if (ret < 0) {
+            /* No need to check for error in fork, we can't really handle it now */
+            ALOGW("Failed trying to run %s\n", kFsckPath);
+            return -1;
+        }
+    }
+
+    return 0;
+}
+
 int Ext4::doMount(const char *fsPath, const char *mountPoint, bool ro, bool remount,
         bool executable) {
     int rc;
@@ -112,28 +189,23 @@
 }
 
 int Ext4::format(const char *fsPath, unsigned int numSectors, const char *mountpoint) {
-    const char *args[7];
-    int rc;
     int status;
 
-    args[0] = MKEXT4FS_PATH;
-    args[1] = "-J";
-    args[2] = "-a";
-    args[3] = mountpoint;
+    std::vector<std::string> cmd;
+    cmd.push_back(kMkfsPath);
+    cmd.push_back("-J");
+
+    cmd.push_back("-a");
+    cmd.push_back(mountpoint);
+
     if (numSectors) {
-        char tmp[32];
-        snprintf(tmp, sizeof(tmp), "%u", numSectors * 512);
-        const char *size = tmp;
-        args[4] = "-l";
-        args[5] = size;
-        args[6] = fsPath;
-        rc = android_fork_execvp(ARRAY_SIZE(args), (char **)args, &status, false, true);
-    } else {
-        args[4] = fsPath;
-        rc = android_fork_execvp(5, (char **)args, &status, false, true);
+        cmd.push_back("-l");
+        cmd.push_back(StringPrintf("%u", numSectors * 512));
     }
-    rc = android_fork_execvp(ARRAY_SIZE(args), (char **)args, &status, false,
-            true);
+
+    cmd.push_back(fsPath);
+
+    int rc = android::vold::ForkExecvp(cmd, &status, false, true);
     if (rc != 0) {
         SLOGE("Filesystem (ext4) format failed due to logwrap error");
         errno = EIO;
diff --git a/Ext4.h b/Ext4.h
index c768f5a..55672af 100644
--- a/Ext4.h
+++ b/Ext4.h
@@ -21,6 +21,7 @@
 
 class Ext4 {
 public:
+    static int check(const char *fsPath, const char *mountPoint);
     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);
diff --git a/PrivateVolume.cpp b/PrivateVolume.cpp
new file mode 100644
index 0000000..42eea64
--- /dev/null
+++ b/PrivateVolume.cpp
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2015 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 "Ext4.h"
+#include "PrivateVolume.h"
+#include "Utils.h"
+#include "VolumeManager.h"
+#include "ResponseCode.h"
+#include "cryptfs.h"
+
+#include <base/stringprintf.h>
+#include <base/logging.h>
+#include <cutils/fs.h>
+#include <private/android_filesystem_config.h>
+
+#include <fcntl.h>
+#include <stdlib.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/param.h>
+
+using android::base::StringPrintf;
+
+namespace android {
+namespace vold {
+
+PrivateVolume::PrivateVolume(dev_t device, const std::string& keyRaw) :
+        VolumeBase(Type::kPrivate), mRawDevice(device), mKeyRaw(keyRaw) {
+    setId(StringPrintf("private:%u,%u", major(device), minor(device)));
+    mRawDevPath = StringPrintf("/dev/block/vold/%s", getId().c_str());
+    mPath = StringPrintf("/mnt/secure/%s", getId().c_str());
+}
+
+PrivateVolume::~PrivateVolume() {
+}
+
+status_t PrivateVolume::readMetadata() {
+    status_t res = ReadMetadata(mDmDevPath, mFsType, mFsUuid, mFsLabel);
+
+    VolumeManager::Instance()->getBroadcaster()->sendBroadcast(
+            ResponseCode::VolumeFsTypeChanged,
+            StringPrintf("%s %s", getId().c_str(), mFsType.c_str()).c_str(), false);
+    VolumeManager::Instance()->getBroadcaster()->sendBroadcast(
+            ResponseCode::VolumeFsUuidChanged,
+            StringPrintf("%s %s", getId().c_str(), mFsUuid.c_str()).c_str(), false);
+    VolumeManager::Instance()->getBroadcaster()->sendBroadcast(
+            ResponseCode::VolumeFsLabelChanged,
+            StringPrintf("%s %s", getId().c_str(), mFsLabel.c_str()).c_str(), false);
+
+    return res;
+}
+
+status_t PrivateVolume::doCreate() {
+    if (CreateDeviceNode(mRawDevPath, mRawDevice)) {
+        return -EIO;
+    }
+
+    // Recover from stale vold by tearing down any old mappings
+    cryptfs_revert_ext_volume(getId().c_str());
+
+    // TODO: figure out better SELinux labels for private volumes
+
+    unsigned char* key = (unsigned char*) mKeyRaw.data();
+    char crypto_blkdev[MAXPATHLEN];
+    int res = cryptfs_setup_ext_volume(getId().c_str(), mRawDevPath.c_str(),
+            key, mKeyRaw.size(), crypto_blkdev);
+    mDmDevPath = crypto_blkdev;
+    if (res != 0) {
+        PLOG(ERROR) << getId() << " failed to setup cryptfs";
+        return -EIO;
+    }
+
+    return OK;
+}
+
+status_t PrivateVolume::doDestroy() {
+    if (cryptfs_revert_ext_volume(getId().c_str())) {
+        LOG(ERROR) << getId() << " failed to revert cryptfs";
+    }
+    return DestroyDeviceNode(mRawDevPath);
+}
+
+status_t PrivateVolume::doMount() {
+    if (readMetadata()) {
+        LOG(ERROR) << getId() << " failed to read metadata";
+        return -EIO;
+    }
+
+    if (Ext4::check(mDmDevPath.c_str(), mPath.c_str())) {
+        PLOG(ERROR) << getId() << " failed filesystem check";
+        return -EIO;
+    }
+
+    setPath(mPath);
+
+    if (fs_prepare_dir(mPath.c_str(), 0700, AID_ROOT, AID_ROOT)) {
+        PLOG(ERROR) << getId() << " failed to create mount point " << mPath;
+        return -errno;
+    }
+
+    if (Ext4::doMount(mDmDevPath.c_str(), mPath.c_str(), false, false, true)) {
+        PLOG(ERROR) << getId() << " failed to mount";
+        return -EIO;
+    }
+
+    return OK;
+}
+
+status_t PrivateVolume::doUnmount() {
+    ForceUnmount(mPath);
+
+    if (TEMP_FAILURE_RETRY(rmdir(mPath.c_str()))) {
+        PLOG(ERROR) << getId() << " failed to rmdir mount point " << mPath;
+    }
+
+    return OK;
+}
+
+status_t PrivateVolume::doFormat() {
+    // TODO: change mountpoint once we have selinux labels
+    if (Ext4::format(mDmDevPath.c_str(), 0, "/data")) {
+        PLOG(ERROR) << getId() << " failed to format";
+        return -EIO;
+    }
+
+    return OK;
+}
+
+}  // namespace vold
+}  // namespace android
diff --git a/PrivateVolume.h b/PrivateVolume.h
new file mode 100644
index 0000000..bd464e6
--- /dev/null
+++ b/PrivateVolume.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+#ifndef ANDROID_VOLD_PRIVATE_VOLUME_H
+#define ANDROID_VOLD_PRIVATE_VOLUME_H
+
+#include "VolumeBase.h"
+
+#include <cutils/multiuser.h>
+
+namespace android {
+namespace vold {
+
+/*
+ * Private storage provided by an encrypted partition.
+ *
+ * Given a raw block device, it knows how to wrap it in dm-crypt and
+ * format as ext4/f2fs.  EmulatedVolume can be stacked above it.
+ *
+ * This volume is designed to behave much like the internal /data
+ * partition, both in layout and function.  For example, apps and
+ * private app data can be safely stored on this volume because the
+ * keys are tightly tied to this device.
+ */
+class PrivateVolume : public VolumeBase {
+public:
+    PrivateVolume(dev_t device, const std::string& keyRaw);
+    virtual ~PrivateVolume();
+
+protected:
+    status_t doCreate() override;
+    status_t doDestroy() override;
+    status_t doMount() override;
+    status_t doUnmount() override;
+    status_t doFormat() override;
+
+    status_t readMetadata();
+
+private:
+    /* Kernel device of raw, encrypted partition */
+    dev_t mRawDevice;
+    /* Path to raw, encrypted block device */
+    std::string mRawDevPath;
+    /* Path to decrypted block device */
+    std::string mDmDevPath;
+    /* Path where decrypted device is mounted */
+    std::string mPath;
+
+    /* Encryption key as raw bytes */
+    std::string mKeyRaw;
+
+    /* Filesystem type */
+    std::string mFsType;
+    /* Filesystem UUID */
+    std::string mFsUuid;
+    /* User-visible filesystem label */
+    std::string mFsLabel;
+
+    DISALLOW_COPY_AND_ASSIGN(PrivateVolume);
+};
+
+}  // namespace vold
+}  // namespace android
+
+#endif
diff --git a/PublicVolume.cpp b/PublicVolume.cpp
index e4ed9c1..f3bd905 100644
--- a/PublicVolume.cpp
+++ b/PublicVolume.cpp
@@ -37,7 +37,6 @@
 namespace android {
 namespace vold {
 
-static const char* kBlkidPath = "/system/bin/blkid";
 static const char* kFusePath = "/system/bin/sdcard";
 
 static const char* kAsecPath = "/mnt/secure/asec";
@@ -46,51 +45,13 @@
         VolumeBase(Type::kPublic), mDevice(device), mFusePid(0) {
     setId(StringPrintf("public:%u,%u", major(device), minor(device)));
     mDevPath = StringPrintf("/dev/block/vold/%s", getId().c_str());
-    CreateDeviceNode(mDevPath, mDevice);
 }
 
 PublicVolume::~PublicVolume() {
-    DestroyDeviceNode(mDevPath);
 }
 
 status_t PublicVolume::readMetadata() {
-    mFsType.clear();
-    mFsUuid.clear();
-    mFsLabel.clear();
-
-    std::string path(StringPrintf("%s -c /dev/null %s", kBlkidPath, mDevPath.c_str()));
-    FILE* fp = popen(path.c_str(), "r");
-    if (!fp) {
-        PLOG(ERROR) << "Failed to run " << path;
-        return -errno;
-    }
-
-    status_t res = OK;
-    char line[1024];
-    char value[128];
-    if (fgets(line, sizeof(line), fp) != nullptr) {
-        LOG(DEBUG) << "blkid identified as " << line;
-
-        char* start = strstr(line, "TYPE=");
-        if (start != nullptr && sscanf(start + 5, "\"%127[^\"]\"", value) == 1) {
-            mFsType = value;
-        }
-
-        start = strstr(line, "UUID=");
-        if (start != nullptr && sscanf(start + 5, "\"%127[^\"]\"", value) == 1) {
-            mFsUuid = value;
-        }
-
-        start = strstr(line, "LABEL=");
-        if (start != nullptr && sscanf(start + 6, "\"%127[^\"]\"", value) == 1) {
-            mFsLabel = value;
-        }
-    } else {
-        LOG(WARNING) << "blkid failed to identify " << mDevPath;
-        res = -ENODATA;
-    }
-
-    pclose(fp);
+    status_t res = ReadMetadata(mDevPath, mFsType, mFsUuid, mFsLabel);
 
     VolumeManager::Instance()->getBroadcaster()->sendBroadcast(
             ResponseCode::VolumeFsTypeChanged,
@@ -113,13 +74,13 @@
     if (!access(legacyPath.c_str(), R_OK | X_OK)
             && access(securePath.c_str(), R_OK | X_OK)) {
         if (rename(legacyPath.c_str(), securePath.c_str())) {
-            PLOG(WARNING) << "Failed to rename legacy ASEC dir";
+            PLOG(WARNING) << getId() << " failed to rename legacy ASEC dir";
         }
     }
 
     if (TEMP_FAILURE_RETRY(mkdir(securePath.c_str(), 0700))) {
         if (errno != EEXIST) {
-            PLOG(WARNING) << "Creating ASEC stage failed";
+            PLOG(WARNING) << getId() << " creating ASEC stage failed";
             return -errno;
         }
     }
@@ -129,15 +90,23 @@
     return OK;
 }
 
+status_t PublicVolume::doCreate() {
+    return CreateDeviceNode(mDevPath, mDevice);
+}
+
+status_t PublicVolume::doDestroy() {
+    return DestroyDeviceNode(mDevPath);
+}
+
 status_t PublicVolume::doMount() {
     // TODO: expand to support mounting other filesystems
+    readMetadata();
+
     if (Fat::check(mDevPath.c_str())) {
-        LOG(ERROR) << "Failed filesystem check; not mounting";
+        LOG(ERROR) << getId() << " failed filesystem check";
         return -EIO;
     }
 
-    readMetadata();
-
     // Use UUID as stable name, if available
     std::string stableName = getId();
     if (!mFsUuid.empty()) {
@@ -149,17 +118,17 @@
     setPath(mFusePath);
 
     if (fs_prepare_dir(mRawPath.c_str(), 0700, AID_ROOT, AID_ROOT)) {
-        PLOG(ERROR) << "Failed to create mount point " << mRawPath;
+        PLOG(ERROR) << getId() << " failed to create mount point " << mRawPath;
         return -errno;
     }
     if (fs_prepare_dir(mFusePath.c_str(), 0700, AID_ROOT, AID_ROOT)) {
-        PLOG(ERROR) << "Failed to create mount point " << mFusePath;
+        PLOG(ERROR) << getId() << " failed to create mount point " << mFusePath;
         return -errno;
     }
 
     if (Fat::doMount(mDevPath.c_str(), mRawPath.c_str(), false, false, false,
             AID_MEDIA_RW, AID_MEDIA_RW, 0007, true)) {
-        PLOG(ERROR) << "Failed to mount " << mDevPath;
+        PLOG(ERROR) << getId() << " failed to mount " << mDevPath;
         return -EIO;
     }
 
@@ -202,7 +171,7 @@
     }
 
     if (mFusePid == -1) {
-        PLOG(ERROR) << "Failed to fork";
+        PLOG(ERROR) << getId() << " failed to fork";
         return -errno;
     }
 
@@ -220,10 +189,10 @@
     ForceUnmount(mRawPath);
 
     if (TEMP_FAILURE_RETRY(rmdir(mRawPath.c_str()))) {
-        PLOG(ERROR) << "Failed to rmdir mount point " << mRawPath;
+        PLOG(ERROR) << getId() << " failed to rmdir mount point " << mRawPath;
     }
     if (TEMP_FAILURE_RETRY(rmdir(mFusePath.c_str()))) {
-        PLOG(ERROR) << "Failed to rmdir mount point " << mFusePath;
+        PLOG(ERROR) << getId() << " failed to rmdir mount point " << mFusePath;
     }
 
     mFusePath.clear();
@@ -234,7 +203,7 @@
 
 status_t PublicVolume::doFormat() {
     if (Fat::format(mDevPath.c_str(), 0, true)) {
-        LOG(ERROR) << "Failed to format";
+        LOG(ERROR) << getId() << " failed to format";
         return -errno;
     }
     return OK;
diff --git a/PublicVolume.h b/PublicVolume.h
index 2d2ec6b..45313ec 100644
--- a/PublicVolume.h
+++ b/PublicVolume.h
@@ -43,6 +43,8 @@
     virtual ~PublicVolume();
 
 protected:
+    status_t doCreate() override;
+    status_t doDestroy() override;
     status_t doMount() override;
     status_t doUnmount() override;
     status_t doFormat() override;
diff --git a/Utils.cpp b/Utils.cpp
index b5d037a..7d32590 100644
--- a/Utils.cpp
+++ b/Utils.cpp
@@ -19,8 +19,10 @@
 #include "Process.h"
 
 #include <base/logging.h>
+#include <base/stringprintf.h>
 #include <cutils/fs.h>
 #include <private/android_filesystem_config.h>
+#include <logwrap/logwrap.h>
 
 #include <fcntl.h>
 #include <linux/fs.h>
@@ -34,9 +36,13 @@
 #define UMOUNT_NOFOLLOW    0x00000008  /* Don't follow symlink on umount */
 #endif
 
+using android::base::StringPrintf;
+
 namespace android {
 namespace vold {
 
+static const char* kBlkidPath = "/system/bin/blkid";
+
 status_t CreateDeviceNode(const std::string& path, dev_t dev) {
     const char* cpath = path.c_str();
     status_t res = 0;
@@ -81,14 +87,14 @@
     }
     PLOG(WARNING) << "Failed to unmount " << path << "; sending SIGTERM";
     Process::killProcessesWithOpenFiles(cpath, SIGTERM);
-    sleep(1);
+    sleep(5);
 
     if (!umount2(cpath, UMOUNT_NOFOLLOW) || errno == EINVAL || errno == ENOENT) {
         return OK;
     }
     PLOG(WARNING) << "Failed to unmount " << path << "; sending SIGKILL";
     Process::killProcessesWithOpenFiles(cpath, SIGKILL);
-    sleep(1);
+    sleep(5);
 
     if (!umount2(cpath, UMOUNT_NOFOLLOW) || errno == EINVAL || errno == ENOENT) {
         return OK;
@@ -105,5 +111,138 @@
     return OK;
 }
 
+status_t ReadMetadata(const std::string& path, std::string& fsType,
+        std::string& fsUuid, std::string& fsLabel) {
+    fsType.clear();
+    fsUuid.clear();
+    fsLabel.clear();
+
+    std::string cmd(StringPrintf("%s -c /dev/null %s", kBlkidPath, path.c_str()));
+    FILE* fp = popen(cmd.c_str(), "r");
+    if (!fp) {
+        PLOG(ERROR) << "Failed to run " << cmd;
+        return -errno;
+    }
+
+    status_t res = OK;
+    char line[1024];
+    char value[128];
+    if (fgets(line, sizeof(line), fp) != nullptr) {
+        LOG(DEBUG) << "blkid identified " << path << " as " << line;
+
+        // Extract values from blkid output, if defined
+        char* start = strstr(line, "TYPE=");
+        if (start != nullptr && sscanf(start + 5, "\"%127[^\"]\"", value) == 1) {
+            fsType = value;
+        }
+
+        start = strstr(line, "UUID=");
+        if (start != nullptr && sscanf(start + 5, "\"%127[^\"]\"", value) == 1) {
+            fsUuid = value;
+        }
+
+        start = strstr(line, "LABEL=");
+        if (start != nullptr && sscanf(start + 6, "\"%127[^\"]\"", value) == 1) {
+            fsLabel = value;
+        }
+    } else {
+        LOG(WARNING) << "blkid failed to identify " << path;
+        res = -ENODATA;
+    }
+
+    pclose(fp);
+    return res;
+}
+
+status_t ForkExecvp(const std::vector<std::string>& args, int* status,
+        bool ignore_int_quit, bool logwrap) {
+    int argc = args.size();
+    char** argv = (char**) calloc(argc, sizeof(char*));
+    for (int i = 0; i < argc; i++) {
+        argv[i] = (char*) args[i].c_str();
+        if (i == 0) {
+            LOG(VERBOSE) << args[i];
+        } else {
+            LOG(VERBOSE) << "    " << args[i];
+        }
+    }
+    int res = android_fork_execvp(argc, argv, status, ignore_int_quit, logwrap);
+    free(argv);
+    return res;
+}
+
+status_t ReadRandomBytes(size_t bytes, std::string& out) {
+    out.clear();
+
+    int fd = TEMP_FAILURE_RETRY(open("/dev/urandom", O_RDONLY | O_CLOEXEC | O_NOFOLLOW));
+    if (fd == -1) {
+        return -errno;
+    }
+
+    char buf[BUFSIZ];
+    size_t n;
+    while ((n = TEMP_FAILURE_RETRY(read(fd, &buf[0], std::min(sizeof(buf), bytes)))) > 0) {
+        out.append(buf, n);
+        bytes -= n;
+    }
+    TEMP_FAILURE_RETRY(close(fd));
+
+    if (bytes == 0) {
+        return OK;
+    } else {
+        return -EIO;
+    }
+}
+
+status_t HexToStr(const std::string& hex, std::string& str) {
+    str.clear();
+    bool even = true;
+    char cur = 0;
+    for (size_t i = 0; i < hex.size(); i++) {
+        int val = 0;
+        switch (hex[i]) {
+        case ' ': case '-': case ':': continue;
+        case 'f': case 'F': val = 15; break;
+        case 'e': case 'E': val = 14; break;
+        case 'd': case 'D': val = 13; break;
+        case 'c': case 'C': val = 12; break;
+        case 'b': case 'B': val = 11; break;
+        case 'a': case 'A': val = 10; break;
+        case '9': val = 9; break;
+        case '8': val = 8; break;
+        case '7': val = 7; break;
+        case '6': val = 6; break;
+        case '5': val = 5; break;
+        case '4': val = 4; break;
+        case '3': val = 3; break;
+        case '2': val = 2; break;
+        case '1': val = 1; break;
+        case '0': val = 0; break;
+        default: return -EINVAL;
+        }
+
+        if (even) {
+            cur = val << 4;
+        } else {
+            cur += val;
+            str.push_back(cur);
+            cur = 0;
+        }
+        even = !even;
+    }
+    return even ? OK : -EINVAL;
+}
+
+static const char* kLookup = "0123456789abcdef";
+
+status_t StrToHex(const std::string& str, std::string& hex) {
+    hex.clear();
+    for (size_t i = 0; i < str.size(); i++) {
+        hex.push_back(kLookup[str[i] >> 4]);
+        hex.push_back(kLookup[str[i] & 0x0F]);
+    }
+    return OK;
+}
+
 }  // namespace vold
 }  // namespace android
diff --git a/Utils.h b/Utils.h
index 660ef19..0e0904e 100644
--- a/Utils.h
+++ b/Utils.h
@@ -19,6 +19,7 @@
 
 #include <utils/Errors.h>
 
+#include <vector>
 #include <string>
 
 // DISALLOW_COPY_AND_ASSIGN disallows the copy and operator= functions. It goes in the private:
@@ -41,6 +42,18 @@
 /* Creates bind mount from source to target */
 status_t BindMount(const std::string& source, const std::string& target);
 
+/* Reads filesystem metadata from device at path */
+status_t ReadMetadata(const std::string& path, std::string& fsType,
+        std::string& fsUuid, std::string& fsLabel);
+
+status_t ForkExecvp(const std::vector<std::string>& args, int* status,
+        bool ignore_int_quit, bool logwrap);
+
+status_t ReadRandomBytes(size_t bytes, std::string& out);
+
+status_t HexToStr(const std::string& hex, std::string& str);
+status_t StrToHex(const std::string& str, std::string& hex);
+
 }  // namespace vold
 }  // namespace android
 
diff --git a/Volume.cpp b/Volume.cpp
index 497a7be..625e94d 100644
--- a/Volume.cpp
+++ b/Volume.cpp
@@ -395,12 +395,12 @@
            return -1;
        }
 
-       if (cryptfs_setup_volume(getLabel(), MAJOR(deviceNodes[0]), MINOR(deviceNodes[0]),
-                                new_sys_path, sizeof(new_sys_path),
-                                &new_major, &new_minor)) {
+//       if (cryptfs_setup_volume(getLabel(), MAJOR(deviceNodes[0]), MINOR(deviceNodes[0]),
+//                                new_sys_path, sizeof(new_sys_path),
+//                                &new_major, &new_minor)) {
            SLOGE("Cannot setup encryption mapping for %s\n", getMountpoint());
            return -1;
-       }
+//       }
        /* We now have the new sysfs path for the decrypted block device, and the
         * majore and minor numbers for it.  So, create the device, update the
         * path to the new sysfs path, and continue.
@@ -587,8 +587,8 @@
      * the device info to the original values.
      */
     if (revert && isDecrypted()) {
-        cryptfs_revert_volume(getLabel());
-        revertDeviceInfo();
+//        cryptfs_revert_volume(getLabel());
+//        revertDeviceInfo();
         SLOGI("Encrypted volume %s reverted successfully", getMountpoint());
     }
 
diff --git a/VolumeBase.cpp b/VolumeBase.cpp
index 8d160ae..292a4cd 100644
--- a/VolumeBase.cpp
+++ b/VolumeBase.cpp
@@ -113,10 +113,16 @@
 
 status_t VolumeBase::create() {
     CHECK(!mCreated);
+
     mCreated = true;
+    status_t res = doCreate();
     VolumeManager::Instance()->getBroadcaster()->sendBroadcast(
             ResponseCode::VolumeCreated,
             StringPrintf("%s %d", getId().c_str(), mType).c_str(), false);
+    return res;
+}
+
+status_t VolumeBase::doCreate() {
     return OK;
 }
 
@@ -127,9 +133,14 @@
         unmount();
     }
 
-    mCreated = false;
     VolumeManager::Instance()->getBroadcaster()->sendBroadcast(
             ResponseCode::VolumeDestroyed, getId().c_str(), false);
+    status_t res = doDestroy();
+    mCreated = false;
+    return res;
+}
+
+status_t VolumeBase::doDestroy() {
     return OK;
 }
 
diff --git a/VolumeBase.h b/VolumeBase.h
index e7be8fb..e3d91ff 100644
--- a/VolumeBase.h
+++ b/VolumeBase.h
@@ -87,7 +87,6 @@
 
     status_t create();
     status_t destroy();
-
     status_t mount();
     status_t unmount();
     status_t format();
@@ -95,6 +94,8 @@
 protected:
     explicit VolumeBase(Type type);
 
+    virtual status_t doCreate();
+    virtual status_t doDestroy();
     virtual status_t doMount() = 0;
     virtual status_t doUnmount() = 0;
     virtual status_t doFormat();
diff --git a/VolumeManager.cpp b/VolumeManager.cpp
index 0404ce7..5296114 100644
--- a/VolumeManager.cpp
+++ b/VolumeManager.cpp
@@ -243,30 +243,7 @@
 int VolumeManager::start() {
     // Always start from a clean slate by unmounting everything in
     // directories that we own, in case we crashed.
-    FILE* fp = setmntent("/proc/mounts", "r");
-    if (fp == NULL) {
-        SLOGE("Error opening /proc/mounts: %s", strerror(errno));
-        return -errno;
-    }
-
-    // Some volumes can be stacked on each other, so force unmount in
-    // reverse order to give us the best chance of success.
-    std::list<std::string> toUnmount;
-    mntent* mentry;
-    while ((mentry = getmntent(fp)) != NULL) {
-        if (strncmp(mentry->mnt_dir, "/mnt/", 5) == 0
-                || strncmp(mentry->mnt_dir, "/storage/", 9) == 0) {
-            toUnmount.push_front(std::string(mentry->mnt_dir));
-        }
-    }
-    endmntent(fp);
-
-    for (auto path : toUnmount) {
-        SLOGW("Tearing down stale mount %s", path.c_str());
-        android::vold::ForceUnmount(path);
-    }
-
-    // TODO: nuke all files under mnt and storage tmpfs too?
+    unmountAll();
 
     // Assume that we always have an emulated volume on internal
     // storage; the framework will decide if it should be mounted.
@@ -440,6 +417,7 @@
 }
 
 int VolumeManager::shutdown() {
+    mInternalEmulated->destroy();
     for (auto disk : mDisks) {
         disk->destroy();
     }
@@ -447,6 +425,43 @@
     return 0;
 }
 
+int VolumeManager::unmountAll() {
+    // First, try gracefully unmounting all known devices
+    if (mInternalEmulated != nullptr) {
+        mInternalEmulated->unmount();
+    }
+    for (auto disk : mDisks) {
+        disk->unmountAll();
+    }
+
+    // Worst case we might have some stale mounts lurking around, so
+    // force unmount those just to be safe.
+    FILE* fp = setmntent("/proc/mounts", "r");
+    if (fp == NULL) {
+        SLOGE("Error opening /proc/mounts: %s", strerror(errno));
+        return -errno;
+    }
+
+    // Some volumes can be stacked on each other, so force unmount in
+    // reverse order to give us the best chance of success.
+    std::list<std::string> toUnmount;
+    mntent* mentry;
+    while ((mentry = getmntent(fp)) != NULL) {
+        if (strncmp(mentry->mnt_dir, "/mnt/", 5) == 0
+                || strncmp(mentry->mnt_dir, "/storage/", 9) == 0) {
+            toUnmount.push_front(std::string(mentry->mnt_dir));
+        }
+    }
+    endmntent(fp);
+
+    for (auto path : toUnmount) {
+        SLOGW("Tearing down stale mount %s", path.c_str());
+        android::vold::ForceUnmount(path);
+    }
+
+    return 0;
+}
+
 int VolumeManager::listVolumes(SocketClient *cli, bool broadcast) {
     VolumeCollection::iterator i;
     char msg[256];
@@ -1831,35 +1846,6 @@
     return 0;
 }
 
-extern "C" int vold_disableVol(const char *label) {
-    VolumeManager *vm = VolumeManager::Instance();
-    vm->disableVolumeManager();
-    vm->unshareVolume(label, "ums");
-    return vm->unmountVolume(label, true, false);
-}
-
-extern "C" int vold_getNumDirectVolumes(void) {
-    VolumeManager *vm = VolumeManager::Instance();
-    return vm->getNumDirectVolumes();
-}
-
-int VolumeManager::getNumDirectVolumes(void) {
-    VolumeCollection::iterator i;
-    int n=0;
-
-    for (i = mVolumes->begin(); i != mVolumes->end(); ++i) {
-        if ((*i)->getShareDevice() != (dev_t)0) {
-            n++;
-        }
-    }
-    return n;
-}
-
-extern "C" int vold_getDirectVolumeList(struct volume_info *vol_list) {
-    VolumeManager *vm = VolumeManager::Instance();
-    return vm->getDirectVolumeList(vol_list);
-}
-
 int VolumeManager::getDirectVolumeList(struct volume_info *vol_list) {
     VolumeCollection::iterator i;
     int n=0;
@@ -1902,15 +1888,9 @@
     return v->unmountVol(force, revert);
 }
 
-extern "C" int vold_unmountAllAsecs(void) {
-    int rc;
-
+extern "C" int vold_unmountAll(void) {
     VolumeManager *vm = VolumeManager::Instance();
-    rc = vm->unmountAllAsecsInDir(Volume::SEC_ASECDIR_EXT);
-    if (vm->unmountAllAsecsInDir(Volume::SEC_ASECDIR_INT)) {
-        rc = -1;
-    }
-    return rc;
+    return vm->unmountAll();
 }
 
 #define ID_BUF_LEN 256
diff --git a/VolumeManager.h b/VolumeManager.h
index 5b3fbf6..f36561c 100644
--- a/VolumeManager.h
+++ b/VolumeManager.h
@@ -118,6 +118,8 @@
     int reset();
     /* Prepare for device shutdown, safely unmounting all devices */
     int shutdown();
+    /* Unmount all volumes, usually for encryption */
+    int unmountAll();
 
     int listVolumes(SocketClient *cli, bool broadcast);
     int mountVolume(const char *label);
@@ -213,10 +215,7 @@
 extern "C" {
 #endif /* __cplusplus */
 #define UNMOUNT_NOT_MOUNTED_ERR -2
-    int vold_disableVol(const char *label);
-    int vold_getNumDirectVolumes(void);
-    int vold_getDirectVolumeList(struct volume_info *v);
-    int vold_unmountAllAsecs(void);
+    int vold_unmountAll(void);
 #ifdef __cplusplus
 }
 #endif
diff --git a/cryptfs.c b/cryptfs.c
index 7d2b872..ebce6e8 100644
--- a/cryptfs.c
+++ b/cryptfs.c
@@ -945,9 +945,8 @@
 /* Convert a binary key of specified length into an ascii hex string equivalent,
  * without the leading 0x and with null termination
  */
-static void convert_key_to_hex_ascii(unsigned char *master_key, unsigned int keysize,
-                              char *master_key_ascii)
-{
+static void convert_key_to_hex_ascii(const unsigned char *master_key,
+        unsigned int keysize, char *master_key_ascii) {
   unsigned int i, a;
   unsigned char nibble;
 
@@ -965,10 +964,9 @@
 
 }
 
-static int load_crypto_mapping_table(struct crypt_mnt_ftr *crypt_ftr, unsigned char *master_key,
-                                     char *real_blk_name, const char *name, int fd,
-                                     char *extra_params)
-{
+static int load_crypto_mapping_table(struct crypt_mnt_ftr *crypt_ftr,
+        const unsigned char *master_key, const char *real_blk_name,
+        const char *name, int fd, const char *extra_params) {
   _Alignas(struct dm_ioctl) char buffer[DM_CRYPT_BUF_SIZE];
   struct dm_ioctl *io;
   struct dm_target_spec *tgt;
@@ -1057,9 +1055,9 @@
     return -1;
 }
 
-static int create_crypto_blk_dev(struct crypt_mnt_ftr *crypt_ftr, unsigned char *master_key,
-                                    char *real_blk_name, char *crypto_blk_name, const char *name)
-{
+static int create_crypto_blk_dev(struct crypt_mnt_ftr *crypt_ftr,
+        const unsigned char *master_key, const char *real_blk_name,
+        char *crypto_blk_name, const char *name) {
   char buffer[DM_CRYPT_BUF_SIZE];
   struct dm_ioctl *io;
   unsigned int minor;
@@ -1885,36 +1883,18 @@
   return rc;
 }
 
-/* Called by vold when it wants to undo the crypto mapping of a volume it
- * manages.  This is usually in response to a factory reset, when we want
- * to undo the crypto mapping so the volume is formatted in the clear.
- */
-int cryptfs_revert_volume(const char *label)
-{
-    return delete_crypto_blk_dev((char *)label);
-}
-
 /*
- * Called by vold when it's asked to mount an encrypted, nonremovable volume.
- * Setup a dm-crypt mapping, use the saved master key from
- * setting up the /data mapping, and return the new device path.
+ * Called by vold when it's asked to mount an encrypted external
+ * storage volume. The incoming partition has no crypto header/footer,
+ * as any metadata is been stored in a separate, small partition.
+ *
+ * out_crypto_blkdev must be MAXPATHLEN.
  */
-int cryptfs_setup_volume(const char *label, int major, int minor,
-                         char *crypto_sys_path, unsigned int max_path,
-                         int *new_major, int *new_minor)
-{
-    char real_blkdev[MAXPATHLEN], crypto_blkdev[MAXPATHLEN];
-    struct crypt_mnt_ftr sd_crypt_ftr;
-    struct stat statbuf;
-
-    sprintf(real_blkdev, "/dev/block/vold/%d:%d", major, minor);
-
-    get_crypt_ftr_and_key(&sd_crypt_ftr);
-
-    /* Update the fs_size field to be the size of the volume */
+int cryptfs_setup_ext_volume(const char* label, const char* real_blkdev,
+        const unsigned char* key, int keysize, char* out_crypto_blkdev) {
     int fd = open(real_blkdev, O_RDONLY);
     if (fd == -1) {
-        SLOGE("Cannot open volume %s\n", real_blkdev);
+        SLOGE("Failed to open %s: %s", real_blkdev, strerror(errno));
         return -1;
     }
 
@@ -1923,25 +1903,26 @@
     close(fd);
 
     if (nr_sec == 0) {
-        SLOGE("Cannot get size of volume %s\n", real_blkdev);
+        SLOGE("Failed to get size of %s: %s", real_blkdev, strerror(errno));
         return -1;
     }
 
-    sd_crypt_ftr.fs_size = nr_sec;
-    create_crypto_blk_dev(&sd_crypt_ftr, saved_master_key, real_blkdev, 
-                          crypto_blkdev, label);
+    struct crypt_mnt_ftr ext_crypt_ftr;
+    memset(&ext_crypt_ftr, 0, sizeof(ext_crypt_ftr));
+    ext_crypt_ftr.fs_size = nr_sec;
+    ext_crypt_ftr.keysize = keysize;
+    strcpy((char*) ext_crypt_ftr.crypto_type_name, "aes-cbc-essiv:sha256");
 
-    if (stat(crypto_blkdev, &statbuf) < 0) {
-        SLOGE("Error get stat for crypto_blkdev %s. err=%d(%s)\n",
-              crypto_blkdev, errno, strerror(errno));
-    }
-    *new_major = MAJOR(statbuf.st_rdev);
-    *new_minor = MINOR(statbuf.st_rdev);
+    return create_crypto_blk_dev(&ext_crypt_ftr, key, real_blkdev,
+            out_crypto_blkdev, label);
+}
 
-    /* Create path to sys entry for this block device */
-    snprintf(crypto_sys_path, max_path, "/devices/virtual/block/%s", strrchr(crypto_blkdev, '/')+1);
-
-    return 0;
+/*
+ * Called by vold when it's asked to unmount an encrypted external
+ * storage volume.
+ */
+int cryptfs_revert_ext_volume(const char* label) {
+    return delete_crypto_blk_dev((char*) label);
 }
 
 int cryptfs_crypto_complete(void)
@@ -2836,12 +2817,6 @@
 
 #define FRAMEWORK_BOOT_WAIT 60
 
-static inline int should_encrypt(struct volume_info *volume)
-{
-    return (volume->flags & (VOL_ENCRYPTABLE | VOL_NONREMOVABLE)) ==
-            (VOL_ENCRYPTABLE | VOL_NONREMOVABLE);
-}
-
 static int cryptfs_SHA256_fileblock(const char* filename, __le8* buf)
 {
     int fd = open(filename, O_RDONLY);
@@ -2944,10 +2919,7 @@
     char encrypted_state[PROPERTY_VALUE_MAX];
     char lockid[32] = { 0 };
     char key_loc[PROPERTY_VALUE_MAX];
-    char fuse_sdcard[PROPERTY_VALUE_MAX];
-    char *sd_mnt_point;
     int num_vols;
-    struct volume_info *vol_list = 0;
     off64_t previously_encrypted_upto = 0;
 
     if (!strcmp(howarg, "wipe")) {
@@ -3022,58 +2994,15 @@
     snprintf(lockid, sizeof(lockid), "enablecrypto%d", (int) getpid());
     acquire_wake_lock(PARTIAL_WAKE_LOCK, lockid);
 
-    /* Get the sdcard mount point */
-    sd_mnt_point = getenv("EMULATED_STORAGE_SOURCE");
-    if (!sd_mnt_point) {
-       sd_mnt_point = getenv("EXTERNAL_STORAGE");
-    }
-    if (!sd_mnt_point) {
-        sd_mnt_point = "/mnt/sdcard";
-    }
-
-    /* TODO
-     * Currently do not have test devices with multiple encryptable volumes.
-     * When we acquire some, re-add support.
-     */
-    num_vols=vold_getNumDirectVolumes();
-    vol_list = malloc(sizeof(struct volume_info) * num_vols);
-    vold_getDirectVolumeList(vol_list);
-
-    for (i=0; i<num_vols; i++) {
-        if (should_encrypt(&vol_list[i])) {
-            SLOGE("Cannot encrypt if there are multiple encryptable volumes"
-                  "%s\n", vol_list[i].label);
-            goto error_unencrypted;
-        }
-    }
-
     /* The init files are setup to stop the class main and late start when
      * vold sets trigger_shutdown_framework.
      */
     property_set("vold.decrypt", "trigger_shutdown_framework");
     SLOGD("Just asked init to shut down class main\n");
 
-    if (vold_unmountAllAsecs()) {
-        /* Just report the error.  If any are left mounted,
-         * umounting /data below will fail and handle the error.
-         */
-        SLOGE("Error unmounting internal asecs");
-    }
-
-    property_get("ro.crypto.fuse_sdcard", fuse_sdcard, "");
-    if (!strcmp(fuse_sdcard, "true")) {
-        // STOPSHIP: UNMOUNT ALL STORAGE BEFORE REACHING HERE, SINCE VOLD NOW MANAGES FUSE
-        // "ro.crypto.fuse_sdcard" is now deprecated
-
-        /* This is a device using the fuse layer to emulate the sdcard semantics
-         * on top of the userdata partition.  vold does not manage it, it is managed
-         * by the sdcard service.  The sdcard service was killed by the property trigger
-         * above, so just unmount it now.  We must do this _AFTER_ killing the framework,
-         * unlike the case for vold managed devices above.
-         */
-        if (wait_and_unmount(sd_mnt_point, false)) {
-            goto error_shutting_down;
-        }
+    /* Ask vold to unmount all devices that it manages */
+    if (vold_unmountAll()) {
+        SLOGE("Failed to unmount all vold managed devices");
     }
 
     /* Now unmount the /data partition. */
@@ -3221,8 +3150,6 @@
     /* Undo the dm-crypt mapping whether we succeed or not */
     delete_crypto_blk_dev("userdata");
 
-    free(vol_list);
-
     if (! rc) {
         /* Success */
         crypt_ftr.flags &= ~CRYPT_INCONSISTENT_STATE;
@@ -3289,7 +3216,6 @@
     return rc;
 
 error_unencrypted:
-    free(vol_list);
     property_set("vold.encrypt_progress", "error_not_encrypted");
     if (lockid[0]) {
         release_wake_lock(lockid);
@@ -3306,7 +3232,6 @@
 
     /* shouldn't get here */
     property_set("vold.encrypt_progress", "error_shutting_down");
-    free(vol_list);
     if (lockid[0]) {
         release_wake_lock(lockid);
     }
diff --git a/cryptfs.h b/cryptfs.h
index c34410a..d630479 100644
--- a/cryptfs.h
+++ b/cryptfs.h
@@ -172,6 +172,7 @@
   struct crypt_persist_entry persist_entry[0];
 };
 
+// TODO: remove this deprecated struct
 struct volume_info {
    unsigned int size;
    unsigned int flags;
@@ -232,10 +233,9 @@
   int cryptfs_enable(char *flag, int type, char *passwd, int allow_reboot);
   int cryptfs_changepw(int type, const char *newpw);
   int cryptfs_enable_default(char *flag, int allow_reboot);
-  int cryptfs_setup_volume(const char *label, int major, int minor,
-                           char *crypto_dev_path, unsigned int max_pathlen,
-                           int *new_major, int *new_minor);
-  int cryptfs_revert_volume(const char *label);
+  int cryptfs_setup_ext_volume(const char* label, const char* real_blkdev,
+          const unsigned char* key, int keysize, char* out_crypto_blkdev);
+  int cryptfs_revert_ext_volume(const char* label);
   int cryptfs_getfield(const char *fieldname, char *value, int len);
   int cryptfs_setfield(const char *fieldname, const char *value);
   int cryptfs_mount_default_encrypted(void);