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/VolumeManager.cpp b/VolumeManager.cpp
index ddf06eb..022caff 100644
--- a/VolumeManager.cpp
+++ b/VolumeManager.cpp
@@ -48,7 +48,8 @@
#include <private/android_filesystem_config.h>
#include "Benchmark.h"
-#include "EmulatedVolume.h"
+#include "model/EmulatedVolume.h"
+#include "model/ObbVolume.h"
#include "VolumeManager.h"
#include "NetlinkManager.h"
#include "ResponseCode.h"
@@ -68,6 +69,7 @@
+ ((number) & (~((1U << (po2)) - 1))))
using android::base::StringPrintf;
+using android::base::unique_fd;
/*
* Path to external storage where *only* root can access ASEC image files
@@ -220,6 +222,7 @@
mSavedDirtyRatio = -1;
// set dirty ratio to 0 when UMS is active
mUmsDirtyRatio = 0;
+ mNextObbId = 0;
}
VolumeManager::~VolumeManager() {
@@ -317,6 +320,9 @@
// directories that we own, in case we crashed.
unmountAll();
+ Devmapper::destroyAll();
+ Loop::destroyAll();
+
// Assume that we always have an emulated volume on internal
// storage; the framework will decide if it should be mounted.
CHECK(mInternalEmulated == nullptr);
@@ -437,6 +443,11 @@
return vol;
}
}
+ for (const auto& vol : mObbVolumes) {
+ if (vol->getId() == id) {
+ return vol;
+ }
+ }
return nullptr;
}
@@ -754,7 +765,7 @@
endmntent(fp);
for (const auto& path : toUnmount) {
- SLOGW("Tearing down stale mount %s", path.c_str());
+ LOG(DEBUG) << "Tearing down stale mount " << path;
android::vold::ForceUnmount(path);
}
@@ -1962,3 +1973,223 @@
return -EINVAL;
}
}
+
+static size_t kAppFuseMaxMountPointName = 32;
+
+static android::status_t getMountPath(uid_t uid, const std::string& name, std::string* path) {
+ if (name.size() > kAppFuseMaxMountPointName) {
+ LOG(ERROR) << "AppFuse mount name is too long.";
+ return -EINVAL;
+ }
+ for (size_t i = 0; i < name.size(); i++) {
+ if (!isalnum(name[i])) {
+ LOG(ERROR) << "AppFuse mount name contains invalid character.";
+ return -EINVAL;
+ }
+ }
+ *path = android::base::StringPrintf("/mnt/appfuse/%d_%s", uid, name.c_str());
+ return android::OK;
+}
+
+static android::status_t mountInNamespace(uid_t uid, int device_fd, const std::string& path) {
+ // Remove existing mount.
+ android::vold::ForceUnmount(path);
+
+ const auto opts = android::base::StringPrintf(
+ "fd=%i,"
+ "rootmode=40000,"
+ "default_permissions,"
+ "allow_other,"
+ "user_id=%d,group_id=%d,"
+ "context=\"u:object_r:app_fuse_file:s0\","
+ "fscontext=u:object_r:app_fusefs:s0",
+ device_fd,
+ uid,
+ uid);
+
+ const int result = TEMP_FAILURE_RETRY(mount(
+ "/dev/fuse", path.c_str(), "fuse",
+ MS_NOSUID | MS_NODEV | MS_NOEXEC | MS_NOATIME, opts.c_str()));
+ if (result != 0) {
+ PLOG(ERROR) << "Failed to mount " << path;
+ return -errno;
+ }
+
+ return android::OK;
+}
+
+static android::status_t runCommandInNamespace(const std::string& command,
+ uid_t uid,
+ pid_t pid,
+ const std::string& path,
+ int device_fd) {
+ if (DEBUG_APPFUSE) {
+ LOG(DEBUG) << "Run app fuse command " << command << " for the path " << path
+ << " in namespace " << uid;
+ }
+
+ unique_fd dir(open("/proc", O_RDONLY | O_DIRECTORY | O_CLOEXEC));
+ if (dir.get() == -1) {
+ PLOG(ERROR) << "Failed to open /proc";
+ return -errno;
+ }
+
+ // Obtains process file descriptor.
+ const std::string pid_str = android::base::StringPrintf("%d", pid);
+ const unique_fd pid_fd(
+ openat(dir.get(), pid_str.c_str(), O_RDONLY | O_DIRECTORY | O_CLOEXEC));
+ if (pid_fd.get() == -1) {
+ PLOG(ERROR) << "Failed to open /proc/" << pid;
+ return -errno;
+ }
+
+ // Check UID of process.
+ {
+ struct stat sb;
+ const int result = fstat(pid_fd.get(), &sb);
+ if (result == -1) {
+ PLOG(ERROR) << "Failed to stat /proc/" << pid;
+ return -errno;
+ }
+ if (sb.st_uid != AID_SYSTEM) {
+ LOG(ERROR) << "Only system can mount appfuse. UID expected=" << AID_SYSTEM
+ << ", actual=" << sb.st_uid;
+ return -EPERM;
+ }
+ }
+
+ // Matches so far, but refuse to touch if in root namespace
+ {
+ char rootName[PATH_MAX];
+ char pidName[PATH_MAX];
+ const int root_result =
+ android::vold::SaneReadLinkAt(dir.get(), "1/ns/mnt", rootName, PATH_MAX);
+ const int pid_result =
+ android::vold::SaneReadLinkAt(pid_fd.get(), "ns/mnt", pidName, PATH_MAX);
+ if (root_result == -1) {
+ LOG(ERROR) << "Failed to readlink for /proc/1/ns/mnt";
+ return -EPERM;
+ }
+ if (pid_result == -1) {
+ LOG(ERROR) << "Failed to readlink for /proc/" << pid << "/ns/mnt";
+ return -EPERM;
+ }
+ if (!strcmp(rootName, pidName)) {
+ LOG(ERROR) << "Don't mount appfuse in root namespace";
+ return -EPERM;
+ }
+ }
+
+ // We purposefully leave the namespace open across the fork
+ unique_fd ns_fd(openat(pid_fd.get(), "ns/mnt", O_RDONLY)); // not O_CLOEXEC
+ if (ns_fd.get() < 0) {
+ PLOG(ERROR) << "Failed to open namespace for /proc/" << pid << "/ns/mnt";
+ return -errno;
+ }
+
+ int child = fork();
+ if (child == 0) {
+ if (setns(ns_fd.get(), CLONE_NEWNS) != 0) {
+ PLOG(ERROR) << "Failed to setns";
+ _exit(-errno);
+ }
+
+ if (command == "mount") {
+ _exit(mountInNamespace(uid, device_fd, path));
+ } else if (command == "unmount") {
+ // If it's just after all FD opened on mount point are closed, umount2 can fail with
+ // EBUSY. To avoid the case, specify MNT_DETACH.
+ if (umount2(path.c_str(), UMOUNT_NOFOLLOW | MNT_DETACH) != 0 &&
+ errno != EINVAL && errno != ENOENT) {
+ PLOG(ERROR) << "Failed to unmount directory.";
+ _exit(-errno);
+ }
+ if (rmdir(path.c_str()) != 0) {
+ PLOG(ERROR) << "Failed to remove the mount directory.";
+ _exit(-errno);
+ }
+ _exit(android::OK);
+ } else {
+ LOG(ERROR) << "Unknown appfuse command " << command;
+ _exit(-EPERM);
+ }
+ }
+
+ if (child == -1) {
+ PLOG(ERROR) << "Failed to folk child process";
+ return -errno;
+ }
+
+ android::status_t status;
+ TEMP_FAILURE_RETRY(waitpid(child, &status, 0));
+
+ return status;
+}
+
+int VolumeManager::createObb(const std::string& sourcePath, const std::string& sourceKey,
+ int32_t ownerGid, std::string* outVolId) {
+ int id = mNextObbId++;
+
+ auto vol = std::shared_ptr<android::vold::VolumeBase>(
+ new android::vold::ObbVolume(id, sourcePath, sourceKey, ownerGid));
+ vol->create();
+
+ mObbVolumes.push_back(vol);
+ *outVolId = vol->getId();
+ return android::OK;
+}
+
+int VolumeManager::destroyObb(const std::string& volId) {
+ auto i = mObbVolumes.begin();
+ while (i != mObbVolumes.end()) {
+ if ((*i)->getId() == volId) {
+ (*i)->destroy();
+ i = mObbVolumes.erase(i);
+ } else {
+ ++i;
+ }
+ }
+ return android::OK;
+}
+
+int VolumeManager::mountAppFuse(uid_t uid, pid_t pid, int mountId,
+ android::base::unique_fd* device_fd) {
+ std::string name = std::to_string(mountId);
+
+ // Check mount point name.
+ std::string path;
+ if (getMountPath(uid, name, &path) != android::OK) {
+ LOG(ERROR) << "Invalid mount point name";
+ return -1;
+ }
+
+ // Create directories.
+ const android::status_t result = android::vold::PrepareDir(path, 0700, 0, 0);
+ if (result != android::OK) {
+ PLOG(ERROR) << "Failed to prepare directory " << path;
+ return -1;
+ }
+
+ // Open device FD.
+ device_fd->reset(open("/dev/fuse", O_RDWR)); // not O_CLOEXEC
+ if (device_fd->get() == -1) {
+ PLOG(ERROR) << "Failed to open /dev/fuse";
+ return -1;
+ }
+
+ // Mount.
+ return runCommandInNamespace("mount", uid, pid, path, device_fd->get());
+}
+
+int VolumeManager::unmountAppFuse(uid_t uid, pid_t pid, int mountId) {
+ std::string name = std::to_string(mountId);
+
+ // Check mount point name.
+ std::string path;
+ if (getMountPath(uid, name, &path) != android::OK) {
+ LOG(ERROR) << "Invalid mount point name";
+ return -1;
+ }
+
+ return runCommandInNamespace("unmount", uid, pid, path, -1 /* device_fd */);
+}