Fix multi-user and multi-storage with FUSE
Up until now, the FUSE mount logic has made two assumptions:
1. The primary external volume is an emulated volume on /data/media
2. Only the primary user is running, as user zero
These assumptions are fixed by the following changes
creating an EmulatedVolume per Android user and changing the
VolumeBase id format to append the user to the id, so
s/emulated/emulated-0/. This allows us mount separate volumes per user
Some additional refactorings to re-use/clean up code.
Test: adb shell sm set-virtual-disk and partition disk operations work
even after setting up a work profile
Bug: 135341433
Change-Id: Ifabaa12368e5a591fbcdce4ee71c83ff35fdac6b
diff --git a/Utils.cpp b/Utils.cpp
index e5bf33d..48e5ce0 100644
--- a/Utils.cpp
+++ b/Utils.cpp
@@ -985,26 +985,61 @@
return true;
}
-int MountUserFuse(userid_t user_id, const std::string& relative_path,
- android::base::unique_fd* fuse_fd) {
- std::string path(StringPrintf("/mnt/user/%d/%s", user_id, relative_path.c_str()));
+status_t MountUserFuse(userid_t user_id, const std::string& absolute_lower_path,
+ const std::string& relative_upper_path, android::base::unique_fd* fuse_fd) {
+ std::string pre_fuse_path(StringPrintf("/mnt/user/%d", user_id));
+ std::string fuse_path(
+ StringPrintf("%s/%s", pre_fuse_path.c_str(), relative_upper_path.c_str()));
+
+ std::string pre_pass_through_path(StringPrintf("/mnt/pass_through/%d", user_id));
+ std::string pass_through_path(
+ StringPrintf("%s/%s", pre_pass_through_path.c_str(), relative_upper_path.c_str()));
// Force remove the existing mount before we attempt to prepare the
// directory. If we have a dangling mount, then PrepareDir may fail if the
// indirection to FUSE doesn't work.
- android::status_t result = android::vold::ForceUnmount(path);
+ android::status_t result = UnmountUserFuse(pass_through_path, fuse_path);
if (result != android::OK) {
- PLOG(ERROR) << "Failed to unmount " << path;
return -1;
}
// Create directories.
- result = android::vold::PrepareDir(path, 0700, AID_ROOT, AID_ROOT);
+ result = PrepareDir(pre_fuse_path, 0700, AID_ROOT, AID_ROOT);
if (result != android::OK) {
- PLOG(ERROR) << "Failed to prepare directory " << path;
+ PLOG(ERROR) << "Failed to prepare directory " << pre_fuse_path;
return -1;
}
+ result = PrepareDir(fuse_path, 0700, AID_ROOT, AID_ROOT);
+ if (result != android::OK) {
+ PLOG(ERROR) << "Failed to prepare directory " << fuse_path;
+ return -1;
+ }
+
+ result = PrepareDir(pre_pass_through_path, 0755, AID_ROOT, AID_ROOT);
+ if (result != android::OK) {
+ PLOG(ERROR) << "Failed to prepare directory " << pre_pass_through_path;
+ return -1;
+ }
+
+ result = PrepareDir(pass_through_path, 0755, AID_ROOT, AID_ROOT);
+ if (result != android::OK) {
+ PLOG(ERROR) << "Failed to prepare directory " << pass_through_path;
+ return -1;
+ }
+
+ if (relative_upper_path == "emulated") {
+ std::string target(StringPrintf("/mnt/user/%d/self", user_id));
+ result = PrepareDir(target, 0755, AID_ROOT, AID_ROOT);
+ if (result != android::OK) {
+ PLOG(ERROR) << "Failed to prepare directory " << target;
+ return -1;
+ }
+ target += "/primary";
+
+ Symlink(fuse_path + "/" + std::to_string(user_id), target);
+ }
+
// Open fuse fd.
fuse_fd->reset(open("/dev/fuse", O_RDWR | O_CLOEXEC));
if (fuse_fd->get() == -1) {
@@ -1021,15 +1056,35 @@
"user_id=0,group_id=0,",
fuse_fd->get());
- const int result_int =
- TEMP_FAILURE_RETRY(mount("/dev/fuse", path.c_str(), "fuse",
- MS_NOSUID | MS_NODEV | MS_NOEXEC | MS_NOATIME | MS_LAZYTIME,
- opts.c_str()));
- if (result_int != 0) {
- PLOG(ERROR) << "Failed to mount " << path;
+ result = TEMP_FAILURE_RETRY(mount("/dev/fuse", fuse_path.c_str(), "fuse",
+ MS_NOSUID | MS_NODEV | MS_NOEXEC | MS_NOATIME | MS_LAZYTIME,
+ opts.c_str()));
+ if (result != 0) {
+ PLOG(ERROR) << "Failed to mount " << fuse_path;
return -errno;
}
- return 0;
+ LOG(INFO) << "Bind mounting to " << absolute_lower_path;
+ return BindMount(absolute_lower_path, pass_through_path);
+}
+
+status_t UnmountUserFuse(const std::string& pass_through_path, const std::string& fuse_path) {
+ // Best effort unmount pass_through path
+ sSleepOnUnmount = false;
+ ForceUnmount(pass_through_path);
+ android::status_t result = ForceUnmount(fuse_path);
+ sSleepOnUnmount = true;
+ if (result != android::OK) {
+ // TODO(b/135341433): MNT_DETACH is needed for fuse because umount2 can fail with EBUSY.
+ // Figure out why we get EBUSY and remove this special casing if possible.
+ PLOG(ERROR) << "Failed to unmount. Trying MNT_DETACH " << fuse_path << " ...";
+ if (umount2(fuse_path.c_str(), UMOUNT_NOFOLLOW | MNT_DETACH) && errno != EINVAL &&
+ errno != ENOENT) {
+ PLOG(ERROR) << "Failed to unmount with MNT_DETACH " << fuse_path;
+ return -errno;
+ }
+ return android::OK;
+ }
+ return result;
}
} // namespace vold