Merge "Checkpoint of better dynamic device support."
diff --git a/Android.mk b/Android.mk
index cf2b291..cbfbbd2 100644
--- a/Android.mk
+++ b/Android.mk
@@ -17,7 +17,12 @@
 	CheckBattery.cpp \
 	VoldUtil.c \
 	fstrim.c \
-	cryptfs.c
+	cryptfs.c \
+	Disk.cpp \
+	VolumeBase.cpp \
+	PublicVolume.cpp \
+	EmulatedVolume.cpp \
+	Utils.cpp \
 
 common_c_includes := \
 	system/extras/ext4_utils \
@@ -51,7 +56,7 @@
 	libbatteryservice
 
 vold_conlyflags := -std=c11
-vold_cflags := -Werror -Wall -Wno-missing-field-initializers
+vold_cflags := -Werror -Wall -Wno-missing-field-initializers -Wno-unused-variable -Wno-unused-parameter
 
 include $(CLEAR_VARS)
 
diff --git a/Disk.cpp b/Disk.cpp
new file mode 100644
index 0000000..673827c
--- /dev/null
+++ b/Disk.cpp
@@ -0,0 +1,286 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "Vold"
+
+#include "Disk.h"
+#include "PublicVolume.h"
+#include "Utils.h"
+#include "VolumeBase.h"
+
+#include <cutils/log.h>
+#include <diskconfig/diskconfig.h>
+#include <utils/file.h>
+#include <utils/stringprintf.h>
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mount.h>
+
+namespace android {
+namespace vold {
+
+static const char* kSgdiskPath = "/system/bin/sgdisk";
+static const char* kSgdiskToken = " \t\n";
+
+static const char* kSysfsMmcMaxMinors = "/sys/module/mmcblk/parameters/perdev_minors";
+
+static const unsigned int kMajorBlockScsi = 8;
+static const unsigned int kMajorBlockMmc = 179;
+
+static const char* kGptBasicData = "EBD0A0A2-B9E5-4433-87C0-68B6B72699C7";
+static const char* kGptAndroidMeta = "19A710A2-B3CA-11E4-B026-10604B889DCF";
+static const char* kGptAndroidExt = "193D1EA4-B3CA-11E4-B075-10604B889DCF";
+
+enum class Table {
+    kUnknown,
+    kMbr,
+    kGpt,
+};
+
+Disk::Disk(const std::string& eventPath, dev_t device) :
+        mDevice(device), mSize(-1) {
+    mId = StringPrintf("disk:%ud:%ud", major(device), minor(device));
+    mSysPath = StringPrintf("/sys/%s", eventPath.c_str());
+    mDevPath = StringPrintf("/dev/block/vold/%ud:%ud", major(device), minor(device));
+
+    CreateDeviceNode(mDevPath, mDevice);
+}
+
+Disk::~Disk() {
+    DestroyDeviceNode(mDevPath);
+}
+
+std::shared_ptr<VolumeBase> Disk::findVolume(const std::string& id) {
+    for (std::shared_ptr<VolumeBase>& v : mParts) {
+        if (!id.compare(v->getId())) {
+            return v;
+        }
+    }
+    return nullptr;
+}
+
+status_t Disk::readMetadata() {
+    mSize = -1;
+    mLabel = "";
+
+    {
+        std::string path(mSysPath + "/size");
+        std::string tmp;
+        if (!ReadFileToString(path, &tmp)) {
+            ALOGW("Failed to read size from %s: %s", path.c_str(), strerror(errno));
+            return -errno;
+        }
+        mSize = strtoll(tmp.c_str(), nullptr, 10);
+    }
+
+    switch (major(mDevice)) {
+    case kMajorBlockScsi: {
+        std::string path(mSysPath + "/device/vendor");
+        std::string tmp;
+        if (!ReadFileToString(path, &tmp)) {
+            ALOGW("Failed to read vendor from %s: %s", path.c_str(), strerror(errno));
+            return -errno;
+        }
+        mLabel = tmp;
+        break;
+    }
+    case kMajorBlockMmc: {
+        std::string path(mSysPath + "/device/manfid");
+        std::string tmp;
+        if (!ReadFileToString(path, &tmp)) {
+            ALOGW("Failed to read manufacturer from %s: %s", path.c_str(), strerror(errno));
+            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: {
+        ALOGW("Unsupported block major type %d", major(mDevice));
+        return -ENOTSUP;
+    }
+    }
+
+    return OK;
+}
+
+status_t Disk::readPartitions() {
+    int8_t maxMinors = getMaxMinors();
+    if (maxMinors < 0) {
+        return -ENOTSUP;
+    }
+
+    mParts.clear();
+
+    // Parse partition table
+    std::string path(kSgdiskPath);
+    path += " --android-dump ";
+    path += mDevPath;
+    FILE* fp = popen(path.c_str(), "r");
+    if (!fp) {
+        ALOGE("Failed to run %s: %s", path.c_str(), strerror(errno));
+        return -errno;
+    }
+
+    char line[1024];
+    Table table = Table::kUnknown;
+    while (fgets(line, sizeof(line), fp) != nullptr) {
+        char* token = strtok(line, kSgdiskToken);
+        if (!strcmp(token, "DISK")) {
+            const char* type = strtok(nullptr, kSgdiskToken);
+            ALOGD("%s: found %s partition table", mId.c_str(), type);
+            if (!strcmp(type, "mbr")) {
+                table = Table::kMbr;
+            } else if (!strcmp(type, "gpt")) {
+                table = Table::kGpt;
+            }
+        } else if (!strcmp(token, "PART")) {
+            int i = strtol(strtok(nullptr, kSgdiskToken), nullptr, 10);
+            if (i <= 0 || i > maxMinors) {
+                ALOGW("%s: ignoring partition %d beyond max supported devices",
+                        mId.c_str(), i);
+                continue;
+            }
+            dev_t partDevice = makedev(major(mDevice), minor(mDevice) + i);
+
+            VolumeBase* vol = nullptr;
+            if (table == Table::kMbr) {
+                const char* type = strtok(nullptr, kSgdiskToken);
+                ALOGD("%s: MBR partition %d type %s", mId.c_str(), i, type);
+
+                switch (strtol(type, nullptr, 16)) {
+                case 0x06: // FAT16
+                case 0x0b: // W95 FAT32 (LBA)
+                case 0x0c: // W95 FAT32 (LBA)
+                case 0x0e: // W95 FAT16 (LBA)
+                    vol = new PublicVolume(partDevice);
+                    break;
+                }
+            } else if (table == Table::kGpt) {
+                const char* typeGuid = strtok(nullptr, kSgdiskToken);
+                const char* partGuid = strtok(nullptr, kSgdiskToken);
+                ALOGD("%s: GPT partition %d type %s, GUID %s", mId.c_str(), i,
+                        typeGuid, partGuid);
+
+                if (!strcasecmp(typeGuid, kGptBasicData)) {
+                    vol = new PublicVolume(partDevice);
+                } else if (!strcasecmp(typeGuid, kGptAndroidExt)) {
+                    //vol = new PrivateVolume();
+                }
+            }
+
+            if (vol != nullptr) {
+                mParts.push_back(std::shared_ptr<VolumeBase>(vol));
+            }
+        }
+    }
+
+    // Ugly last ditch effort, treat entire disk as partition
+    if (table == Table::kUnknown) {
+        ALOGD("%s: unknown partition table; trying entire device", mId.c_str());
+        VolumeBase* vol = new PublicVolume(mDevice);
+        mParts.push_back(std::shared_ptr<VolumeBase>(vol));
+    }
+
+    pclose(fp);
+    return OK;
+}
+
+status_t Disk::partitionPublic() {
+    // TODO: improve this code
+
+    struct disk_info dinfo;
+    memset(&dinfo, 0, sizeof(dinfo));
+
+    if (!(dinfo.part_lst = (struct part_info *) malloc(
+            MAX_NUM_PARTS * sizeof(struct part_info)))) {
+        SLOGE("Failed to malloc prt_lst");
+        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) {
+        SLOGE("Failed to apply disk configuration (%d)", rc);
+        goto out;
+    }
+
+out:
+    free(pinfo->name);
+    free(dinfo.device);
+    free(dinfo.part_lst);
+
+    return rc;
+}
+
+status_t Disk::partitionPrivate() {
+    return -ENOTSUP;
+}
+
+status_t Disk::partitionMixed(int8_t ratio) {
+    return -ENOTSUP;
+}
+
+int Disk::getMaxMinors() {
+    // Figure out maximum partition devices supported
+    switch (major(mDevice)) {
+    case kMajorBlockScsi: {
+        // 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)) {
+            ALOGW("Failed to read max minors");
+            return -errno;
+        }
+        return atoi(tmp.c_str());
+    }
+    }
+
+    ALOGW("Unsupported block major type %d", major(mDevice));
+    return -ENOTSUP;
+}
+
+}  // namespace vold
+}  // namespace android
diff --git a/Disk.h b/Disk.h
new file mode 100644
index 0000000..eab8976
--- /dev/null
+++ b/Disk.h
@@ -0,0 +1,92 @@
+/*
+ * 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 <utils/Errors.h>
+
+#include <vector>
+
+namespace android {
+namespace vold {
+
+class VolumeBase;
+
+// events:
+// disk_created 127:4
+// disk_meta 127:4 [size] [label]
+// disk_destroyed 127:4
+
+// commands:
+// disk partition_public 127:4
+// disk partition_private 127:4
+// disk partition_mixed 127:4 50
+
+/*
+ * 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);
+    virtual ~Disk();
+
+    const std::string& getId() { return mId; }
+    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; }
+
+    std::shared_ptr<VolumeBase> findVolume(const std::string& id);
+
+    status_t readMetadata();
+    status_t readPartitions();
+
+    status_t partitionPublic();
+    status_t partitionPrivate();
+    status_t partitionMixed(int8_t ratio);
+
+private:
+    /* ID that uniquely references this disk */
+    std::string mId;
+    /* 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>> mParts;
+
+    int getMaxMinors();
+
+    DISALLOW_COPY_AND_ASSIGN(Disk);
+};
+
+}  // namespace vold
+}  // namespace android
+
+#endif
diff --git a/EmulatedVolume.cpp b/EmulatedVolume.cpp
new file mode 100644
index 0000000..5149962
--- /dev/null
+++ b/EmulatedVolume.cpp
@@ -0,0 +1,131 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "Vold"
+
+#include "EmulatedVolume.h"
+#include "Utils.h"
+
+#include <cutils/fs.h>
+#include <cutils/log.h>
+#include <utils/file.h>
+#include <utils/stringprintf.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>
+
+namespace android {
+namespace vold {
+
+static const char* kFusePath = "/system/bin/sdcard";
+
+static const char* kUserMountPath = "/mnt/user";
+
+EmulatedVolume::EmulatedVolume(const std::string& rawPath, const std::string& nickname) :
+        VolumeBase(VolumeType::kEmulated), mFusePid(0), mPrimary(false) {
+    mRawPath = rawPath;
+    mFusePath = StringPrintf("/mnt/media_rw/emulated_fuse_%s", nickname.c_str());
+}
+
+EmulatedVolume::~EmulatedVolume() {
+}
+
+status_t EmulatedVolume::doMount() {
+    if (fs_prepare_dir(mFusePath.c_str(), 0770, AID_MEDIA_RW, AID_MEDIA_RW)) {
+        SLOGE("Failed to create mount point %s: %s", mFusePath.c_str(), strerror(errno));
+        return -errno;
+    }
+
+    if (!(mFusePid = fork())) {
+        if (execl(kFusePath,
+                "-u", "1023", // AID_MEDIA_RW
+                "-g", "1023", // AID_MEDIA_RW
+                "-d",
+                mRawPath.c_str(),
+                mFusePath.c_str())) {
+            SLOGE("Failed to exec: %s", strerror(errno));
+        }
+        _exit(1);
+    }
+
+    if (mFusePid == -1) {
+        SLOGE("Failed to fork: %s", strerror(errno));
+        return -errno;
+    }
+
+    return OK;
+}
+
+status_t EmulatedVolume::doUnmount() {
+    if (mFusePid > 0) {
+        kill(mFusePid, SIGTERM);
+        TEMP_FAILURE_RETRY(waitpid(mFusePid, nullptr, 0));
+        mFusePid = 0;
+    }
+
+    ForceUnmount(mFusePath);
+
+    TEMP_FAILURE_RETRY(unlink(mFusePath.c_str()));
+
+    return OK;
+}
+
+status_t EmulatedVolume::doFormat() {
+    return -ENOTSUP;
+}
+
+status_t EmulatedVolume::bindUser(userid_t user) {
+    return bindUserInternal(user, true);
+}
+
+status_t EmulatedVolume::unbindUser(userid_t user) {
+    return bindUserInternal(user, false);
+}
+
+status_t EmulatedVolume::bindUserInternal(userid_t user, bool bind) {
+    if (!mPrimary) {
+        // Emulated volumes are only bound when primary
+        return OK;
+    }
+
+    std::string fromPath(StringPrintf("%s/%ud", mFusePath.c_str(), user));
+    std::string toPath(StringPrintf("%s/%ud/primary", kUserMountPath, user));
+
+    if (bind) {
+        mountBind(fromPath, toPath);
+    } else {
+        unmountBind(toPath);
+    }
+
+    return OK;
+}
+
+void EmulatedVolume::setPrimary(bool primary) {
+    if (getState() != VolumeState::kUnmounted) {
+        SLOGE("Primary state change requires %s to be unmounted", getId().c_str());
+        return;
+    }
+
+    mPrimary = primary;
+}
+
+}  // namespace vold
+}  // namespace android
diff --git a/EmulatedVolume.h b/EmulatedVolume.h
new file mode 100644
index 0000000..972351c
--- /dev/null
+++ b/EmulatedVolume.h
@@ -0,0 +1,72 @@
+/*
+ * 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:
+    EmulatedVolume(const std::string& rawPath, const std::string& nickname);
+    virtual ~EmulatedVolume();
+
+    void setPrimary(bool primary);
+    bool getPrimary() { return mPrimary; }
+
+    status_t bindUser(userid_t user);
+    status_t unbindUser(userid_t user);
+
+private:
+    /* Mount point of raw storage */
+    std::string mRawPath;
+    /* Mount point of FUSE wrapper */
+    std::string mFusePath;
+    /* PID of FUSE wrapper */
+    pid_t mFusePid;
+    /* Flag indicating this is primary storage */
+    bool mPrimary;
+
+protected:
+    status_t doMount();
+    status_t doUnmount();
+    status_t doFormat();
+
+    status_t bindUserInternal(userid_t user, bool bind);
+
+    DISALLOW_COPY_AND_ASSIGN(EmulatedVolume);
+};
+
+}  // namespace vold
+}  // namespace android
+
+#endif
diff --git a/PublicVolume.cpp b/PublicVolume.cpp
new file mode 100644
index 0000000..78c8fa5
--- /dev/null
+++ b/PublicVolume.cpp
@@ -0,0 +1,251 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "Vold"
+
+#include "Fat.h"
+#include "PublicVolume.h"
+#include "Utils.h"
+
+#include <cutils/fs.h>
+#include <cutils/log.h>
+#include <utils/file.h>
+#include <utils/stringprintf.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>
+
+namespace android {
+namespace vold {
+
+static const char* kBlkidPath = "/system/bin/blkid";
+static const char* kFusePath = "/system/bin/sdcard";
+
+static const char* kUserMountPath = "/mnt/user";
+
+PublicVolume::PublicVolume(dev_t device) :
+        VolumeBase(VolumeType::kPublic), mDevice(device), mFusePid(0), mPrimary(false) {
+    mId = StringPrintf("public:%ud:%ud", major(device), minor(device));
+    mDevPath = StringPrintf("/dev/block/vold/%ud:%ud", major(device), minor(device));
+    mRawPath = StringPrintf("/mnt/media_rw/public_raw_%ud:%ud", major(device), minor(device));
+    mFusePath = StringPrintf("/mnt/media_rw/public_fuse_%ud:%ud", major(device), minor(device));
+
+    CreateDeviceNode(mDevPath, device);
+}
+
+PublicVolume::~PublicVolume() {
+    DestroyDeviceNode(mDevPath);
+}
+
+status_t PublicVolume::readMetadata() {
+    mFsUuid = "";
+    mFsLabel = "";
+
+    std::string path(StringPrintf("%s -c /dev/null %s", kBlkidPath, mDevPath.c_str()));
+    FILE* fp = popen(path.c_str(), "r");
+    if (!fp) {
+        ALOGE("Failed to run %s: %s", path.c_str(), strerror(errno));
+        return -errno;
+    }
+
+    char line[1024];
+    char value[128];
+    if (fgets(line, sizeof(line), fp) != nullptr) {
+        ALOGD("blkid identified as %s", line);
+
+        char* 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 {
+        ALOGW("blkid failed to identify %s", mDevPath.c_str());
+        return -ENODATA;
+    }
+
+    pclose(fp);
+
+    // TODO: broadcast ident to framework
+    return OK;
+}
+
+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())) {
+            SLOGE("Failed to rename legacy ASEC dir: %s", strerror(errno));
+        }
+    }
+
+    if (fs_prepare_dir(securePath.c_str(), 0770, AID_MEDIA_RW, AID_MEDIA_RW) != 0) {
+        SLOGW("fs_prepare_dir failed: %s", strerror(errno));
+        return -errno;
+    }
+
+    return OK;
+}
+
+status_t PublicVolume::doMount() {
+    if (Fat::check(mDevPath.c_str())) {
+        SLOGE("Failed filesystem check; not mounting");
+        return -EIO;
+    }
+
+    if (fs_prepare_dir(mRawPath.c_str(), 0770, AID_MEDIA_RW, AID_MEDIA_RW)) {
+        SLOGE("Failed to create mount point %s: %s", mRawPath.c_str(), strerror(errno));
+        return -errno;
+    }
+    if (fs_prepare_dir(mFusePath.c_str(), 0770, AID_MEDIA_RW, AID_MEDIA_RW)) {
+        SLOGE("Failed to create mount point %s: %s", mFusePath.c_str(), strerror(errno));
+        return -errno;
+    }
+
+    if (Fat::doMount(mDevPath.c_str(), mRawPath.c_str(), false, false, false,
+            AID_MEDIA_RW, AID_MEDIA_RW, 0007, true)) {
+        SLOGE("Failed to mount %s: %s", mDevPath.c_str(), strerror(errno));
+        return -EIO;
+    }
+
+    if (!(mFusePid = fork())) {
+        if (mPrimary) {
+            if (execl(kFusePath,
+                    "-u", "1023", // AID_MEDIA_RW
+                    "-g", "1023", // AID_MEDIA_RW
+                    "-d",
+                    mRawPath.c_str(),
+                    mFusePath.c_str())) {
+                SLOGE("Failed to exec: %s", strerror(errno));
+            }
+        } else {
+            if (execl(kFusePath,
+                    "-u", "1023", // AID_MEDIA_RW
+                    "-g", "1023", // AID_MEDIA_RW
+                    "-w", "1023", // AID_MEDIA_RW
+                    "-d",
+                    mRawPath.c_str(),
+                    mFusePath.c_str())) {
+                SLOGE("Failed to exec: %s", strerror(errno));
+            }
+        }
+
+        _exit(1);
+    }
+
+    if (mFusePid == -1) {
+        SLOGE("Failed to fork: %s", strerror(errno));
+        return -errno;
+    }
+
+    return OK;
+}
+
+status_t PublicVolume::doUnmount() {
+    if (mFusePid > 0) {
+        kill(mFusePid, SIGTERM);
+        TEMP_FAILURE_RETRY(waitpid(mFusePid, nullptr, 0));
+        mFusePid = 0;
+    }
+
+    ForceUnmount(mFusePath);
+    ForceUnmount(mRawPath);
+
+    TEMP_FAILURE_RETRY(unlink(mRawPath.c_str()));
+    TEMP_FAILURE_RETRY(unlink(mFusePath.c_str()));
+
+    return OK;
+}
+
+status_t PublicVolume::doFormat() {
+    if (Fat::format(mDevPath.c_str(), 0, true)) {
+        SLOGE("Failed to format: %s", strerror(errno));
+        return -errno;
+    }
+    return OK;
+}
+
+status_t PublicVolume::bindUser(userid_t user) {
+    return bindUserInternal(user, true);
+}
+
+status_t PublicVolume::unbindUser(userid_t user) {
+    return bindUserInternal(user, false);
+}
+
+status_t PublicVolume::bindUserInternal(userid_t user, bool bind) {
+    if (mPrimary) {
+        if (user == 0) {
+            std::string path(StringPrintf("%s/%ud/primary", kUserMountPath, user));
+            if (bind) {
+                mountBind(mFusePath, path);
+            } else {
+                unmountBind(path);
+            }
+        } else {
+            // Public volumes are only visible to owner when primary
+            // storage, so we don't mount for secondary users.
+        }
+    } else {
+        std::string path(StringPrintf("%s/%ud/public_%ud:%ud", kUserMountPath, user,
+                        major(mDevice), minor(mDevice)));
+        if (bind) {
+            mountBind(mFusePath, path);
+        } else {
+            unmountBind(path);
+        }
+
+        if (user != 0) {
+            // To prevent information leakage between users, only owner
+            // has access to the Android directory
+            path += "/Android";
+            if (bind) {
+                if (::mount("tmpfs", path.c_str(), "tmpfs", MS_NOSUID, "mode=0000")) {
+                    SLOGE("Failed to protect secondary path %s: %s",
+                            path.c_str(), strerror(errno));
+                    return -errno;
+                }
+            } else {
+                ForceUnmount(path);
+            }
+        }
+    }
+
+    return OK;
+}
+
+void PublicVolume::setPrimary(bool primary) {
+    if (getState() != VolumeState::kUnmounted) {
+        SLOGE("Primary state change requires %s to be unmounted", getId().c_str());
+        return;
+    }
+
+    mPrimary = primary;
+}
+
+}  // namespace vold
+}  // namespace android
diff --git a/PublicVolume.h b/PublicVolume.h
new file mode 100644
index 0000000..3da0995
--- /dev/null
+++ b/PublicVolume.h
@@ -0,0 +1,89 @@
+/*
+ * 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();
+
+    status_t readMetadata();
+    status_t initAsecStage();
+
+    void setPrimary(bool primary);
+    bool getPrimary() { return mPrimary; }
+
+    const std::string& getFsUuid() { return mFsUuid; }
+    const std::string& getFsLabel() { return mFsLabel; }
+
+    status_t bindUser(userid_t user);
+    status_t unbindUser(userid_t user);
+
+protected:
+    status_t doMount();
+    status_t doUnmount();
+    status_t doFormat();
+
+    status_t bindUserInternal(userid_t user, bool bind);
+
+private:
+    /* Kernel device representing partition */
+    dev_t mDevice;
+    /* Block device path */
+    std::string mDevPath;
+    /* Mount point of raw partition */
+    std::string mRawPath;
+    /* Mount point of FUSE wrapper */
+    std::string mFusePath;
+    /* PID of FUSE wrapper */
+    pid_t mFusePid;
+    /* Flag indicating this is primary storage */
+    bool mPrimary;
+
+    /* Parsed UUID from filesystem */
+    std::string mFsUuid;
+    /* User-visible label from filesystem */
+    std::string mFsLabel;
+
+    DISALLOW_COPY_AND_ASSIGN(PublicVolume);
+};
+
+}  // namespace vold
+}  // namespace android
+
+#endif
diff --git a/Utils.cpp b/Utils.cpp
new file mode 100644
index 0000000..7cbb2ce
--- /dev/null
+++ b/Utils.cpp
@@ -0,0 +1,105 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "Vold"
+
+#include "sehandle.h"
+#include "Utils.h"
+#include "Process.h"
+
+#include <cutils/fs.h>
+#include <cutils/log.h>
+#include <utils/file.h>
+#include <utils/stringprintf.h>
+#include <private/android_filesystem_config.h>
+
+#include <fcntl.h>
+#include <linux/fs.h>
+#include <stdlib.h>
+#include <sys/mount.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#ifndef UMOUNT_NOFOLLOW
+#define UMOUNT_NOFOLLOW    0x00000008  /* Don't follow symlink on umount */
+#endif
+
+namespace android {
+namespace vold {
+
+status_t CreateDeviceNode(const std::string& path, dev_t dev) {
+    const char* cpath = path.c_str();
+    status_t res = 0;
+
+    char* secontext = nullptr;
+    if (sehandle) {
+        if (!selabel_lookup(sehandle, &secontext, cpath, S_IFBLK)) {
+            setfscreatecon(secontext);
+        }
+    }
+
+    mode_t mode = 0660 | S_IFBLK;
+    if (mknod(cpath, mode, dev) < 0) {
+        if (errno != EEXIST) {
+            ALOGW("Failed to create device node for %ud:%ud at %s: %s",
+                    major(dev), minor(dev), cpath, strerror(errno));
+            res = -errno;
+        }
+    }
+
+    if (secontext) {
+        setfscreatecon(nullptr);
+        freecon(secontext);
+    }
+
+    return res;
+}
+
+status_t DestroyDeviceNode(const std::string& path) {
+    const char* cpath = path.c_str();
+    if (TEMP_FAILURE_RETRY(unlink(cpath))) {
+        return -errno;
+    } else {
+        return OK;
+    }
+}
+
+status_t ForceUnmount(const std::string& path) {
+    const char* cpath = path.c_str();
+    if (!umount2(cpath, UMOUNT_NOFOLLOW) || errno == EINVAL || errno == ENOENT) {
+        return OK;
+    }
+    ALOGW("Failed to unmount %s (%s), sending SIGTERM", cpath, strerror(errno));
+    Process::killProcessesWithOpenFiles(cpath, SIGTERM);
+    sleep(1);
+
+    if (!umount2(cpath, UMOUNT_NOFOLLOW) || errno == EINVAL || errno == ENOENT) {
+        return OK;
+    }
+    ALOGW("Failed to unmount %s (%s), sending SIGKILL", cpath, strerror(errno));
+    Process::killProcessesWithOpenFiles(cpath, SIGKILL);
+    sleep(1);
+
+    if (!umount2(cpath, UMOUNT_NOFOLLOW) || errno == EINVAL || errno == ENOENT) {
+        return OK;
+    }
+    ALOGW("Failed to unmount %s (%s)", cpath, strerror(errno));
+    return -errno;
+}
+
+}  // namespace vold
+}  // namespace android
diff --git a/Utils.h b/Utils.h
new file mode 100644
index 0000000..5199af6
--- /dev/null
+++ b/Utils.h
@@ -0,0 +1,44 @@
+/*
+ * 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_UTILS_H
+#define ANDROID_VOLD_UTILS_H
+
+#include <utils/Errors.h>
+
+#include <string>
+
+// DISALLOW_COPY_AND_ASSIGN disallows the copy and operator= functions. It goes in the private:
+// declarations in a class.
+#if !defined(DISALLOW_COPY_AND_ASSIGN)
+#define DISALLOW_COPY_AND_ASSIGN(TypeName) \
+    TypeName(const TypeName&) = delete;  \
+    void operator=(const TypeName&) = delete
+#endif
+
+namespace android {
+namespace vold {
+
+status_t CreateDeviceNode(const std::string& path, dev_t dev);
+status_t DestroyDeviceNode(const std::string& path);
+
+/* Really unmounts the path, killing active processes along the way */
+status_t ForceUnmount(const std::string& path);
+
+}  // namespace vold
+}  // namespace android
+
+#endif
diff --git a/VolumeBase.cpp b/VolumeBase.cpp
new file mode 100644
index 0000000..307791d
--- /dev/null
+++ b/VolumeBase.cpp
@@ -0,0 +1,131 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "Vold"
+
+#include "Utils.h"
+#include "VolumeBase.h"
+
+#include <cutils/log.h>
+
+#include <fcntl.h>
+#include <stdlib.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+namespace android {
+namespace vold {
+
+VolumeBase::VolumeBase(VolumeType type) :
+        mType(type), mState(VolumeState::kUnmounted) {
+}
+
+VolumeBase::~VolumeBase() {
+}
+
+void VolumeBase::setState(VolumeState state) {
+    mState = state;
+
+    // TODO: publish state up to framework
+}
+
+void VolumeBase::stackVolume(const std::shared_ptr<VolumeBase>& volume) {
+    mStacked.push_back(volume);
+}
+
+void VolumeBase::unstackVolume(const std::shared_ptr<VolumeBase>& volume) {
+    mStacked.remove(volume);
+}
+
+status_t VolumeBase::mount() {
+    if (getState() != VolumeState::kUnmounted) {
+        SLOGE("Must be unmounted to mount %s", getId().c_str());
+        return -EBUSY;
+    }
+
+    setState(VolumeState::kMounting);
+    status_t res = doMount();
+    if (!res) {
+        setState(VolumeState::kMounted);
+    } else {
+        setState(VolumeState::kCorrupt);
+    }
+
+    return res;
+}
+
+status_t VolumeBase::unmount() {
+    if (getState() != VolumeState::kMounted) {
+        SLOGE("Must be mounted to unmount %s", getId().c_str());
+        return -EBUSY;
+    }
+
+    setState(VolumeState::kUnmounting);
+
+    for (std::string target : mBindTargets) {
+        ForceUnmount(target);
+    }
+    mBindTargets.clear();
+
+    for (std::shared_ptr<VolumeBase> v : mStacked) {
+        if (v->unmount()) {
+            ALOGW("Failed to unmount %s stacked above %s", v->getId().c_str(),
+                    getId().c_str());
+        }
+    }
+    mStacked.clear();
+
+    status_t res = doUnmount();
+    setState(VolumeState::kUnmounted);
+    return res;
+}
+
+status_t VolumeBase::format() {
+    if (getState() != VolumeState::kUnmounted
+            || getState() != VolumeState::kCorrupt) {
+        SLOGE("Must be unmounted or corrupt to format %s", getId().c_str());
+        return -EBUSY;
+    }
+
+    setState(VolumeState::kFormatting);
+    status_t res = doFormat();
+    setState(VolumeState::kUnmounted);
+    return res;
+}
+
+status_t VolumeBase::doFormat() {
+    return -ENOTSUP;
+}
+
+status_t VolumeBase::mountBind(const std::string& source, const std::string& target) {
+    if (::mount(source.c_str(), target.c_str(), "", MS_BIND, NULL)) {
+        SLOGE("Failed to bind mount %s to %s: %s", source.c_str(),
+                target.c_str(), strerror(errno));
+        return -errno;
+    }
+    mBindTargets.push_back(target);
+    return OK;
+}
+
+status_t VolumeBase::unmountBind(const std::string& target) {
+    ForceUnmount(target);
+    mBindTargets.remove(target);
+    return OK;
+}
+
+}  // namespace vold
+}  // namespace android
diff --git a/VolumeBase.h b/VolumeBase.h
new file mode 100644
index 0000000..04240b7
--- /dev/null
+++ b/VolumeBase.h
@@ -0,0 +1,124 @@
+/*
+ * 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 <utils/Errors.h>
+
+#include <sys/types.h>
+#include <list>
+#include <string>
+
+namespace android {
+namespace vold {
+
+enum class VolumeState {
+    kUnmounted,
+    kMounting,
+    kMounted,
+    kCorrupt,
+    kFormatting,
+    kUnmounting,
+};
+
+enum class VolumeType {
+    kPublic,
+    kPrivate,
+    kEmulated,
+    kAsec,
+    kObb,
+};
+
+// events:
+// volume_created private:127:4
+// volume_state private:127:4 mounted
+// volume_meta private:127:4 [fsGuid] [label]
+// volume_destroyed public:127:4
+
+// commands:
+// volume mount public:127:4 [primary]
+// volume unmount public:127:4
+// volume bind_user public:127:4 [userId]
+// volume unbind_user public:127:4 [userId]
+// volume bind_package private:4:1 [userId] [package]
+// volume unbind_package private:4:1 [userId] [package]
+
+/*
+ * 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();
+
+    VolumeType getType() { return mType; }
+    const std::string& getId() { return mId; }
+    VolumeState getState() { return mState; }
+
+    void stackVolume(const std::shared_ptr<VolumeBase>& volume);
+    void unstackVolume(const std::shared_ptr<VolumeBase>& volume);
+
+    status_t mount();
+    status_t unmount();
+    status_t format();
+
+protected:
+    explicit VolumeBase(VolumeType type);
+
+    /* ID that uniquely references this disk */
+    std::string mId;
+
+    /* Manage bind mounts for this volume */
+    status_t mountBind(const std::string& source, const std::string& target);
+    status_t unmountBind(const std::string& target);
+
+    virtual status_t doMount() = 0;
+    virtual status_t doUnmount() = 0;
+    virtual status_t doFormat();
+
+private:
+    /* Volume type */
+    VolumeType mType;
+    /* Current state of volume */
+    VolumeState mState;
+
+    /* Volumes stacked on top of this volume */
+    std::list<std::shared_ptr<VolumeBase>> mStacked;
+    /* Currently active bind mounts */
+    std::list<std::string> mBindTargets;
+
+    void setState(VolumeState state);
+
+    DISALLOW_COPY_AND_ASSIGN(VolumeBase);
+};
+
+}  // namespace vold
+}  // namespace android
+
+#endif