Mount /dev/fuse on /mnt/user/<userid>/<volumeid>

Since system_server cannot mount devices by itself,
add a binder interface to vold that system_server
can call to initiate this mount when required.

BUG: 135341433
Test: manual
Test: atest --test-mapping packages/providers/MediaProvider
Test: ExternalStorageHostTest DownloadProviderTests

Change-Id: If4fd02a1f1a8d921a3f96783d8c73e085c5b7ca1
This commit is contained in:
Zim 2019-07-19 16:46:53 +01:00 committed by Nandana Dutt
parent 7cd6ceb835
commit 3623a212e3
10 changed files with 197 additions and 4 deletions

View file

@ -165,7 +165,7 @@ status_t ForceUnmount(const std::string& path) {
if (!umount2(cpath, UMOUNT_NOFOLLOW) || errno == EINVAL || errno == ENOENT) {
return OK;
}
PLOG(INFO) << "ForceUnmount failed";
return -errno;
}
@ -985,5 +985,50 @@ bool writeStringToFile(const std::string& payload, const std::string& filename)
return true;
}
int MountUserFuse(userid_t user_id, const std::string& relative_path, int* device_fd) {
std::string path(StringPrintf("/mnt/user/%d/%s", user_id, relative_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);
if (result != android::OK) {
PLOG(ERROR) << "Failed to unmount " << path;
return -1;
}
// Create directories.
result = android::vold::PrepareDir(path, 0700, AID_ROOT, AID_ROOT);
if (result != android::OK) {
PLOG(ERROR) << "Failed to prepare directory " << path;
return -1;
}
// Open device fd.
*device_fd = open("/dev/fuse", O_RDWR | O_CLOEXEC);
if (*device_fd == -1) {
PLOG(ERROR) << "Failed to open /dev/fuse";
return -1;
}
// Note: leaving out default_permissions since we don't want kernel to do lower filesystem
// permission checks before routing to FUSE daemon.
const auto opts = StringPrintf(
"fd=%i,"
"rootmode=40000,"
"allow_other,"
"user_id=0,group_id=0,",
*device_fd);
const int result_int =
TEMP_FAILURE_RETRY(mount("/dev/fuse", path.c_str(), "fuse",
MS_NOSUID | MS_NODEV | MS_NOEXEC | MS_NOATIME, opts.c_str()));
if (result_int != 0) {
PLOG(ERROR) << "Failed to mount " << path;
return -errno;
}
return 0;
}
} // namespace vold
} // namespace android

View file

@ -33,6 +33,8 @@ struct DIR;
namespace android {
namespace vold {
static const char* kPropFuse = "persist.sys.fuse";
/* SELinux contexts used depending on the block device type */
extern security_context_t sBlkidContext;
extern security_context_t sBlkidUntrustedContext;
@ -147,6 +149,9 @@ status_t WaitForFile(const char* filename, std::chrono::nanoseconds timeout);
bool FsyncDirectory(const std::string& dirname);
bool writeStringToFile(const std::string& payload, const std::string& filename);
int MountUserFuse(userid_t user_id, const std::string& relative_path, int* device_fd);
} // namespace vold
} // namespace android

View file

@ -326,7 +326,8 @@ binder::Status VoldNativeService::forgetPartition(const std::string& partGuid,
}
binder::Status VoldNativeService::mount(const std::string& volId, int32_t mountFlags,
int32_t mountUserId) {
int32_t mountUserId,
android::base::unique_fd* _aidl_return) {
ENFORCE_UID(AID_SYSTEM);
CHECK_ARGUMENT_ID(volId);
ACQUIRE_LOCK;
@ -343,6 +344,14 @@ binder::Status VoldNativeService::mount(const std::string& volId, int32_t mountF
if (res != OK) {
return translate(res);
}
_aidl_return->reset(vol->getDeviceFd());
if (_aidl_return->get() == -1) {
// Let's not return invalid fd since binder will not allow null fds. Instead give it a
// default value.
_aidl_return->reset(open("/dev/null", O_RDONLY | O_CLOEXEC));
}
if ((mountFlags & MOUNT_FLAG_PRIMARY) != 0) {
res = VolumeManager::Instance()->setPrimary(vol);
if (res != OK) {

View file

@ -52,7 +52,8 @@ class VoldNativeService : public BinderService<VoldNativeService>, public os::Bn
binder::Status partition(const std::string& diskId, int32_t partitionType, int32_t ratio);
binder::Status forgetPartition(const std::string& partGuid, const std::string& fsUuid);
binder::Status mount(const std::string& volId, int32_t mountFlags, int32_t mountUserId);
binder::Status mount(const std::string& volId, int32_t mountFlags, int32_t mountUserId,
android::base::unique_fd* _aidl_return);
binder::Status unmount(const std::string& volId);
binder::Status format(const std::string& volId, const std::string& fsType);
binder::Status benchmark(const std::string& volId,

View file

@ -31,6 +31,7 @@
#include <sys/wait.h>
#include <unistd.h>
#include <array>
#include <thread>
#include <linux/kdev_t.h>
@ -100,6 +101,22 @@ static const unsigned int kMajorBlockExperimentalMax = 254;
VolumeManager* VolumeManager::sInstance = NULL;
static void* symlinkPrimary(void* data) {
std::unique_ptr<std::pair<std::string, std::string>> linkInfo(
static_cast<std::pair<std::string, std::string>*>(data));
std::string* source = &linkInfo->first;
std::string* target = &linkInfo->second;
fs_prepare_dir(source->c_str(), 0755, AID_ROOT, AID_ROOT);
fs_prepare_dir(target->c_str(), 0755, AID_ROOT, AID_ROOT);
*target = *target + "/primary";
// Link source to target
LOG(DEBUG) << "Linking " << *source << " to " << *target;
Symlink(*source, *target);
return nullptr;
}
VolumeManager* VolumeManager::Instance() {
if (!sInstance) sInstance = new VolumeManager();
return sInstance;
@ -365,6 +382,21 @@ int VolumeManager::forgetPartition(const std::string& partGuid, const std::strin
}
int VolumeManager::linkPrimary(userid_t userId) {
bool isFuse = GetBoolProperty(android::vold::kPropFuse, false);
if (isFuse) {
// Here we have to touch /mnt/user/userid>/<volumeid> which was already mounted as part of
// the boot sequence, requiring waiting till a fuse handler is available. If we do this work
// in foreground we could hang the caller, i.e. system server, which needs to start the fuse
// handler. So do it in the background.
std::string source(
StringPrintf("/mnt/user/%d/%s/%d", userId, mPrimary->getId().c_str(), userId));
std::string target(StringPrintf("/mnt/user/%d/self", userId));
auto symlinkInfo = new std::pair<std::string, std::string>(source, target);
std::thread(symlinkPrimary, symlinkInfo).detach();
}
std::string source(mPrimary->getPath());
if (mPrimary->isEmulated()) {
source = StringPrintf("%s/%d", source.c_str(), userId);
@ -431,6 +463,10 @@ int VolumeManager::setPrimary(const std::shared_ptr<android::vold::VolumeBase>&
}
int VolumeManager::remountUid(uid_t uid, int32_t mountMode) {
if (GetBoolProperty(android::vold::kPropFuse, false)) {
// TODO(135341433): Implement fuse specific logic.
return 0;
}
std::string mode;
switch (mountMode) {
case VoldNativeService::REMOUNT_MODE_NONE:

View file

@ -40,7 +40,7 @@ interface IVold {
void partition(@utf8InCpp String diskId, int partitionType, int ratio);
void forgetPartition(@utf8InCpp String partGuid, @utf8InCpp String fsUuid);
void mount(@utf8InCpp String volId, int mountFlags, int mountUserId);
FileDescriptor mount(@utf8InCpp String volId, int mountFlags, int mountUserId);
void unmount(@utf8InCpp String volId);
void format(@utf8InCpp String volId, @utf8InCpp String fsType);
void benchmark(@utf8InCpp String volId, IVoldTaskListener listener);

View file

@ -15,10 +15,13 @@
*/
#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/stringprintf.h>
#include <cutils/fs.h>
#include <private/android_filesystem_config.h>
@ -81,7 +84,22 @@ status_t EmulatedVolume::doMount() {
dev_t before = GetDevice(mFuseFull);
bool isFuse = base::GetBoolProperty(kPropFuse, false);
if (isFuse) {
LOG(INFO) << "Mounting emulated fuse volume";
int fd = -1;
int result = MountUserFuse(getMountUserId(), label, &fd);
if (result != 0) {
PLOG(ERROR) << "Failed to mount emulated fuse volume";
return -result;
}
setDeviceFd(fd);
return OK;
}
if (!(mFusePid = fork())) {
LOG(INFO) << "Executing sdcardfs";
// clang-format off
if (execl(kFusePath, kFusePath,
"-u", "1023", // AID_MEDIA_RW
@ -131,6 +149,31 @@ status_t EmulatedVolume::doUnmount() {
// ENOTCONN until the unmount completes. This is an exotic and unusual
// error code and might cause broken behaviour in applications.
KillProcessesUsingPath(getPath());
bool isFuse = base::GetBoolProperty(kPropFuse, false);
if (isFuse) {
// We could have migrated storage to an adopted private volume, so always
// call primary storage "emulated" to avoid media rescans.
std::string label = mLabel;
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";
}
}
rmdir(path.c_str());
setDeviceFd(-1);
return OK;
}
ForceUnmount(mFuseDefault);
ForceUnmount(mFuseRead);
ForceUnmount(mFuseWrite);

View file

@ -15,6 +15,8 @@
*/
#include "PublicVolume.h"
#include "AppFuseUtil.h"
#include "Utils.h"
#include "VolumeManager.h"
#include "fs/Exfat.h"
@ -167,6 +169,20 @@ status_t PublicVolume::doMount() {
dev_t before = GetDevice(mFuseFull);
bool isFuse = base::GetBoolProperty(kPropFuse, false);
if (isFuse) {
LOG(INFO) << "Mounting public fuse volume";
int fd = -1;
int result = MountUserFuse(getMountUserId(), stableName, &fd);
if (result != 0) {
LOG(ERROR) << "Failed to mount public fuse volume";
return -result;
}
setDeviceFd(fd);
return OK;
}
if (!(mFusePid = fork())) {
if (getMountFlags() & MountFlags::kPrimary) {
// clang-format off
@ -229,6 +245,30 @@ status_t PublicVolume::doUnmount() {
// error code and might cause broken behaviour in applications.
KillProcessesUsingPath(getPath());
bool isFuse = base::GetBoolProperty(kPropFuse, false);
if (isFuse) {
// Use UUID as stable name, if available
std::string stableName = getId();
if (!mFsUuid.empty()) {
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";
}
}
rmdir(path.c_str());
setDeviceFd(-1);
return OK;
}
ForceUnmount(kAsecPath);
ForceUnmount(mFuseDefault);

View file

@ -143,6 +143,16 @@ status_t VolumeBase::setInternalPath(const std::string& internalPath) {
return OK;
}
status_t VolumeBase::setDeviceFd(int deviceFd) {
if ((mState != State::kChecking)) {
LOG(WARNING) << getId() << " device fd change requires state checking";
return -EBUSY;
}
mDeviceFd.reset(deviceFd);
return OK;
}
android::sp<android::os::IVoldListener> VolumeBase::getListener() const {
if (mSilent) {
return nullptr;

View file

@ -87,6 +87,7 @@ class VolumeBase {
State getState() const { return mState; }
const std::string& getPath() const { return mPath; }
const std::string& getInternalPath() const { return mInternalPath; }
int getDeviceFd() const { return dup(mDeviceFd.get()); }
status_t setDiskId(const std::string& diskId);
status_t setPartGuid(const std::string& partGuid);
@ -121,6 +122,7 @@ class VolumeBase {
status_t setId(const std::string& id);
status_t setPath(const std::string& path);
status_t setInternalPath(const std::string& internalPath);
status_t setDeviceFd(int deviceFd);
android::sp<android::os::IVoldListener> getListener() const;
@ -147,6 +149,8 @@ class VolumeBase {
std::string mInternalPath;
/* Flag indicating that volume should emit no events */
bool mSilent;
/* The filedescriptor for the fuse device, if the volume uses fuse, or -1 otherwise */
android::base::unique_fd mDeviceFd;
/* Volumes stacked on top of this volume */
std::list<std::shared_ptr<VolumeBase>> mVolumes;