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