platform_system_vold/model/EmulatedVolume.cpp
Ricky Wai 5f2a9fee66 Always unmount data and obb directory that mounted
Otherwise, when system removes user's volume, it will hang
as there are mounts (obb and data mounts) still remain mounted in system.

Bug: 187122943
Test: atest UserLifecycleTests#managedProfileUnlock_stopped, it's not blocked anymore

Change-Id: Ic37985f98e6cbfe4fa38b981d3332c4dfc40c5b8
2021-05-05 14:44:16 +00:00

442 lines
16 KiB
C++

/*
* 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 "AppFuseUtil.h"
#include "Utils.h"
#include "VolumeManager.h"
#include <android-base/logging.h>
#include <android-base/properties.h>
#include <android-base/scopeguard.h>
#include <android-base/stringprintf.h>
#include <cutils/fs.h>
#include <private/android_filesystem_config.h>
#include <utils/Timers.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <sys/types.h>
#include <sys/wait.h>
using android::base::StringPrintf;
namespace android {
namespace vold {
static const char* kSdcardFsPath = "/system/bin/sdcard";
EmulatedVolume::EmulatedVolume(const std::string& rawPath, int userId)
: VolumeBase(Type::kEmulated) {
setId(StringPrintf("emulated;%u", userId));
mRawPath = rawPath;
mLabel = "emulated";
mFuseMounted = false;
mUseSdcardFs = IsSdcardfsUsed();
mAppDataIsolationEnabled = base::GetBoolProperty(kVoldAppDataIsolationEnabled, false);
}
EmulatedVolume::EmulatedVolume(const std::string& rawPath, dev_t device, const std::string& fsUuid,
int userId)
: VolumeBase(Type::kEmulated) {
setId(StringPrintf("emulated:%u,%u;%u", major(device), minor(device), userId));
mRawPath = rawPath;
mLabel = fsUuid;
mFuseMounted = false;
mUseSdcardFs = IsSdcardfsUsed();
mAppDataIsolationEnabled = base::GetBoolProperty(kVoldAppDataIsolationEnabled, false);
}
EmulatedVolume::~EmulatedVolume() {}
std::string EmulatedVolume::getLabel() {
// We could have migrated storage to an adopted private volume, so always
// call primary storage "emulated" to avoid media rescans.
if (getMountFlags() & MountFlags::kPrimary) {
return "emulated";
} else {
return mLabel;
}
}
// Creates a bind mount from source to target
static status_t doFuseBindMount(const std::string& source, const std::string& target,
std::list<std::string>& pathsToUnmount) {
LOG(INFO) << "Bind mounting " << source << " on " << target;
auto status = BindMount(source, target);
if (status != OK) {
return status;
}
LOG(INFO) << "Bind mounted " << source << " on " << target;
pathsToUnmount.push_front(target);
return OK;
}
status_t EmulatedVolume::mountFuseBindMounts() {
std::string androidSource;
std::string label = getLabel();
int userId = getMountUserId();
std::list<std::string> pathsToUnmount;
auto unmounter = [&]() {
LOG(INFO) << "mountFuseBindMounts() unmount scope_guard running";
for (const auto& path : pathsToUnmount) {
LOG(INFO) << "Unmounting " << path;
auto status = UnmountTree(path);
if (status != OK) {
LOG(INFO) << "Failed to unmount " << path;
} else {
LOG(INFO) << "Unmounted " << path;
}
}
};
auto unmount_guard = android::base::make_scope_guard(unmounter);
if (mUseSdcardFs) {
androidSource = StringPrintf("/mnt/runtime/default/%s/%d/Android", label.c_str(), userId);
} else {
androidSource = StringPrintf("/%s/%d/Android", mRawPath.c_str(), userId);
}
status_t status = OK;
// Zygote will unmount these dirs if app data isolation is enabled, so apps
// cannot access these dirs directly.
std::string androidDataSource = StringPrintf("%s/data", androidSource.c_str());
std::string androidDataTarget(
StringPrintf("/mnt/user/%d/%s/%d/Android/data", userId, label.c_str(), userId));
status = doFuseBindMount(androidDataSource, androidDataTarget, pathsToUnmount);
if (status != OK) {
return status;
}
std::string androidObbSource = StringPrintf("%s/obb", androidSource.c_str());
std::string androidObbTarget(
StringPrintf("/mnt/user/%d/%s/%d/Android/obb", userId, label.c_str(), userId));
status = doFuseBindMount(androidObbSource, androidObbTarget, pathsToUnmount);
if (status != OK) {
return status;
}
// Installers get the same view as all other apps, with the sole exception that the
// OBB dirs (Android/obb) are writable to them. On sdcardfs devices, this requires
// a special bind mount, since app-private and OBB dirs share the same GID, but we
// only want to give access to the latter.
if (mUseSdcardFs) {
std::string obbSource(StringPrintf("/mnt/runtime/write/%s/%d/Android/obb",
label.c_str(), userId));
std::string obbInstallerTarget(StringPrintf("/mnt/installer/%d/%s/%d/Android/obb",
userId, label.c_str(), userId));
status = doFuseBindMount(obbSource, obbInstallerTarget, pathsToUnmount);
if (status != OK) {
return status;
}
}
unmount_guard.Disable();
return OK;
}
status_t EmulatedVolume::unmountFuseBindMounts() {
std::string label = getLabel();
int userId = getMountUserId();
if (mUseSdcardFs || mAppDataIsolationEnabled) {
std::string installerTarget(
StringPrintf("/mnt/installer/%d/%s/%d/Android/obb", userId, label.c_str(), userId));
LOG(INFO) << "Unmounting " << installerTarget;
auto status = UnmountTree(installerTarget);
if (status != OK) {
LOG(ERROR) << "Failed to unmount " << installerTarget;
// Intentional continue to try to unmount the other bind mount
}
}
if (mAppDataIsolationEnabled) {
std::string obbTarget( StringPrintf("/mnt/androidwritable/%d/%s/%d/Android/obb",
userId, label.c_str(), userId));
LOG(INFO) << "Unmounting " << obbTarget;
auto status = UnmountTree(obbTarget);
if (status != OK) {
LOG(ERROR) << "Failed to unmount " << obbTarget;
// Intentional continue to try to unmount the other bind mount
}
std::string dataTarget(StringPrintf("/mnt/androidwritable/%d/%s/%d/Android/data",
userId, label.c_str(), userId));
LOG(INFO) << "Unmounting " << dataTarget;
status = UnmountTree(dataTarget);
if (status != OK) {
LOG(ERROR) << "Failed to unmount " << dataTarget;
// Intentional continue to try to unmount the other bind mount
}
}
// When app data isolation is enabled, kill all apps that obb/ is mounted, otherwise we should
// umount the whole Android/ dir.
if (mAppDataIsolationEnabled) {
std::string appObbDir(StringPrintf("%s/%d/Android/obb", getPath().c_str(), userId));
// Here we assume obb/data dirs is mounted as tmpfs, then it must be caused by
// app data isolation.
KillProcessesWithTmpfsMountPrefix(appObbDir);
}
// Always unmount data and obb dirs as they are mounted to lowerfs for speeding up access.
std::string androidDataTarget(
StringPrintf("/mnt/user/%d/%s/%d/Android/data", userId, label.c_str(), userId));
LOG(INFO) << "Unmounting " << androidDataTarget;
auto status = UnmountTree(androidDataTarget);
if (status != OK) {
return status;
}
LOG(INFO) << "Unmounted " << androidDataTarget;
std::string androidObbTarget(
StringPrintf("/mnt/user/%d/%s/%d/Android/obb", userId, label.c_str(), userId));
LOG(INFO) << "Unmounting " << androidObbTarget;
status = UnmountTree(androidObbTarget);
if (status != OK) {
return status;
}
LOG(INFO) << "Unmounted " << androidObbTarget;
return OK;
}
status_t EmulatedVolume::unmountSdcardFs() {
if (!mUseSdcardFs || getMountUserId() != 0) {
// For sdcardfs, only unmount for user 0, since user 0 will always be running
// and the paths don't change for different users.
return OK;
}
ForceUnmount(mSdcardFsDefault);
ForceUnmount(mSdcardFsRead);
ForceUnmount(mSdcardFsWrite);
ForceUnmount(mSdcardFsFull);
rmdir(mSdcardFsDefault.c_str());
rmdir(mSdcardFsRead.c_str());
rmdir(mSdcardFsWrite.c_str());
rmdir(mSdcardFsFull.c_str());
mSdcardFsDefault.clear();
mSdcardFsRead.clear();
mSdcardFsWrite.clear();
mSdcardFsFull.clear();
return OK;
}
status_t EmulatedVolume::doMount() {
std::string label = getLabel();
bool isVisible = getMountFlags() & MountFlags::kVisible;
mSdcardFsDefault = StringPrintf("/mnt/runtime/default/%s", label.c_str());
mSdcardFsRead = StringPrintf("/mnt/runtime/read/%s", label.c_str());
mSdcardFsWrite = StringPrintf("/mnt/runtime/write/%s", label.c_str());
mSdcardFsFull = StringPrintf("/mnt/runtime/full/%s", label.c_str());
setInternalPath(mRawPath);
setPath(StringPrintf("/storage/%s", label.c_str()));
if (fs_prepare_dir(mSdcardFsDefault.c_str(), 0700, AID_ROOT, AID_ROOT) ||
fs_prepare_dir(mSdcardFsRead.c_str(), 0700, AID_ROOT, AID_ROOT) ||
fs_prepare_dir(mSdcardFsWrite.c_str(), 0700, AID_ROOT, AID_ROOT) ||
fs_prepare_dir(mSdcardFsFull.c_str(), 0700, AID_ROOT, AID_ROOT)) {
PLOG(ERROR) << getId() << " failed to create mount points";
return -errno;
}
dev_t before = GetDevice(mSdcardFsFull);
// Mount sdcardfs regardless of FUSE, since we need it to bind-mount on top of the
// FUSE volume for various reasons.
if (mUseSdcardFs && getMountUserId() == 0) {
LOG(INFO) << "Executing sdcardfs";
int sdcardFsPid;
if (!(sdcardFsPid = fork())) {
// clang-format off
if (execl(kSdcardFsPath, kSdcardFsPath,
"-u", "1023", // AID_MEDIA_RW
"-g", "1023", // AID_MEDIA_RW
"-m",
"-w",
"-G",
"-i",
"-o",
mRawPath.c_str(),
label.c_str(),
NULL)) {
// clang-format on
PLOG(ERROR) << "Failed to exec";
}
LOG(ERROR) << "sdcardfs exiting";
_exit(1);
}
if (sdcardFsPid == -1) {
PLOG(ERROR) << getId() << " failed to fork";
return -errno;
}
nsecs_t start = systemTime(SYSTEM_TIME_BOOTTIME);
while (before == GetDevice(mSdcardFsFull)) {
LOG(DEBUG) << "Waiting for sdcardfs to spin up...";
usleep(50000); // 50ms
nsecs_t now = systemTime(SYSTEM_TIME_BOOTTIME);
if (nanoseconds_to_milliseconds(now - start) > 5000) {
LOG(WARNING) << "Timed out while waiting for sdcardfs to spin up";
return -ETIMEDOUT;
}
}
/* sdcardfs will have exited already. The filesystem will still be running */
TEMP_FAILURE_RETRY(waitpid(sdcardFsPid, nullptr, 0));
sdcardFsPid = 0;
}
if (isVisible) {
// Make sure we unmount sdcardfs if we bail out with an error below
auto sdcardfs_unmounter = [&]() {
LOG(INFO) << "sdcardfs_unmounter scope_guard running";
unmountSdcardFs();
};
auto sdcardfs_guard = android::base::make_scope_guard(sdcardfs_unmounter);
LOG(INFO) << "Mounting emulated fuse volume";
android::base::unique_fd fd;
int user_id = getMountUserId();
auto volumeRoot = getRootPath();
// Make sure Android/ dirs exist for bind mounting
status_t res = PrepareAndroidDirs(volumeRoot);
if (res != OK) {
LOG(ERROR) << "Failed to prepare Android/ directories";
return res;
}
res = MountUserFuse(user_id, getInternalPath(), label, &fd);
if (res != 0) {
PLOG(ERROR) << "Failed to mount emulated fuse volume";
return res;
}
mFuseMounted = true;
auto fuse_unmounter = [&]() {
LOG(INFO) << "fuse_unmounter scope_guard running";
fd.reset();
if (UnmountUserFuse(user_id, getInternalPath(), label) != OK) {
PLOG(INFO) << "UnmountUserFuse failed on emulated fuse volume";
}
mFuseMounted = false;
};
auto fuse_guard = android::base::make_scope_guard(fuse_unmounter);
auto callback = getMountCallback();
if (callback) {
bool is_ready = false;
callback->onVolumeChecking(std::move(fd), getPath(), getInternalPath(), &is_ready);
if (!is_ready) {
return -EIO;
}
}
// Only do the bind-mounts when we know for sure the FUSE daemon can resolve the path.
res = mountFuseBindMounts();
if (res != OK) {
return res;
}
ConfigureReadAheadForFuse(GetFuseMountPathForUser(user_id, label), 256u);
// By default, FUSE has a max_dirty ratio of 1%. This means that out of
// all dirty pages in the system, only 1% is allowed to belong to any
// FUSE filesystem. The reason this is in place is that FUSE
// filesystems shouldn't be trusted by default; a FUSE filesystem could
// take up say 100% of dirty pages, and subsequently refuse to write
// them back to storage. The kernel will then apply rate-limiting, and
// block other tasks from writing. For this particular FUSE filesystem
// however, we trust the implementation, because it is a part of the
// Android platform. So use the default ratio of 100%.
//
// The reason we're setting this is that there's a suspicion that the
// kernel starts rate-limiting the FUSE filesystem under extreme
// memory pressure scenarios. While the kernel will only rate limit if
// the writeback can't keep up with the write rate, under extreme
// memory pressure the write rate may dip as well, in which case FUSE
// writes to a 1% max_ratio filesystem are throttled to an extreme amount.
//
// To prevent this, just give FUSE 40% max_ratio, meaning it can take
// up to 40% of all dirty pages in the system.
ConfigureMaxDirtyRatioForFuse(GetFuseMountPathForUser(user_id, label), 40u);
// All mounts where successful, disable scope guards
sdcardfs_guard.Disable();
fuse_guard.Disable();
}
return OK;
}
status_t EmulatedVolume::doUnmount() {
int userId = getMountUserId();
// Kill all processes using the filesystem before we unmount it. If we
// unmount the filesystem 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.
if (mFuseMounted) {
// For FUSE specifically, we have an emulated volume per user, so only kill
// processes using files from this particular user.
std::string user_path(StringPrintf("%s/%d", getPath().c_str(), getMountUserId()));
LOG(INFO) << "Killing all processes referencing " << user_path;
KillProcessesUsingPath(user_path);
} else {
KillProcessesUsingPath(getPath());
}
if (mFuseMounted) {
std::string label = getLabel();
// Ignoring unmount return status because we do want to try to unmount
// the rest cleanly.
unmountFuseBindMounts();
if (UnmountUserFuse(userId, getInternalPath(), label) != OK) {
PLOG(INFO) << "UnmountUserFuse failed on emulated fuse volume";
return -errno;
}
mFuseMounted = false;
}
return unmountSdcardFs();
}
std::string EmulatedVolume::getRootPath() const {
int user_id = getMountUserId();
std::string volumeRoot = StringPrintf("%s/%d", getInternalPath().c_str(), user_id);
return volumeRoot;
}
} // namespace vold
} // namespace android