Move even more vold commands over to Binder.

This moves fstrim, obb and appfuse commands over to the new Binder
interface.  This change also separates creating/destroying and
mounting/unmounting of OBB volumes, which means they finally flow
nicely into the modern VolumeInfo/VolumeBase design.

We now generate unique identifiers for all OBB volumes, instead of
using a shady MD5 hash.

Change all "loop" and "dm" devices to tag the kernel resources with
a vold-specific prefix so that we can clean them up if vold crashes;
there are new destroyAll() methods that handle this cleanup.

Move appfuse mounting/unmounting into VolumeManager so it can be
shared.  Move various model objects into a separate directory to
tidy things up.

Test: cts-tradefed run commandAndExit cts-dev -m CtsOsTestCases -t android.os.storage.cts.StorageManagerTest
Bug: 13758960
Change-Id: I7294e32b3fb6efe07cb3b77bd20166e70b66958f
diff --git a/model/Disk.cpp b/model/Disk.cpp
new file mode 100644
index 0000000..9c22400
--- /dev/null
+++ b/model/Disk.cpp
@@ -0,0 +1,589 @@
+/*
+ * 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 "Disk.h"
+#include "PublicVolume.h"
+#include "PrivateVolume.h"
+#include "Utils.h"
+#include "VolumeBase.h"
+#include "VolumeManager.h"
+#include "ResponseCode.h"
+#include "Ext4Crypt.h"
+
+#include <android-base/file.h>
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <android-base/logging.h>
+#include <diskconfig/diskconfig.h>
+
+#include <vector>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
+#include <sys/mount.h>
+
+using android::base::ReadFileToString;
+using android::base::WriteStringToFile;
+using android::base::StringPrintf;
+
+namespace android {
+namespace vold {
+
+static const char* kSgdiskPath = "/system/bin/sgdisk";
+static const char* kSgdiskToken = " \t\n";
+
+static const char* kSysfsLoopMaxMinors = "/sys/module/loop/parameters/max_part";
+static const char* kSysfsMmcMaxMinors = "/sys/module/mmcblk/parameters/perdev_minors";
+
+static const unsigned int kMajorBlockLoop = 7;
+static const unsigned int kMajorBlockScsiA = 8;
+static const unsigned int kMajorBlockScsiB = 65;
+static const unsigned int kMajorBlockScsiC = 66;
+static const unsigned int kMajorBlockScsiD = 67;
+static const unsigned int kMajorBlockScsiE = 68;
+static const unsigned int kMajorBlockScsiF = 69;
+static const unsigned int kMajorBlockScsiG = 70;
+static const unsigned int kMajorBlockScsiH = 71;
+static const unsigned int kMajorBlockScsiI = 128;
+static const unsigned int kMajorBlockScsiJ = 129;
+static const unsigned int kMajorBlockScsiK = 130;
+static const unsigned int kMajorBlockScsiL = 131;
+static const unsigned int kMajorBlockScsiM = 132;
+static const unsigned int kMajorBlockScsiN = 133;
+static const unsigned int kMajorBlockScsiO = 134;
+static const unsigned int kMajorBlockScsiP = 135;
+static const unsigned int kMajorBlockMmc = 179;
+static const unsigned int kMajorBlockExperimentalMin = 240;
+static const unsigned int kMajorBlockExperimentalMax = 254;
+
+static const char* kGptBasicData = "EBD0A0A2-B9E5-4433-87C0-68B6B72699C7";
+static const char* kGptAndroidMeta = "19A710A2-B3CA-11E4-B026-10604B889DCF";
+static const char* kGptAndroidExpand = "193D1EA4-B3CA-11E4-B075-10604B889DCF";
+
+enum class Table {
+    kUnknown,
+    kMbr,
+    kGpt,
+};
+
+static bool isVirtioBlkDevice(unsigned int major) {
+    /*
+     * The new emulator's "ranchu" virtual board no longer includes a goldfish
+     * MMC-based SD card device; instead, it emulates SD cards with virtio-blk,
+     * which has been supported by upstream kernel and QEMU for quite a while.
+     * Unfortunately, the virtio-blk block device driver does not use a fixed
+     * major number, but relies on the kernel to assign one from a specific
+     * range of block majors, which are allocated for "LOCAL/EXPERIMENAL USE"
+     * per Documentation/devices.txt. This is true even for the latest Linux
+     * kernel (4.4; see init() in drivers/block/virtio_blk.c).
+     *
+     * This makes it difficult for vold to detect a virtio-blk based SD card.
+     * The current solution checks two conditions (both must be met):
+     *
+     *  a) If the running environment is the emulator;
+     *  b) If the major number is an experimental block device major number (for
+     *     x86/x86_64 3.10 ranchu kernels, virtio-blk always gets major number
+     *     253, but it is safer to match the range than just one value).
+     *
+     * Other conditions could be used, too, e.g. the hardware name should be
+     * "ranchu", the device's sysfs path should end with "/block/vd[d-z]", etc.
+     * But just having a) and b) is enough for now.
+     */
+    return IsRunningInEmulator() && major >= kMajorBlockExperimentalMin
+            && major <= kMajorBlockExperimentalMax;
+}
+
+Disk::Disk(const std::string& eventPath, dev_t device,
+        const std::string& nickname, int flags) :
+        mDevice(device), mSize(-1), mNickname(nickname), mFlags(flags), mCreated(
+                false), mJustPartitioned(false) {
+    mId = StringPrintf("disk:%u,%u", major(device), minor(device));
+    mEventPath = eventPath;
+    mSysPath = StringPrintf("/sys/%s", eventPath.c_str());
+    mDevPath = StringPrintf("/dev/block/vold/%s", mId.c_str());
+    CreateDeviceNode(mDevPath, mDevice);
+}
+
+Disk::~Disk() {
+    CHECK(!mCreated);
+    DestroyDeviceNode(mDevPath);
+}
+
+std::shared_ptr<VolumeBase> Disk::findVolume(const std::string& id) {
+    for (auto vol : mVolumes) {
+        if (vol->getId() == id) {
+            return vol;
+        }
+        auto stackedVol = vol->findVolume(id);
+        if (stackedVol != nullptr) {
+            return stackedVol;
+        }
+    }
+    return nullptr;
+}
+
+void Disk::listVolumes(VolumeBase::Type type, std::list<std::string>& list) {
+    for (const auto& vol : mVolumes) {
+        if (vol->getType() == type) {
+            list.push_back(vol->getId());
+        }
+        // TODO: consider looking at stacked volumes
+    }
+}
+
+status_t Disk::create() {
+    CHECK(!mCreated);
+    mCreated = true;
+    notifyEvent(ResponseCode::DiskCreated, StringPrintf("%d", mFlags));
+    readMetadata();
+    readPartitions();
+    return OK;
+}
+
+status_t Disk::destroy() {
+    CHECK(mCreated);
+    destroyAllVolumes();
+    mCreated = false;
+    notifyEvent(ResponseCode::DiskDestroyed);
+    return OK;
+}
+
+void Disk::createPublicVolume(dev_t device) {
+    auto vol = std::shared_ptr<VolumeBase>(new PublicVolume(device));
+    if (mJustPartitioned) {
+        LOG(DEBUG) << "Device just partitioned; silently formatting";
+        vol->setSilent(true);
+        vol->create();
+        vol->format("auto");
+        vol->destroy();
+        vol->setSilent(false);
+    }
+
+    mVolumes.push_back(vol);
+    vol->setDiskId(getId());
+    vol->create();
+}
+
+void Disk::createPrivateVolume(dev_t device, const std::string& partGuid) {
+    std::string normalizedGuid;
+    if (NormalizeHex(partGuid, normalizedGuid)) {
+        LOG(WARNING) << "Invalid GUID " << partGuid;
+        return;
+    }
+
+    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));
+    if (mJustPartitioned) {
+        LOG(DEBUG) << "Device just partitioned; silently formatting";
+        vol->setSilent(true);
+        vol->create();
+        vol->format("auto");
+        vol->destroy();
+        vol->setSilent(false);
+    }
+
+    mVolumes.push_back(vol);
+    vol->setDiskId(getId());
+    vol->setPartGuid(partGuid);
+    vol->create();
+}
+
+void Disk::destroyAllVolumes() {
+    for (const auto& vol : mVolumes) {
+        vol->destroy();
+    }
+    mVolumes.clear();
+}
+
+status_t Disk::readMetadata() {
+    mSize = -1;
+    mLabel.clear();
+
+    int fd = open(mDevPath.c_str(), O_RDONLY | O_CLOEXEC);
+    if (fd != -1) {
+        if (ioctl(fd, BLKGETSIZE64, &mSize)) {
+            mSize = -1;
+        }
+        close(fd);
+    }
+
+    unsigned int majorId = major(mDevice);
+    switch (majorId) {
+    case kMajorBlockLoop: {
+        mLabel = "Virtual";
+        break;
+    }
+    case kMajorBlockScsiA: case kMajorBlockScsiB: case kMajorBlockScsiC: case kMajorBlockScsiD:
+    case kMajorBlockScsiE: case kMajorBlockScsiF: case kMajorBlockScsiG: case kMajorBlockScsiH:
+    case kMajorBlockScsiI: case kMajorBlockScsiJ: case kMajorBlockScsiK: case kMajorBlockScsiL:
+    case kMajorBlockScsiM: case kMajorBlockScsiN: case kMajorBlockScsiO: case kMajorBlockScsiP: {
+        std::string path(mSysPath + "/device/vendor");
+        std::string tmp;
+        if (!ReadFileToString(path, &tmp)) {
+            PLOG(WARNING) << "Failed to read vendor from " << path;
+            return -errno;
+        }
+        mLabel = tmp;
+        break;
+    }
+    case kMajorBlockMmc: {
+        std::string path(mSysPath + "/device/manfid");
+        std::string tmp;
+        if (!ReadFileToString(path, &tmp)) {
+            PLOG(WARNING) << "Failed to read manufacturer from " << path;
+            return -errno;
+        }
+        uint64_t manfid = strtoll(tmp.c_str(), nullptr, 16);
+        // Our goal here is to give the user a meaningful label, ideally
+        // matching whatever is silk-screened on the card.  To reduce
+        // user confusion, this list doesn't contain white-label manfid.
+        switch (manfid) {
+        case 0x000003: mLabel = "SanDisk"; break;
+        case 0x00001b: mLabel = "Samsung"; break;
+        case 0x000028: mLabel = "Lexar"; break;
+        case 0x000074: mLabel = "Transcend"; break;
+        }
+        break;
+    }
+    default: {
+        if (isVirtioBlkDevice(majorId)) {
+            LOG(DEBUG) << "Recognized experimental block major ID " << majorId
+                    << " as virtio-blk (emulator's virtual SD card device)";
+            mLabel = "Virtual";
+            break;
+        }
+        LOG(WARNING) << "Unsupported block major type " << majorId;
+        return -ENOTSUP;
+    }
+    }
+
+    notifyEvent(ResponseCode::DiskSizeChanged, StringPrintf("%" PRIu64, mSize));
+    notifyEvent(ResponseCode::DiskLabelChanged, mLabel);
+    notifyEvent(ResponseCode::DiskSysPathChanged, mSysPath);
+    return OK;
+}
+
+status_t Disk::readPartitions() {
+    int8_t maxMinors = getMaxMinors();
+    if (maxMinors < 0) {
+        return -ENOTSUP;
+    }
+
+    destroyAllVolumes();
+
+    // Parse partition table
+
+    std::vector<std::string> cmd;
+    cmd.push_back(kSgdiskPath);
+    cmd.push_back("--android-dump");
+    cmd.push_back(mDevPath);
+
+    std::vector<std::string> output;
+    status_t res = ForkExecvp(cmd, output);
+    if (res != OK) {
+        LOG(WARNING) << "sgdisk failed to scan " << mDevPath;
+        notifyEvent(ResponseCode::DiskScanned);
+        mJustPartitioned = false;
+        return res;
+    }
+
+    Table table = Table::kUnknown;
+    bool foundParts = false;
+    for (const auto& line : output) {
+        char* cline = (char*) line.c_str();
+        char* token = strtok(cline, kSgdiskToken);
+        if (token == nullptr) continue;
+
+        if (!strcmp(token, "DISK")) {
+            const char* type = strtok(nullptr, kSgdiskToken);
+            if (!strcmp(type, "mbr")) {
+                table = Table::kMbr;
+            } else if (!strcmp(type, "gpt")) {
+                table = Table::kGpt;
+            }
+        } else if (!strcmp(token, "PART")) {
+            foundParts = true;
+            int i = strtol(strtok(nullptr, kSgdiskToken), nullptr, 10);
+            if (i <= 0 || i > maxMinors) {
+                LOG(WARNING) << mId << " is ignoring partition " << i
+                        << " beyond max supported devices";
+                continue;
+            }
+            dev_t partDevice = makedev(major(mDevice), minor(mDevice) + i);
+
+            if (table == Table::kMbr) {
+                const char* type = strtok(nullptr, kSgdiskToken);
+
+                switch (strtol(type, nullptr, 16)) {
+                case 0x06: // FAT16
+                case 0x0b: // W95 FAT32 (LBA)
+                case 0x0c: // W95 FAT32 (LBA)
+                case 0x0e: // W95 FAT16 (LBA)
+                    createPublicVolume(partDevice);
+                    break;
+                }
+            } else if (table == Table::kGpt) {
+                const char* typeGuid = strtok(nullptr, kSgdiskToken);
+                const char* partGuid = strtok(nullptr, kSgdiskToken);
+
+                if (!strcasecmp(typeGuid, kGptBasicData)) {
+                    createPublicVolume(partDevice);
+                } else if (!strcasecmp(typeGuid, kGptAndroidExpand)) {
+                    createPrivateVolume(partDevice, partGuid);
+                }
+            }
+        }
+    }
+
+    // Ugly last ditch effort, treat entire disk as partition
+    if (table == Table::kUnknown || !foundParts) {
+        LOG(WARNING) << mId << " has unknown partition table; trying entire device";
+
+        std::string fsType;
+        std::string unused;
+        if (ReadMetadataUntrusted(mDevPath, fsType, unused, unused) == OK) {
+            createPublicVolume(mDevice);
+        } else {
+            LOG(WARNING) << mId << " failed to identify, giving up";
+        }
+    }
+
+    notifyEvent(ResponseCode::DiskScanned);
+    mJustPartitioned = false;
+    return OK;
+}
+
+status_t Disk::unmountAll() {
+    for (const auto& vol : mVolumes) {
+        vol->unmount();
+    }
+    return OK;
+}
+
+status_t Disk::partitionPublic() {
+    int res;
+
+    // TODO: improve this code
+    destroyAllVolumes();
+    mJustPartitioned = true;
+
+    // First nuke any existing partition table
+    std::vector<std::string> cmd;
+    cmd.push_back(kSgdiskPath);
+    cmd.push_back("--zap-all");
+    cmd.push_back(mDevPath);
+
+    // Zap sometimes returns an error when it actually succeeded, so
+    // just log as warning and keep rolling forward.
+    if ((res = ForkExecvp(cmd)) != 0) {
+        LOG(WARNING) << "Failed to zap; status " << res;
+    }
+
+    struct disk_info dinfo;
+    memset(&dinfo, 0, sizeof(dinfo));
+
+    if (!(dinfo.part_lst = (struct part_info *) malloc(
+            MAX_NUM_PARTS * sizeof(struct part_info)))) {
+        return -1;
+    }
+
+    memset(dinfo.part_lst, 0, MAX_NUM_PARTS * sizeof(struct part_info));
+    dinfo.device = strdup(mDevPath.c_str());
+    dinfo.scheme = PART_SCHEME_MBR;
+    dinfo.sect_size = 512;
+    dinfo.skip_lba = 2048;
+    dinfo.num_lba = 0;
+    dinfo.num_parts = 1;
+
+    struct part_info *pinfo = &dinfo.part_lst[0];
+
+    pinfo->name = strdup("android_sdcard");
+    pinfo->flags |= PART_ACTIVE_FLAG;
+    pinfo->type = PC_PART_TYPE_FAT32;
+    pinfo->len_kb = -1;
+
+    int rc = apply_disk_config(&dinfo, 0);
+    if (rc) {
+        LOG(ERROR) << "Failed to apply disk configuration: " << rc;
+        goto out;
+    }
+
+out:
+    free(pinfo->name);
+    free(dinfo.device);
+    free(dinfo.part_lst);
+
+    return rc;
+}
+
+status_t Disk::partitionPrivate() {
+    return partitionMixed(0);
+}
+
+status_t Disk::partitionMixed(int8_t ratio) {
+    int res;
+
+    if (e4crypt_is_native()
+            && !android::base::GetBoolProperty("persist.sys.adoptable_fbe", false)) {
+        LOG(ERROR) << "Private volumes not yet supported on FBE devices";
+        return -EINVAL;
+    }
+
+    destroyAllVolumes();
+    mJustPartitioned = true;
+
+    // First nuke any existing partition table
+    std::vector<std::string> cmd;
+    cmd.push_back(kSgdiskPath);
+    cmd.push_back("--zap-all");
+    cmd.push_back(mDevPath);
+
+    // Zap sometimes returns an error when it actually succeeded, so
+    // just log as warning and keep rolling forward.
+    if ((res = ForkExecvp(cmd)) != 0) {
+        LOG(WARNING) << "Failed to zap; status " << res;
+    }
+
+    // We've had some success above, so generate both the private partition
+    // GUID and encryption key and persist them.
+    std::string partGuidRaw;
+    if (GenerateRandomUuid(partGuidRaw) != OK) {
+        LOG(ERROR) << "Failed to generate GUID";
+        return -EIO;
+    }
+
+    std::string keyRaw;
+    if (ReadRandomBytes(16, keyRaw) != OK) {
+        LOG(ERROR) << "Failed to generate 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", kGptAndroidExpand));
+    cmd.push_back(StringPrintf("--partition-guid=0:%s", partGuid.c_str()));
+    cmd.push_back("--change-name=0:android_expand");
+
+    cmd.push_back(mDevPath);
+
+    if ((res = ForkExecvp(cmd)) != 0) {
+        LOG(ERROR) << "Failed to partition; status " << res;
+        return res;
+    }
+
+    return OK;
+}
+
+void Disk::notifyEvent(int event) {
+    VolumeManager::Instance()->getBroadcaster()->sendBroadcast(event,
+            getId().c_str(), false);
+}
+
+void Disk::notifyEvent(int event, const std::string& value) {
+    VolumeManager::Instance()->getBroadcaster()->sendBroadcast(event,
+            StringPrintf("%s %s", getId().c_str(), value.c_str()).c_str(), false);
+}
+
+int Disk::getMaxMinors() {
+    // Figure out maximum partition devices supported
+    unsigned int majorId = major(mDevice);
+    switch (majorId) {
+    case kMajorBlockLoop: {
+        std::string tmp;
+        if (!ReadFileToString(kSysfsLoopMaxMinors, &tmp)) {
+            LOG(ERROR) << "Failed to read max minors";
+            return -errno;
+        }
+        return atoi(tmp.c_str());
+    }
+    case kMajorBlockScsiA: case kMajorBlockScsiB: case kMajorBlockScsiC: case kMajorBlockScsiD:
+    case kMajorBlockScsiE: case kMajorBlockScsiF: case kMajorBlockScsiG: case kMajorBlockScsiH:
+    case kMajorBlockScsiI: case kMajorBlockScsiJ: case kMajorBlockScsiK: case kMajorBlockScsiL:
+    case kMajorBlockScsiM: case kMajorBlockScsiN: case kMajorBlockScsiO: case kMajorBlockScsiP: {
+        // Per Documentation/devices.txt this is static
+        return 15;
+    }
+    case kMajorBlockMmc: {
+        // Per Documentation/devices.txt this is dynamic
+        std::string tmp;
+        if (!ReadFileToString(kSysfsMmcMaxMinors, &tmp)) {
+            LOG(ERROR) << "Failed to read max minors";
+            return -errno;
+        }
+        return atoi(tmp.c_str());
+    }
+    default: {
+        if (isVirtioBlkDevice(majorId)) {
+            // drivers/block/virtio_blk.c has "#define PART_BITS 4", so max is
+            // 2^4 - 1 = 15
+            return 15;
+        }
+    }
+    }
+
+    LOG(ERROR) << "Unsupported block major type " << majorId;
+    return -ENOTSUP;
+}
+
+}  // namespace vold
+}  // namespace android
diff --git a/model/Disk.h b/model/Disk.h
new file mode 100644
index 0000000..77ec7df
--- /dev/null
+++ b/model/Disk.h
@@ -0,0 +1,124 @@
+/*
+ * 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_DISK_H
+#define ANDROID_VOLD_DISK_H
+
+#include "Utils.h"
+#include "VolumeBase.h"
+
+#include <utils/Errors.h>
+
+#include <vector>
+
+namespace android {
+namespace vold {
+
+class VolumeBase;
+
+/*
+ * Representation of detected physical media.
+ *
+ * Knows how to create volumes based on the partition tables found, and also
+ * how to repartition itself.
+ */
+class Disk {
+public:
+    Disk(const std::string& eventPath, dev_t device, const std::string& nickname, int flags);
+    virtual ~Disk();
+
+    enum Flags {
+        /* Flag that disk is adoptable */
+        kAdoptable = 1 << 0,
+        /* Flag that disk is considered primary when the user hasn't
+         * explicitly picked a primary storage location */
+        kDefaultPrimary = 1 << 1,
+        /* Flag that disk is SD card */
+        kSd = 1 << 2,
+        /* Flag that disk is USB disk */
+        kUsb = 1 << 3,
+        /* Flag that disk is EMMC internal */
+        kEmmc = 1 << 4,
+    };
+
+    const std::string& getId() { return mId; }
+    const std::string& getEventPath() { return mEventPath; }
+    const std::string& getSysPath() { return mSysPath; }
+    const std::string& getDevPath() { return mDevPath; }
+    dev_t getDevice() { return mDevice; }
+    uint64_t getSize() { return mSize; }
+    const std::string& getLabel() { return mLabel; }
+    int getFlags() { return mFlags; }
+
+    std::shared_ptr<VolumeBase> findVolume(const std::string& id);
+
+    void listVolumes(VolumeBase::Type type, std::list<std::string>& list);
+
+    status_t create();
+    status_t destroy();
+
+    status_t readMetadata();
+    status_t readPartitions();
+
+    status_t unmountAll();
+
+    status_t partitionPublic();
+    status_t partitionPrivate();
+    status_t partitionMixed(int8_t ratio);
+
+    void notifyEvent(int msg);
+    void notifyEvent(int msg, const std::string& value);
+
+private:
+    /* ID that uniquely references this disk */
+    std::string mId;
+    /* Original event path */
+    std::string mEventPath;
+    /* Device path under sysfs */
+    std::string mSysPath;
+    /* Device path under dev */
+    std::string mDevPath;
+    /* Kernel device representing disk */
+    dev_t mDevice;
+    /* Size of disk, in bytes */
+    uint64_t mSize;
+    /* User-visible label, such as manufacturer */
+    std::string mLabel;
+    /* Current partitions on disk */
+    std::vector<std::shared_ptr<VolumeBase>> mVolumes;
+    /* Nickname for this disk */
+    std::string mNickname;
+    /* Flags applicable to this disk */
+    int mFlags;
+    /* Flag indicating object is created */
+    bool mCreated;
+    /* Flag that we just partitioned and should format all volumes */
+    bool mJustPartitioned;
+
+    void createPublicVolume(dev_t device);
+    void createPrivateVolume(dev_t device, const std::string& partGuid);
+
+    void destroyAllVolumes();
+
+    int getMaxMinors();
+
+    DISALLOW_COPY_AND_ASSIGN(Disk);
+};
+
+}  // namespace vold
+}  // namespace android
+
+#endif
diff --git a/model/EmulatedVolume.cpp b/model/EmulatedVolume.cpp
new file mode 100644
index 0000000..df91904
--- /dev/null
+++ b/model/EmulatedVolume.cpp
@@ -0,0 +1,140 @@
+/*
+ * 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 "EmulatedVolume.h"
+#include "Utils.h"
+
+#include <android-base/stringprintf.h>
+#include <android-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/sysmacros.h>
+#include <sys/wait.h>
+
+using android::base::StringPrintf;
+
+namespace android {
+namespace vold {
+
+static const char* kFusePath = "/system/bin/sdcard";
+
+EmulatedVolume::EmulatedVolume(const std::string& rawPath) :
+        VolumeBase(Type::kEmulated), mFusePid(0) {
+    setId("emulated");
+    mRawPath = rawPath;
+    mLabel = "emulated";
+}
+
+EmulatedVolume::EmulatedVolume(const std::string& rawPath, dev_t device,
+        const std::string& fsUuid) : VolumeBase(Type::kEmulated), mFusePid(0) {
+    setId(StringPrintf("emulated:%u,%u", major(device), minor(device)));
+    mRawPath = rawPath;
+    mLabel = fsUuid;
+}
+
+EmulatedVolume::~EmulatedVolume() {
+}
+
+status_t EmulatedVolume::doMount() {
+    // We could have migrated storage to an adopted private volume, so always
+    // call primary storage "emulated" to avoid media rescans.
+    std::string label = mLabel;
+    if (getMountFlags() & MountFlags::kPrimary) {
+        label = "emulated";
+    }
+
+    mFuseDefault = StringPrintf("/mnt/runtime/default/%s", label.c_str());
+    mFuseRead = StringPrintf("/mnt/runtime/read/%s", label.c_str());
+    mFuseWrite = StringPrintf("/mnt/runtime/write/%s", label.c_str());
+
+    setInternalPath(mRawPath);
+    setPath(StringPrintf("/storage/%s", label.c_str()));
+
+    if (fs_prepare_dir(mFuseDefault.c_str(), 0700, AID_ROOT, AID_ROOT) ||
+            fs_prepare_dir(mFuseRead.c_str(), 0700, AID_ROOT, AID_ROOT) ||
+            fs_prepare_dir(mFuseWrite.c_str(), 0700, AID_ROOT, AID_ROOT)) {
+        PLOG(ERROR) << getId() << " failed to create mount points";
+        return -errno;
+    }
+
+    dev_t before = GetDevice(mFuseWrite);
+
+    if (!(mFusePid = fork())) {
+        if (execl(kFusePath, kFusePath,
+                "-u", "1023", // AID_MEDIA_RW
+                "-g", "1023", // AID_MEDIA_RW
+                "-m",
+                "-w",
+                mRawPath.c_str(),
+                label.c_str(),
+                NULL)) {
+            PLOG(ERROR) << "Failed to exec";
+        }
+
+        LOG(ERROR) << "FUSE exiting";
+        _exit(1);
+    }
+
+    if (mFusePid == -1) {
+        PLOG(ERROR) << getId() << " failed to fork";
+        return -errno;
+    }
+
+    while (before == GetDevice(mFuseWrite)) {
+        LOG(VERBOSE) << "Waiting for FUSE to spin up...";
+        usleep(50000); // 50ms
+    }
+    /* sdcardfs will have exited already. FUSE will still be running */
+    TEMP_FAILURE_RETRY(waitpid(mFusePid, nullptr, WNOHANG));
+
+    return OK;
+}
+
+status_t EmulatedVolume::doUnmount() {
+    // Unmount the storage before we kill the FUSE process. If we kill
+    // the FUSE process first, most file system operations will return
+    // ENOTCONN until the unmount completes. This is an exotic and unusual
+    // error code and might cause broken behaviour in applications.
+    KillProcessesUsingPath(getPath());
+    ForceUnmount(mFuseDefault);
+    ForceUnmount(mFuseRead);
+    ForceUnmount(mFuseWrite);
+
+    if (mFusePid > 0) {
+        kill(mFusePid, SIGTERM);
+        TEMP_FAILURE_RETRY(waitpid(mFusePid, nullptr, 0));
+        mFusePid = 0;
+    }
+
+    rmdir(mFuseDefault.c_str());
+    rmdir(mFuseRead.c_str());
+    rmdir(mFuseWrite.c_str());
+
+    mFuseDefault.clear();
+    mFuseRead.clear();
+    mFuseWrite.clear();
+
+    return OK;
+}
+
+}  // namespace vold
+}  // namespace android
diff --git a/model/EmulatedVolume.h b/model/EmulatedVolume.h
new file mode 100644
index 0000000..9b0c049
--- /dev/null
+++ b/model/EmulatedVolume.h
@@ -0,0 +1,65 @@
+/*
+ * 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_EMULATED_VOLUME_H
+#define ANDROID_VOLD_EMULATED_VOLUME_H
+
+#include "VolumeBase.h"
+
+#include <cutils/multiuser.h>
+
+namespace android {
+namespace vold {
+
+/*
+ * Shared storage emulated on top of private storage.
+ *
+ * Knows how to spawn a FUSE daemon to synthesize permissions.  ObbVolume
+ * can be stacked above it.
+ *
+ * This volume is always multi-user aware, but is only binds itself to
+ * users when its primary storage.  This volume should never be presented
+ * as secondary storage, since we're strongly encouraging developers to
+ * store data local to their app.
+ */
+class EmulatedVolume : public VolumeBase {
+public:
+    explicit EmulatedVolume(const std::string& rawPath);
+    EmulatedVolume(const std::string& rawPath, dev_t device, const std::string& fsUuid);
+    virtual ~EmulatedVolume();
+
+protected:
+    status_t doMount() override;
+    status_t doUnmount() override;
+
+private:
+    std::string mRawPath;
+    std::string mLabel;
+
+    std::string mFuseDefault;
+    std::string mFuseRead;
+    std::string mFuseWrite;
+
+    /* PID of FUSE wrapper */
+    pid_t mFusePid;
+
+    DISALLOW_COPY_AND_ASSIGN(EmulatedVolume);
+};
+
+}  // namespace vold
+}  // namespace android
+
+#endif
diff --git a/model/ObbVolume.cpp b/model/ObbVolume.cpp
new file mode 100644
index 0000000..709c7a3
--- /dev/null
+++ b/model/ObbVolume.cpp
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2017 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 "fs/Vfat.h"
+#include "Devmapper.h"
+#include "Loop.h"
+#include "ObbVolume.h"
+#include "Utils.h"
+#include "VoldUtil.h"
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <android-base/unique_fd.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/sysmacros.h>
+#include <sys/wait.h>
+
+using android::base::StringPrintf;
+using android::base::unique_fd;
+
+namespace android {
+namespace vold {
+
+ObbVolume::ObbVolume(int id, const std::string& sourcePath, const std::string& sourceKey,
+        gid_t ownerGid) : VolumeBase(Type::kObb) {
+    setId(StringPrintf("obb:%d", id));
+    mSourcePath = sourcePath;
+    mSourceKey = sourceKey;
+    mOwnerGid = ownerGid;
+}
+
+ObbVolume::~ObbVolume() {
+}
+
+status_t ObbVolume::doCreate() {
+    if (Loop::create(mSourcePath, mLoopPath)) {
+        PLOG(ERROR) << getId() << " failed to create loop";
+        return -1;
+    }
+
+    if (!mSourceKey.empty()) {
+        unsigned long nr_sec = 0;
+        {
+            unique_fd loop_fd(open(mLoopPath.c_str(), O_RDWR | O_CLOEXEC));
+            if (loop_fd.get() == -1) {
+                PLOG(ERROR) << getId() << " failed to open loop";
+                return -1;
+            }
+
+            get_blkdev_size(loop_fd.get(), &nr_sec);
+            if (nr_sec == 0) {
+                PLOG(ERROR) << getId() << " failed to get loop size";
+                return -1;
+            }
+        }
+
+        char tmp[PATH_MAX];
+        if (Devmapper::create(getId().c_str(), mLoopPath.c_str(), mSourceKey.c_str(), nr_sec,
+                tmp, PATH_MAX)) {
+            PLOG(ERROR) << getId() << " failed to create dm";
+            return -1;
+        }
+        mDmPath = tmp;
+        mMountPath = mDmPath;
+    } else {
+        mMountPath = mLoopPath;
+    }
+    return OK;
+}
+
+status_t ObbVolume::doDestroy() {
+    if (!mDmPath.empty() && Devmapper::destroy(getId().c_str())) {
+        PLOG(WARNING) << getId() << " failed to destroy dm";
+    }
+    if (!mLoopPath.empty() && Loop::destroyByDevice(mLoopPath.c_str())) {
+        PLOG(WARNING) << getId() << " failed to destroy loop";
+    }
+    mDmPath.clear();
+    mLoopPath.clear();
+    return OK;
+}
+
+status_t ObbVolume::doMount() {
+    auto path = StringPrintf("/mnt/obb/%s", getId().c_str());
+    setPath(path);
+
+    if (fs_prepare_dir(path.c_str(), 0700, AID_ROOT, AID_ROOT)) {
+        PLOG(ERROR) << getId() << " failed to create mount point";
+        return -1;
+    }
+    if (android::vold::vfat::Mount(mMountPath, path,
+            true, false, true, 0, mOwnerGid, 0227, false)) {
+        PLOG(ERROR) << getId() << " failed to mount";
+        return -1;
+    }
+    return OK;
+}
+
+status_t ObbVolume::doUnmount() {
+    auto path = getPath();
+
+    KillProcessesUsingPath(path);
+    ForceUnmount(path);
+    rmdir(path.c_str());
+
+    return OK;
+}
+
+}  // namespace vold
+}  // namespace android
diff --git a/model/ObbVolume.h b/model/ObbVolume.h
new file mode 100644
index 0000000..5ec0cde
--- /dev/null
+++ b/model/ObbVolume.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2017 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_OBB_VOLUME_H
+#define ANDROID_VOLD_OBB_VOLUME_H
+
+#include "VolumeBase.h"
+
+#include <cutils/multiuser.h>
+
+namespace android {
+namespace vold {
+
+/*
+ * OBB container.
+ */
+class ObbVolume : public VolumeBase {
+public:
+    ObbVolume(int id, const std::string& sourcePath, const std::string& sourceKey,
+            gid_t ownerGid);
+    virtual ~ObbVolume();
+
+protected:
+    status_t doCreate() override;
+    status_t doDestroy() override;
+    status_t doMount() override;
+    status_t doUnmount() override;
+
+private:
+    std::string mSourcePath;
+    std::string mSourceKey;
+    gid_t mOwnerGid;
+
+    std::string mLoopPath;
+    std::string mDmPath;
+    std::string mMountPath;
+
+    DISALLOW_COPY_AND_ASSIGN(ObbVolume);
+};
+
+}  // namespace vold
+}  // namespace android
+
+#endif
diff --git a/model/PrivateVolume.cpp b/model/PrivateVolume.cpp
new file mode 100644
index 0000000..e66e04d
--- /dev/null
+++ b/model/PrivateVolume.cpp
@@ -0,0 +1,209 @@
+/*
+ * 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 "fs/Ext4.h"
+#include "fs/F2fs.h"
+#include "PrivateVolume.h"
+#include "EmulatedVolume.h"
+#include "Utils.h"
+#include "VolumeManager.h"
+#include "ResponseCode.h"
+#include "cryptfs.h"
+
+#include <android-base/stringprintf.h>
+#include <android-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/sysmacros.h>
+#include <sys/wait.h>
+#include <sys/param.h>
+
+using android::base::StringPrintf;
+
+namespace android {
+namespace vold {
+
+static const unsigned int kMajorBlockMmc = 179;
+
+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());
+}
+
+PrivateVolume::~PrivateVolume() {
+}
+
+status_t PrivateVolume::readMetadata() {
+    status_t res = ReadMetadata(mDmDevPath, mFsType, mFsUuid, mFsLabel);
+    notifyEvent(ResponseCode::VolumeFsTypeChanged, mFsType);
+    notifyEvent(ResponseCode::VolumeFsUuidChanged, mFsUuid);
+    notifyEvent(ResponseCode::VolumeFsLabelChanged, mFsLabel);
+    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;
+    }
+
+    mPath = StringPrintf("/mnt/expand/%s", mFsUuid.c_str());
+    setPath(mPath);
+
+    if (PrepareDir(mPath, 0700, AID_ROOT, AID_ROOT)) {
+        PLOG(ERROR) << getId() << " failed to create mount point " << mPath;
+        return -EIO;
+    }
+
+    if (mFsType == "ext4") {
+        int res = ext4::Check(mDmDevPath, mPath);
+        if (res == 0 || res == 1) {
+            LOG(DEBUG) << getId() << " passed filesystem check";
+        } else {
+            PLOG(ERROR) << getId() << " failed filesystem check";
+            return -EIO;
+        }
+
+        if (ext4::Mount(mDmDevPath, mPath, false, false, true)) {
+            PLOG(ERROR) << getId() << " failed to mount";
+            return -EIO;
+        }
+
+    } else if (mFsType == "f2fs") {
+        int res = f2fs::Check(mDmDevPath);
+        if (res == 0) {
+            LOG(DEBUG) << getId() << " passed filesystem check";
+        } else {
+            PLOG(ERROR) << getId() << " failed filesystem check";
+            return -EIO;
+        }
+
+        if (f2fs::Mount(mDmDevPath, mPath)) {
+            PLOG(ERROR) << getId() << " failed to mount";
+            return -EIO;
+        }
+
+    } else {
+        LOG(ERROR) << getId() << " unsupported filesystem " << mFsType;
+        return -EIO;
+    }
+
+    RestoreconRecursive(mPath);
+
+    // Verify that common directories are ready to roll
+    if (PrepareDir(mPath + "/app", 0771, AID_SYSTEM, AID_SYSTEM) ||
+            PrepareDir(mPath + "/user", 0711, AID_SYSTEM, AID_SYSTEM) ||
+            PrepareDir(mPath + "/user_de", 0711, AID_SYSTEM, AID_SYSTEM) ||
+            PrepareDir(mPath + "/media", 0770, AID_MEDIA_RW, AID_MEDIA_RW) ||
+            PrepareDir(mPath + "/media/0", 0770, AID_MEDIA_RW, AID_MEDIA_RW) ||
+            PrepareDir(mPath + "/local", 0751, AID_ROOT, AID_ROOT) ||
+            PrepareDir(mPath + "/local/tmp", 0771, AID_SHELL, AID_SHELL)) {
+        PLOG(ERROR) << getId() << " failed to prepare";
+        return -EIO;
+    }
+
+    // Create a new emulated volume stacked above us, it will automatically
+    // be destroyed during unmount
+    std::string mediaPath(mPath + "/media");
+    auto vol = std::shared_ptr<VolumeBase>(
+            new EmulatedVolume(mediaPath, mRawDevice, mFsUuid));
+    addVolume(vol);
+    vol->create();
+
+    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(const std::string& fsType) {
+    std::string resolvedFsType = fsType;
+    if (fsType == "auto") {
+        // For now, assume that all MMC devices are flash-based SD cards, and
+        // give everyone else ext4 because sysfs rotational isn't reliable.
+        if ((major(mRawDevice) == kMajorBlockMmc) && f2fs::IsSupported()) {
+            resolvedFsType = "f2fs";
+        } else {
+            resolvedFsType = "ext4";
+        }
+        LOG(DEBUG) << "Resolved auto to " << resolvedFsType;
+    }
+
+    if (resolvedFsType == "ext4") {
+        // TODO: change reported mountpoint once we have better selinux support
+        if (ext4::Format(mDmDevPath, 0, "/data")) {
+            PLOG(ERROR) << getId() << " failed to format";
+            return -EIO;
+        }
+    } else if (resolvedFsType == "f2fs") {
+        if (f2fs::Format(mDmDevPath)) {
+            PLOG(ERROR) << getId() << " failed to format";
+            return -EIO;
+        }
+    } else {
+        LOG(ERROR) << getId() << " unsupported filesystem " << fsType;
+        return -EINVAL;
+    }
+
+    return OK;
+}
+
+}  // namespace vold
+}  // namespace android
diff --git a/model/PrivateVolume.h b/model/PrivateVolume.h
new file mode 100644
index 0000000..95b718d
--- /dev/null
+++ b/model/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(const std::string& fsType) 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/model/PublicVolume.cpp b/model/PublicVolume.cpp
new file mode 100644
index 0000000..f976c4a
--- /dev/null
+++ b/model/PublicVolume.cpp
@@ -0,0 +1,250 @@
+/*
+ * 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 "fs/Vfat.h"
+#include "PublicVolume.h"
+#include "Utils.h"
+#include "VolumeManager.h"
+#include "ResponseCode.h"
+
+#include <android-base/stringprintf.h>
+#include <android-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/sysmacros.h>
+#include <sys/wait.h>
+
+using android::base::StringPrintf;
+
+namespace android {
+namespace vold {
+
+static const char* kFusePath = "/system/bin/sdcard";
+
+static const char* kAsecPath = "/mnt/secure/asec";
+
+PublicVolume::PublicVolume(dev_t device) :
+        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());
+}
+
+PublicVolume::~PublicVolume() {
+}
+
+status_t PublicVolume::readMetadata() {
+    status_t res = ReadMetadataUntrusted(mDevPath, mFsType, mFsUuid, mFsLabel);
+    notifyEvent(ResponseCode::VolumeFsTypeChanged, mFsType);
+    notifyEvent(ResponseCode::VolumeFsUuidChanged, mFsUuid);
+    notifyEvent(ResponseCode::VolumeFsLabelChanged, mFsLabel);
+    return res;
+}
+
+status_t PublicVolume::initAsecStage() {
+    std::string legacyPath(mRawPath + "/android_secure");
+    std::string securePath(mRawPath + "/.android_secure");
+
+    // Recover legacy secure path
+    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) << getId() << " failed to rename legacy ASEC dir";
+        }
+    }
+
+    if (TEMP_FAILURE_RETRY(mkdir(securePath.c_str(), 0700))) {
+        if (errno != EEXIST) {
+            PLOG(WARNING) << getId() << " creating ASEC stage failed";
+            return -errno;
+        }
+    }
+
+    BindMount(securePath, kAsecPath);
+
+    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 (mFsType != "vfat") {
+        LOG(ERROR) << getId() << " unsupported filesystem " << mFsType;
+        return -EIO;
+    }
+
+    if (vfat::Check(mDevPath)) {
+        LOG(ERROR) << getId() << " failed filesystem check";
+        return -EIO;
+    }
+
+    // Use UUID as stable name, if available
+    std::string stableName = getId();
+    if (!mFsUuid.empty()) {
+        stableName = mFsUuid;
+    }
+
+    mRawPath = StringPrintf("/mnt/media_rw/%s", stableName.c_str());
+
+    mFuseDefault = StringPrintf("/mnt/runtime/default/%s", stableName.c_str());
+    mFuseRead = StringPrintf("/mnt/runtime/read/%s", stableName.c_str());
+    mFuseWrite = StringPrintf("/mnt/runtime/write/%s", stableName.c_str());
+
+    setInternalPath(mRawPath);
+    if (getMountFlags() & MountFlags::kVisible) {
+        setPath(StringPrintf("/storage/%s", stableName.c_str()));
+    } else {
+        setPath(mRawPath);
+    }
+
+    if (fs_prepare_dir(mRawPath.c_str(), 0700, AID_ROOT, AID_ROOT)) {
+        PLOG(ERROR) << getId() << " failed to create mount points";
+        return -errno;
+    }
+
+    if (vfat::Mount(mDevPath, mRawPath, false, false, false,
+            AID_MEDIA_RW, AID_MEDIA_RW, 0007, true)) {
+        PLOG(ERROR) << getId() << " failed to mount " << mDevPath;
+        return -EIO;
+    }
+
+    if (getMountFlags() & MountFlags::kPrimary) {
+        initAsecStage();
+    }
+
+    if (!(getMountFlags() & MountFlags::kVisible)) {
+        // Not visible to apps, so no need to spin up FUSE
+        return OK;
+    }
+
+    if (fs_prepare_dir(mFuseDefault.c_str(), 0700, AID_ROOT, AID_ROOT) ||
+            fs_prepare_dir(mFuseRead.c_str(), 0700, AID_ROOT, AID_ROOT) ||
+            fs_prepare_dir(mFuseWrite.c_str(), 0700, AID_ROOT, AID_ROOT)) {
+        PLOG(ERROR) << getId() << " failed to create FUSE mount points";
+        return -errno;
+    }
+
+    dev_t before = GetDevice(mFuseWrite);
+
+    if (!(mFusePid = fork())) {
+        if (getMountFlags() & MountFlags::kPrimary) {
+            if (execl(kFusePath, kFusePath,
+                    "-u", "1023", // AID_MEDIA_RW
+                    "-g", "1023", // AID_MEDIA_RW
+                    "-U", std::to_string(getMountUserId()).c_str(),
+                    "-w",
+                    mRawPath.c_str(),
+                    stableName.c_str(),
+                    NULL)) {
+                PLOG(ERROR) << "Failed to exec";
+            }
+        } else {
+            if (execl(kFusePath, kFusePath,
+                    "-u", "1023", // AID_MEDIA_RW
+                    "-g", "1023", // AID_MEDIA_RW
+                    "-U", std::to_string(getMountUserId()).c_str(),
+                    mRawPath.c_str(),
+                    stableName.c_str(),
+                    NULL)) {
+                PLOG(ERROR) << "Failed to exec";
+            }
+        }
+
+        LOG(ERROR) << "FUSE exiting";
+        _exit(1);
+    }
+
+    if (mFusePid == -1) {
+        PLOG(ERROR) << getId() << " failed to fork";
+        return -errno;
+    }
+
+    while (before == GetDevice(mFuseWrite)) {
+        LOG(VERBOSE) << "Waiting for FUSE to spin up...";
+        usleep(50000); // 50ms
+    }
+    /* sdcardfs will have exited already. FUSE will still be running */
+    TEMP_FAILURE_RETRY(waitpid(mFusePid, nullptr, WNOHANG));
+
+    return OK;
+}
+
+status_t PublicVolume::doUnmount() {
+    // Unmount the storage before we kill the FUSE process. If we kill
+    // the FUSE process first, most file system operations will return
+    // ENOTCONN until the unmount completes. This is an exotic and unusual
+    // error code and might cause broken behaviour in applications.
+    KillProcessesUsingPath(getPath());
+
+    ForceUnmount(kAsecPath);
+
+    ForceUnmount(mFuseDefault);
+    ForceUnmount(mFuseRead);
+    ForceUnmount(mFuseWrite);
+    ForceUnmount(mRawPath);
+
+    if (mFusePid > 0) {
+        kill(mFusePid, SIGTERM);
+        TEMP_FAILURE_RETRY(waitpid(mFusePid, nullptr, 0));
+        mFusePid = 0;
+    }
+
+    rmdir(mFuseDefault.c_str());
+    rmdir(mFuseRead.c_str());
+    rmdir(mFuseWrite.c_str());
+    rmdir(mRawPath.c_str());
+
+    mFuseDefault.clear();
+    mFuseRead.clear();
+    mFuseWrite.clear();
+    mRawPath.clear();
+
+    return OK;
+}
+
+status_t PublicVolume::doFormat(const std::string& fsType) {
+    if (fsType == "vfat" || fsType == "auto") {
+        if (WipeBlockDevice(mDevPath) != OK) {
+            LOG(WARNING) << getId() << " failed to wipe";
+        }
+        if (vfat::Format(mDevPath, 0)) {
+            LOG(ERROR) << getId() << " failed to format";
+            return -errno;
+        }
+    } else {
+        LOG(ERROR) << "Unsupported filesystem " << fsType;
+        return -EINVAL;
+    }
+
+    return OK;
+}
+
+}  // namespace vold
+}  // namespace android
diff --git a/model/PublicVolume.h b/model/PublicVolume.h
new file mode 100644
index 0000000..3aa7a73
--- /dev/null
+++ b/model/PublicVolume.h
@@ -0,0 +1,83 @@
+/*
+ * 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_PUBLIC_VOLUME_H
+#define ANDROID_VOLD_PUBLIC_VOLUME_H
+
+#include "VolumeBase.h"
+
+#include <cutils/multiuser.h>
+
+namespace android {
+namespace vold {
+
+/*
+ * Shared storage provided by public (vfat) partition.
+ *
+ * Knows how to mount itself and then spawn a FUSE daemon to synthesize
+ * permissions.  AsecVolume and ObbVolume can be stacked above it.
+ *
+ * This volume is not inherently multi-user aware, so it has two possible
+ * modes of operation:
+ * 1. If primary storage for the device, it only binds itself to the
+ * owner user.
+ * 2. If secondary storage, it binds itself for all users, but masks
+ * away the Android directory for secondary users.
+ */
+class PublicVolume : public VolumeBase {
+public:
+    explicit PublicVolume(dev_t device);
+    virtual ~PublicVolume();
+
+protected:
+    status_t doCreate() override;
+    status_t doDestroy() override;
+    status_t doMount() override;
+    status_t doUnmount() override;
+    status_t doFormat(const std::string& fsType) override;
+
+    status_t readMetadata();
+    status_t initAsecStage();
+
+private:
+    /* Kernel device representing partition */
+    dev_t mDevice;
+    /* Block device path */
+    std::string mDevPath;
+    /* Mount point of raw partition */
+    std::string mRawPath;
+
+    std::string mFuseDefault;
+    std::string mFuseRead;
+    std::string mFuseWrite;
+
+    /* PID of FUSE wrapper */
+    pid_t mFusePid;
+
+    /* Filesystem type */
+    std::string mFsType;
+    /* Filesystem UUID */
+    std::string mFsUuid;
+    /* User-visible filesystem label */
+    std::string mFsLabel;
+
+    DISALLOW_COPY_AND_ASSIGN(PublicVolume);
+};
+
+}  // namespace vold
+}  // namespace android
+
+#endif
diff --git a/model/VolumeBase.cpp b/model/VolumeBase.cpp
new file mode 100644
index 0000000..3f27d87
--- /dev/null
+++ b/model/VolumeBase.cpp
@@ -0,0 +1,254 @@
+/*
+ * 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 "Utils.h"
+#include "VolumeBase.h"
+#include "VolumeManager.h"
+#include "ResponseCode.h"
+
+#include <android-base/stringprintf.h>
+#include <android-base/logging.h>
+
+#include <fcntl.h>
+#include <stdlib.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+using android::base::StringPrintf;
+
+namespace android {
+namespace vold {
+
+VolumeBase::VolumeBase(Type type) :
+        mType(type), mMountFlags(0), mMountUserId(-1), mCreated(false), mState(
+                State::kUnmounted), mSilent(false) {
+}
+
+VolumeBase::~VolumeBase() {
+    CHECK(!mCreated);
+}
+
+void VolumeBase::setState(State state) {
+    mState = state;
+    notifyEvent(ResponseCode::VolumeStateChanged, StringPrintf("%d", mState));
+}
+
+status_t VolumeBase::setDiskId(const std::string& diskId) {
+    if (mCreated) {
+        LOG(WARNING) << getId() << " diskId change requires destroyed";
+        return -EBUSY;
+    }
+
+    mDiskId = diskId;
+    return OK;
+}
+
+status_t VolumeBase::setPartGuid(const std::string& partGuid) {
+    if (mCreated) {
+        LOG(WARNING) << getId() << " partGuid change requires destroyed";
+        return -EBUSY;
+    }
+
+    mPartGuid = partGuid;
+    return OK;
+}
+
+status_t VolumeBase::setMountFlags(int mountFlags) {
+    if ((mState != State::kUnmounted) && (mState != State::kUnmountable)) {
+        LOG(WARNING) << getId() << " flags change requires state unmounted or unmountable";
+        return -EBUSY;
+    }
+
+    mMountFlags = mountFlags;
+    return OK;
+}
+
+status_t VolumeBase::setMountUserId(userid_t mountUserId) {
+    if ((mState != State::kUnmounted) && (mState != State::kUnmountable)) {
+        LOG(WARNING) << getId() << " user change requires state unmounted or unmountable";
+        return -EBUSY;
+    }
+
+    mMountUserId = mountUserId;
+    return OK;
+}
+
+status_t VolumeBase::setSilent(bool silent) {
+    if (mCreated) {
+        LOG(WARNING) << getId() << " silence change requires destroyed";
+        return -EBUSY;
+    }
+
+    mSilent = silent;
+    return OK;
+}
+
+status_t VolumeBase::setId(const std::string& id) {
+    if (mCreated) {
+        LOG(WARNING) << getId() << " id change requires not created";
+        return -EBUSY;
+    }
+
+    mId = id;
+    return OK;
+}
+
+status_t VolumeBase::setPath(const std::string& path) {
+    if (mState != State::kChecking) {
+        LOG(WARNING) << getId() << " path change requires state checking";
+        return -EBUSY;
+    }
+
+    mPath = path;
+    notifyEvent(ResponseCode::VolumePathChanged, mPath);
+    return OK;
+}
+
+status_t VolumeBase::setInternalPath(const std::string& internalPath) {
+    if (mState != State::kChecking) {
+        LOG(WARNING) << getId() << " internal path change requires state checking";
+        return -EBUSY;
+    }
+
+    mInternalPath = internalPath;
+    notifyEvent(ResponseCode::VolumeInternalPathChanged, mInternalPath);
+    return OK;
+}
+
+void VolumeBase::notifyEvent(int event) {
+    if (mSilent) return;
+    VolumeManager::Instance()->getBroadcaster()->sendBroadcast(event,
+            getId().c_str(), false);
+}
+
+void VolumeBase::notifyEvent(int event, const std::string& value) {
+    if (mSilent) return;
+    VolumeManager::Instance()->getBroadcaster()->sendBroadcast(event,
+            StringPrintf("%s %s", getId().c_str(), value.c_str()).c_str(), false);
+}
+
+void VolumeBase::addVolume(const std::shared_ptr<VolumeBase>& volume) {
+    mVolumes.push_back(volume);
+}
+
+void VolumeBase::removeVolume(const std::shared_ptr<VolumeBase>& volume) {
+    mVolumes.remove(volume);
+}
+
+std::shared_ptr<VolumeBase> VolumeBase::findVolume(const std::string& id) {
+    for (auto vol : mVolumes) {
+        if (vol->getId() == id) {
+            return vol;
+        }
+    }
+    return nullptr;
+}
+
+status_t VolumeBase::create() {
+    CHECK(!mCreated);
+
+    mCreated = true;
+    status_t res = doCreate();
+    notifyEvent(ResponseCode::VolumeCreated,
+            StringPrintf("%d \"%s\" \"%s\"", mType, mDiskId.c_str(), mPartGuid.c_str()));
+    setState(State::kUnmounted);
+    return res;
+}
+
+status_t VolumeBase::doCreate() {
+    return OK;
+}
+
+status_t VolumeBase::destroy() {
+    CHECK(mCreated);
+
+    if (mState == State::kMounted) {
+        unmount();
+        setState(State::kBadRemoval);
+    } else {
+        setState(State::kRemoved);
+    }
+
+    notifyEvent(ResponseCode::VolumeDestroyed);
+    status_t res = doDestroy();
+    mCreated = false;
+    return res;
+}
+
+status_t VolumeBase::doDestroy() {
+    return OK;
+}
+
+status_t VolumeBase::mount() {
+    if ((mState != State::kUnmounted) && (mState != State::kUnmountable)) {
+        LOG(WARNING) << getId() << " mount requires state unmounted or unmountable";
+        return -EBUSY;
+    }
+
+    setState(State::kChecking);
+    status_t res = doMount();
+    if (res == OK) {
+        setState(State::kMounted);
+    } else {
+        setState(State::kUnmountable);
+    }
+
+    return res;
+}
+
+status_t VolumeBase::unmount() {
+    if (mState != State::kMounted) {
+        LOG(WARNING) << getId() << " unmount requires state mounted";
+        return -EBUSY;
+    }
+
+    setState(State::kEjecting);
+    for (const auto& vol : mVolumes) {
+        if (vol->destroy()) {
+            LOG(WARNING) << getId() << " failed to destroy " << vol->getId()
+                    << " stacked above";
+        }
+    }
+    mVolumes.clear();
+
+    status_t res = doUnmount();
+    setState(State::kUnmounted);
+    return res;
+}
+
+status_t VolumeBase::format(const std::string& fsType) {
+    if (mState == State::kMounted) {
+        unmount();
+    }
+
+    if ((mState != State::kUnmounted) && (mState != State::kUnmountable)) {
+        LOG(WARNING) << getId() << " format requires state unmounted or unmountable";
+        return -EBUSY;
+    }
+
+    setState(State::kFormatting);
+    status_t res = doFormat(fsType);
+    setState(State::kUnmounted);
+    return res;
+}
+
+status_t VolumeBase::doFormat(const std::string& fsType) {
+    return -ENOTSUP;
+}
+
+}  // namespace vold
+}  // namespace android
diff --git a/model/VolumeBase.h b/model/VolumeBase.h
new file mode 100644
index 0000000..d417019
--- /dev/null
+++ b/model/VolumeBase.h
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2008 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_VOLUME_BASE_H
+#define ANDROID_VOLD_VOLUME_BASE_H
+
+#include "Utils.h"
+
+#include <cutils/multiuser.h>
+#include <utils/Errors.h>
+
+#include <sys/types.h>
+#include <list>
+#include <string>
+
+namespace android {
+namespace vold {
+
+/*
+ * Representation of a mounted volume ready for presentation.
+ *
+ * Various subclasses handle the different mounting prerequisites, such as
+ * encryption details, etc.  Volumes can also be "stacked" above other
+ * volumes to help communicate dependencies.  For example, an ASEC volume
+ * can be stacked on a vfat volume.
+ *
+ * Mounted volumes can be asked to manage bind mounts to present themselves
+ * to specific users on the device.
+ *
+ * When an unmount is requested, the volume recursively unmounts any stacked
+ * volumes and removes any bind mounts before finally unmounting itself.
+ */
+class VolumeBase {
+public:
+    virtual ~VolumeBase();
+
+    enum class Type {
+        kPublic = 0,
+        kPrivate,
+        kEmulated,
+        kAsec,
+        kObb,
+    };
+
+    enum MountFlags {
+        /* Flag that volume is primary external storage */
+        kPrimary = 1 << 0,
+        /* Flag that volume is visible to normal apps */
+        kVisible = 1 << 1,
+    };
+
+    enum class State {
+        kUnmounted = 0,
+        kChecking,
+        kMounted,
+        kMountedReadOnly,
+        kFormatting,
+        kEjecting,
+        kUnmountable,
+        kRemoved,
+        kBadRemoval,
+    };
+
+    const std::string& getId() { return mId; }
+    const std::string& getDiskId() { return mDiskId; }
+    const std::string& getPartGuid() { return mPartGuid; }
+    Type getType() { return mType; }
+    int getMountFlags() { return mMountFlags; }
+    userid_t getMountUserId() { return mMountUserId; }
+    State getState() { return mState; }
+    const std::string& getPath() { return mPath; }
+    const std::string& getInternalPath() { return mInternalPath; }
+
+    status_t setDiskId(const std::string& diskId);
+    status_t setPartGuid(const std::string& partGuid);
+    status_t setMountFlags(int mountFlags);
+    status_t setMountUserId(userid_t mountUserId);
+    status_t setSilent(bool silent);
+
+    void addVolume(const std::shared_ptr<VolumeBase>& volume);
+    void removeVolume(const std::shared_ptr<VolumeBase>& volume);
+
+    std::shared_ptr<VolumeBase> findVolume(const std::string& id);
+
+    status_t create();
+    status_t destroy();
+    status_t mount();
+    status_t unmount();
+    status_t format(const std::string& fsType);
+
+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(const std::string& fsType);
+
+    status_t setId(const std::string& id);
+    status_t setPath(const std::string& path);
+    status_t setInternalPath(const std::string& internalPath);
+
+    void notifyEvent(int msg);
+    void notifyEvent(int msg, const std::string& value);
+
+private:
+    /* ID that uniquely references volume while alive */
+    std::string mId;
+    /* ID that uniquely references parent disk while alive */
+    std::string mDiskId;
+    /* Partition GUID of this volume */
+    std::string mPartGuid;
+    /* Volume type */
+    Type mType;
+    /* Flags used when mounting this volume */
+    int mMountFlags;
+    /* User that owns this volume, otherwise -1 */
+    userid_t mMountUserId;
+    /* Flag indicating object is created */
+    bool mCreated;
+    /* Current state of volume */
+    State mState;
+    /* Path to mounted volume */
+    std::string mPath;
+    /* Path to internal backing storage */
+    std::string mInternalPath;
+    /* Flag indicating that volume should emit no events */
+    bool mSilent;
+
+    /* Volumes stacked on top of this volume */
+    std::list<std::shared_ptr<VolumeBase>> mVolumes;
+
+    void setState(State state);
+
+    DISALLOW_COPY_AND_ASSIGN(VolumeBase);
+};
+
+}  // namespace vold
+}  // namespace android
+
+#endif