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/model/EmulatedVolume.cpp b/model/EmulatedVolume.cpp
index c84fbb7..e6702c9 100644
--- a/model/EmulatedVolume.cpp
+++ b/model/EmulatedVolume.cpp
@@ -42,16 +42,17 @@
static const char* kFusePath = "/system/bin/sdcard";
-EmulatedVolume::EmulatedVolume(const std::string& rawPath)
+EmulatedVolume::EmulatedVolume(const std::string& rawPath, int userId)
: VolumeBase(Type::kEmulated), mFusePid(0) {
- setId("emulated");
+ setId(StringPrintf("emulated;%u", userId));
mRawPath = rawPath;
mLabel = "emulated";
}
-EmulatedVolume::EmulatedVolume(const std::string& rawPath, dev_t device, const std::string& fsUuid)
+EmulatedVolume::EmulatedVolume(const std::string& rawPath, dev_t device, const std::string& fsUuid,
+ int userId)
: VolumeBase(Type::kEmulated), mFusePid(0) {
- setId(StringPrintf("emulated:%u,%u", major(device), minor(device)));
+ setId(StringPrintf("emulated:%u,%u;%u", major(device), minor(device), userId));
mRawPath = rawPath;
mLabel = fsUuid;
}
@@ -90,17 +91,14 @@
LOG(INFO) << "Mounting emulated fuse volume";
android::base::unique_fd fd;
int user_id = getMountUserId();
- int result = MountUserFuse(user_id, label, &fd);
+ int result = MountUserFuse(user_id, getInternalPath(), label, &fd);
if (result != 0) {
PLOG(ERROR) << "Failed to mount emulated fuse volume";
return -result;
}
setFuseFd(std::move(fd));
-
- std::string pass_through_path(StringPrintf("/mnt/pass_through/%d/%s",
- user_id, label.c_str()));
- return BindMount(getInternalPath(), pass_through_path);
+ return 0;
}
if (!(mFusePid = fork())) {
@@ -163,18 +161,17 @@
if (getMountFlags() & MountFlags::kPrimary) {
label = "emulated";
}
- std::string path(StringPrintf("/mnt/user/%d/%s", getMountUserId(), label.c_str()));
- status_t result = ForceUnmount(path);
- if (result != OK) {
- // TODO(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.
- if (!umount2(path.c_str(), UMOUNT_NOFOLLOW | MNT_DETACH) || errno == EINVAL ||
- errno == ENOENT) {
- PLOG(INFO) << "ForceUnmount failed on emulated fuse volume";
- }
+
+ std::string fuse_path(StringPrintf("/mnt/user/%d/%s", getMountUserId(), label.c_str()));
+ std::string pass_through_path(
+ StringPrintf("/mnt/pass_through/%d/%s", getMountUserId(), label.c_str()));
+ if (UnmountUserFuse(pass_through_path, fuse_path) != OK) {
+ PLOG(INFO) << "UnmountUserFuse failed on emulated fuse volume";
+ return -errno;
}
- rmdir(path.c_str());
+ rmdir(fuse_path.c_str());
+ rmdir(pass_through_path.c_str());
setFuseFd(android::base::unique_fd());
return OK;
}
diff --git a/model/EmulatedVolume.h b/model/EmulatedVolume.h
index fddfe4e..8d4c490 100644
--- a/model/EmulatedVolume.h
+++ b/model/EmulatedVolume.h
@@ -37,8 +37,8 @@
*/
class EmulatedVolume : public VolumeBase {
public:
- explicit EmulatedVolume(const std::string& rawPath);
- EmulatedVolume(const std::string& rawPath, dev_t device, const std::string& fsUuid);
+ explicit EmulatedVolume(const std::string& rawPath, int userId);
+ EmulatedVolume(const std::string& rawPath, dev_t device, const std::string& fsUuid, int userId);
virtual ~EmulatedVolume();
protected:
diff --git a/model/PrivateVolume.cpp b/model/PrivateVolume.cpp
index de2a09f..5098e5d 100644
--- a/model/PrivateVolume.cpp
+++ b/model/PrivateVolume.cpp
@@ -155,12 +155,18 @@
return -EIO;
}
- // Create a new emulated volume stacked above us, it will automatically
- // be destroyed during unmount
+ auto vol_manager = VolumeManager::Instance();
std::string mediaPath(mPath + "/media");
- auto vol = std::shared_ptr<VolumeBase>(new EmulatedVolume(mediaPath, mRawDevice, mFsUuid));
- addVolume(vol);
- vol->create();
+
+ // Create a new emulated volume stacked above us for all added users, they will automatically
+ // be destroyed during unmount
+ for (userid_t user : vol_manager->getStartedUsers()) {
+ auto vol = std::shared_ptr<VolumeBase>(
+ new EmulatedVolume(mediaPath, mRawDevice, mFsUuid, user));
+ vol->setMountUserId(user);
+ addVolume(vol);
+ vol->create();
+ }
return OK;
}
diff --git a/model/PrivateVolume.h b/model/PrivateVolume.h
index cb8e75d..656172f 100644
--- a/model/PrivateVolume.h
+++ b/model/PrivateVolume.h
@@ -42,6 +42,8 @@
const std::string& getFsType() const { return mFsType; };
const std::string& getRawDevPath() const { return mRawDevPath; };
const std::string& getRawDmDevPath() const { return mDmDevPath; };
+ const std::string& getFsUuid() const { return mFsUuid; };
+ dev_t getRawDevice() const { return mRawDevice; };
protected:
status_t doCreate() override;
diff --git a/model/PublicVolume.cpp b/model/PublicVolume.cpp
index 7b8a21f..403e85c 100644
--- a/model/PublicVolume.cpp
+++ b/model/PublicVolume.cpp
@@ -170,22 +170,18 @@
dev_t before = GetDevice(mFuseFull);
bool isFuse = base::GetBoolProperty(kPropFuseSnapshot, false);
-
if (isFuse) {
LOG(INFO) << "Mounting public fuse volume";
android::base::unique_fd fd;
int user_id = getMountUserId();
- int result = MountUserFuse(user_id, stableName, &fd);
+ int result = MountUserFuse(user_id, getInternalPath(), stableName, &fd);
if (result != 0) {
LOG(ERROR) << "Failed to mount public fuse volume";
return -result;
}
setFuseFd(std::move(fd));
-
- std::string pass_through_path(StringPrintf("/mnt/pass_through/%d/%s",
- user_id, stableName.c_str()));
- return BindMount(getInternalPath(), pass_through_path);
+ return OK;
}
if (!(mFusePid = fork())) {
@@ -258,19 +254,24 @@
stableName = mFsUuid;
}
- std::string path(StringPrintf("/mnt/user/%d/%s", getMountUserId(), stableName.c_str()));
- status_t result = ForceUnmount(path);
- if (result != OK) {
- // TODO(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.
- if (!umount2(path.c_str(), UMOUNT_NOFOLLOW | MNT_DETACH) || errno == EINVAL ||
- errno == ENOENT) {
- PLOG(INFO) << "ForceUnmount failed on public fuse volume";
- }
+ std::string fuse_path(
+ StringPrintf("/mnt/user/%d/%s", getMountUserId(), stableName.c_str()));
+ std::string pass_through_path(
+ StringPrintf("/mnt/pass_through/%d/%s", getMountUserId(), stableName.c_str()));
+ if (UnmountUserFuse(pass_through_path, fuse_path) != OK) {
+ PLOG(INFO) << "UnmountUserFuse failed on public fuse volume";
+ return -errno;
}
+ ForceUnmount(kAsecPath);
+ ForceUnmount(mRawPath);
- rmdir(path.c_str());
+ rmdir(fuse_path.c_str());
+ rmdir(pass_through_path.c_str());
+ rmdir(mRawPath.c_str());
+ mRawPath.clear();
+
setFuseFd(android::base::unique_fd());
+
return OK;
}
diff --git a/model/VolumeBase.cpp b/model/VolumeBase.cpp
index 08da8f6..ae45f7e 100644
--- a/model/VolumeBase.cpp
+++ b/model/VolumeBase.cpp
@@ -186,7 +186,8 @@
auto listener = getListener();
if (listener) {
- listener->onVolumeCreated(getId(), static_cast<int32_t>(mType), mDiskId, mPartGuid);
+ listener->onVolumeCreated(getId(), static_cast<int32_t>(mType), mDiskId, mPartGuid,
+ mMountUserId);
}
setState(State::kUnmounted);
diff --git a/model/VolumeBase.h b/model/VolumeBase.h
index 5deecdb..82b0ae0 100644
--- a/model/VolumeBase.h
+++ b/model/VolumeBase.h
@@ -88,6 +88,7 @@
const std::string& getPath() const { return mPath; }
const std::string& getInternalPath() const { return mInternalPath; }
const android::base::unique_fd& getFuseFd() const { return mFuseFd; }
+ const std::list<std::shared_ptr<VolumeBase>>& getVolumes() const { return mVolumes; }
status_t setDiskId(const std::string& diskId);
status_t setPartGuid(const std::string& partGuid);