Add a new "virtual disk" feature.

It's extremely difficult to test storage related logic on devices
that don't have physical SD card slots.  So to support better
debugging and testing, add a new "virtual disk" feature which mounts
a 512MB file through loop device.

It relies on the kernel having the "loop.max_part" value set to
something other than 0 via the boot command line, since that allows
all the existing partition logic to fall into place.

Bug: 34903607
Test: builds, boots, virtual disk works
Change-Id: I04c5b33e37319d867542985a56b7999a9b7cf35d
diff --git a/Disk.cpp b/Disk.cpp
index 6562d96..8e58be0 100644
--- a/Disk.cpp
+++ b/Disk.cpp
@@ -47,8 +47,10 @@
 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;
@@ -229,6 +231,10 @@
 
     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:
@@ -534,6 +540,14 @@
     // 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:
diff --git a/Loop.cpp b/Loop.cpp
index 1127817..7e243de 100644
--- a/Loop.cpp
+++ b/Loop.cpp
@@ -32,12 +32,19 @@
 
 #include <cutils/log.h>
 
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <android-base/unique_fd.h>
+
 #include <sysutils/SocketClient.h>
 #include "Loop.h"
 #include "Asec.h"
 #include "VoldUtil.h"
 #include "sehandle.h"
 
+using android::base::StringPrintf;
+using android::base::unique_fd;
+
 int Loop::dumpState(SocketClient *c) {
     int i;
     int fd;
@@ -229,6 +236,40 @@
     return 0;
 }
 
+int Loop::create(const std::string& target, std::string& out_device) {
+    unique_fd ctl_fd(open("/dev/loop-control", O_RDWR));
+    if (ctl_fd.get() == -1) {
+        PLOG(ERROR) << "Failed to open loop-control";
+        return -errno;
+    }
+
+    int num = ioctl(ctl_fd.get(), LOOP_CTL_GET_FREE);
+    if (num == -1) {
+        PLOG(ERROR) << "Failed LOOP_CTL_GET_FREE";
+        return -errno;
+    }
+
+    out_device = StringPrintf("/dev/block/loop%d", num);
+
+    unique_fd target_fd(open(target.c_str(), O_RDWR));
+    if (target_fd.get() == -1) {
+        PLOG(ERROR) << "Failed to open " << target;
+        return -errno;
+    }
+    unique_fd device_fd(open(out_device.c_str(), O_RDWR));
+    if (device_fd.get() == -1) {
+        PLOG(ERROR) << "Failed to open " << out_device;
+        return -errno;
+    }
+
+    if (ioctl(device_fd.get(), LOOP_SET_FD, target_fd.get()) == -1) {
+        PLOG(ERROR) << "Failed to LOOP_SET_FD";
+        return -errno;
+    }
+
+    return 0;
+}
+
 int Loop::destroyByDevice(const char *loopDevice) {
     int device_fd;
 
@@ -254,20 +295,37 @@
 }
 
 int Loop::createImageFile(const char *file, unsigned long numSectors) {
-    int fd;
+    int res = 0;
 
-    if ((fd = creat(file, 0600)) < 0) {
-        SLOGE("Error creating imagefile (%s)", strerror(errno));
-        return -1;
+    char* secontext = nullptr;
+    if (sehandle) {
+        if (!selabel_lookup(sehandle, &secontext, file, S_IFREG)) {
+            setfscreatecon(secontext);
+        }
     }
 
-    if (ftruncate(fd, numSectors * 512) < 0) {
-        SLOGE("Error truncating imagefile (%s)", strerror(errno));
-        close(fd);
-        return -1;
+    unique_fd fd(creat(file, 0600));
+    if (fd.get() == -1) {
+        PLOG(ERROR) << "Failed to create image " << file;
+        res = -errno;
+        goto done;
     }
-    close(fd);
-    return 0;
+
+    if (fallocate(fd.get(), 0, 0, numSectors * 512) == -1) {
+        PLOG(WARNING) << "Failed to fallocate; falling back to ftruncate";
+        if (ftruncate(fd, numSectors * 512) == -1) {
+            PLOG(ERROR) << "Failed to ftruncate";
+            res = -errno;
+        }
+    }
+
+done:
+    if (secontext) {
+        setfscreatecon(nullptr);
+        freecon(secontext);
+    }
+
+    return res;
 }
 
 int Loop::resizeImageFile(const char *file, unsigned long numSectors) {
diff --git a/Loop.h b/Loop.h
index 72130b0..5d8f427 100644
--- a/Loop.h
+++ b/Loop.h
@@ -17,6 +17,7 @@
 #ifndef _LOOP_H
 #define _LOOP_H
 
+#include <string>
 #include <unistd.h>
 #include <linux/loop.h>
 
@@ -29,6 +30,7 @@
     static int lookupActive(const char *id, char *buffer, size_t len);
     static int lookupInfo(const char *loopDevice, struct asec_superblock *sb, unsigned long *nr_sec);
     static int create(const char *id, const char *loopFile, char *loopDeviceBuffer, size_t len);
+    static int create(const std::string& file, std::string& out_device);
     static int destroyByDevice(const char *loopDevice);
     static int destroyByFile(const char *loopFile);
     static int createImageFile(const char *file, unsigned long numSectors);
diff --git a/VolumeManager.cpp b/VolumeManager.cpp
index 40e3921..a67c8ec 100644
--- a/VolumeManager.cpp
+++ b/VolumeManager.cpp
@@ -88,7 +88,13 @@
  */
 const char *VolumeManager::LOOPDIR           = "/mnt/obb";
 
-static const char* kUserMountPath = "/mnt/user";
+static const char* kPathUserMount = "/mnt/user";
+static const char* kPathVirtualDisk = "/data/misc/vold/virtual_disk";
+
+static const char* kPropVirtualDisk = "persist.sys.virtual_disk";
+
+/* 512MiB is large enough for testing purposes */
+static const unsigned int kSizeVirtualDisk = 536870912;
 
 static const unsigned int kMajorBlockMmc = 179;
 static const unsigned int kMajorBlockExperimentalMin = 240;
@@ -249,6 +255,55 @@
     return buffer;
 }
 
+int VolumeManager::updateVirtualDisk() {
+    if (property_get_bool(kPropVirtualDisk, false)) {
+        if (access(kPathVirtualDisk, F_OK) != 0) {
+            Loop::createImageFile(kPathVirtualDisk, kSizeVirtualDisk / 512);
+        }
+
+        if (mVirtualDisk == nullptr) {
+            if (Loop::create(kPathVirtualDisk, mVirtualDiskPath) != 0) {
+                LOG(ERROR) << "Failed to create virtual disk";
+                return -1;
+            }
+
+            struct stat buf;
+            if (stat(mVirtualDiskPath.c_str(), &buf) < 0) {
+                PLOG(ERROR) << "Failed to stat " << mVirtualDiskPath;
+                return -1;
+            }
+
+            auto disk = new android::vold::Disk("virtual", buf.st_rdev, "virtual",
+                    android::vold::Disk::Flags::kAdoptable | android::vold::Disk::Flags::kSd);
+            disk->create();
+            mVirtualDisk = std::shared_ptr<android::vold::Disk>(disk);
+            mDisks.push_back(mVirtualDisk);
+        }
+    } else {
+        if (mVirtualDisk != nullptr) {
+            dev_t device = mVirtualDisk->getDevice();
+
+            auto i = mDisks.begin();
+            while (i != mDisks.end()) {
+                if ((*i)->getDevice() == device) {
+                    (*i)->destroy();
+                    i = mDisks.erase(i);
+                } else {
+                    ++i;
+                }
+            }
+
+            Loop::destroyByDevice(mVirtualDiskPath.c_str());
+            mVirtualDisk = nullptr;
+        }
+
+        if (access(kPathVirtualDisk, F_OK) == 0) {
+            unlink(kPathVirtualDisk);
+        }
+    }
+    return 0;
+}
+
 int VolumeManager::setDebug(bool enable) {
     mDebug = enable;
     return 0;
@@ -266,6 +321,9 @@
             new android::vold::EmulatedVolume("/data/media"));
     mInternalEmulated->create();
 
+    // Consider creating a virtual disk
+    updateVirtualDisk();
+
     return 0;
 }
 
@@ -455,7 +513,7 @@
     // Note that sometimes the system will spin up processes from Zygote
     // before actually starting the user, so we're okay if Zygote
     // already created this directory.
-    std::string path(StringPrintf("%s/%d", kUserMountPath, userId));
+    std::string path(StringPrintf("%s/%d", kPathUserMount, userId));
     fs_prepare_dir(path.c_str(), 0755, AID_ROOT, AID_ROOT);
 
     mStartedUsers.insert(userId);
@@ -634,6 +692,7 @@
         disk->destroy();
         disk->create();
     }
+    updateVirtualDisk();
     mAddedUsers.clear();
     mStartedUsers.clear();
     return 0;
diff --git a/VolumeManager.h b/VolumeManager.h
index dd9f09d..796a91d 100644
--- a/VolumeManager.h
+++ b/VolumeManager.h
@@ -176,6 +176,7 @@
     int unmountLoopImage(const char *containerId, const char *loopId,
             const char *fileName, const char *mountPoint, bool force);
 
+    int updateVirtualDisk();
     int setDebug(bool enable);
 
     void setBroadcaster(SocketListener *sl) { mBroadcaster = sl; }
@@ -211,6 +212,8 @@
     std::unordered_map<userid_t, int> mAddedUsers;
     std::unordered_set<userid_t> mStartedUsers;
 
+    std::string mVirtualDiskPath;
+    std::shared_ptr<android::vold::Disk> mVirtualDisk;
     std::shared_ptr<android::vold::VolumeBase> mInternalEmulated;
     std::shared_ptr<android::vold::VolumeBase> mPrimary;
 };