67b8c49530
Instead of blindly looping across 4096 possible devices, use readdir() to only look at valid devices. This speeds up destroyAll() from 40ms to 0.7ms. Add tracing information in several places. Test: external/chromium-trace/systrace.py -b 128768 sched freq am pm ss core_services binder_driver -a system_server,installd,vold Bug: 65634729, 65737446 Change-Id: If581de47fb55850c0fcd6e25bf33ed246e1b079d
829 lines
25 KiB
C++
829 lines
25 KiB
C++
/*
|
|
* Copyright (C) 2008 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.
|
|
*/
|
|
|
|
#define ATRACE_TAG ATRACE_TAG_PACKAGE_MANAGER
|
|
|
|
#include <dirent.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <fts.h>
|
|
#include <mntent.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/mount.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <sys/sysmacros.h>
|
|
#include <sys/wait.h>
|
|
#include <unistd.h>
|
|
|
|
#include <linux/kdev_t.h>
|
|
|
|
#define LOG_TAG "Vold"
|
|
|
|
#include <openssl/md5.h>
|
|
|
|
#include <android-base/logging.h>
|
|
#include <android-base/stringprintf.h>
|
|
#include <cutils/fs.h>
|
|
#include <cutils/log.h>
|
|
#include <utils/Trace.h>
|
|
|
|
#include <selinux/android.h>
|
|
|
|
#include <sysutils/NetlinkEvent.h>
|
|
|
|
#include <private/android_filesystem_config.h>
|
|
|
|
#include "model/EmulatedVolume.h"
|
|
#include "model/ObbVolume.h"
|
|
#include "VolumeManager.h"
|
|
#include "NetlinkManager.h"
|
|
#include "Loop.h"
|
|
#include "fs/Ext4.h"
|
|
#include "fs/Vfat.h"
|
|
#include "Utils.h"
|
|
#include "Devmapper.h"
|
|
#include "Process.h"
|
|
#include "VoldUtil.h"
|
|
#include "cryptfs.h"
|
|
|
|
using android::base::StringPrintf;
|
|
using android::base::unique_fd;
|
|
|
|
bool VolumeManager::shutting_down = false;
|
|
|
|
static const char* kPathUserMount = "/mnt/user";
|
|
static const char* kPathVirtualDisk = "/data/misc/vold/virtual_disk";
|
|
|
|
static const char* kPropVirtualDisk = "persist.sys.virtual_disk";
|
|
|
|
/* 512MiB is large enough for testing purposes */
|
|
static const unsigned int kSizeVirtualDisk = 536870912;
|
|
|
|
static const unsigned int kMajorBlockMmc = 179;
|
|
static const unsigned int kMajorBlockExperimentalMin = 240;
|
|
static const unsigned int kMajorBlockExperimentalMax = 254;
|
|
|
|
VolumeManager *VolumeManager::sInstance = NULL;
|
|
|
|
VolumeManager *VolumeManager::Instance() {
|
|
if (!sInstance)
|
|
sInstance = new VolumeManager();
|
|
return sInstance;
|
|
}
|
|
|
|
VolumeManager::VolumeManager() {
|
|
mDebug = false;
|
|
mNextObbId = 0;
|
|
}
|
|
|
|
VolumeManager::~VolumeManager() {
|
|
}
|
|
|
|
int VolumeManager::updateVirtualDisk() {
|
|
ATRACE_NAME("VolumeManager::updateVirtualDisk");
|
|
if (property_get_bool(kPropVirtualDisk, false)) {
|
|
if (access(kPathVirtualDisk, F_OK) != 0) {
|
|
Loop::createImageFile(kPathVirtualDisk, kSizeVirtualDisk / 512);
|
|
}
|
|
|
|
if (mVirtualDisk == nullptr) {
|
|
if (Loop::create(kPathVirtualDisk, mVirtualDiskPath) != 0) {
|
|
LOG(ERROR) << "Failed to create virtual disk";
|
|
return -1;
|
|
}
|
|
|
|
struct stat buf;
|
|
if (stat(mVirtualDiskPath.c_str(), &buf) < 0) {
|
|
PLOG(ERROR) << "Failed to stat " << mVirtualDiskPath;
|
|
return -1;
|
|
}
|
|
|
|
auto disk = new android::vold::Disk("virtual", buf.st_rdev, "virtual",
|
|
android::vold::Disk::Flags::kAdoptable | android::vold::Disk::Flags::kSd);
|
|
disk->create();
|
|
mVirtualDisk = std::shared_ptr<android::vold::Disk>(disk);
|
|
mDisks.push_back(mVirtualDisk);
|
|
}
|
|
} else {
|
|
if (mVirtualDisk != nullptr) {
|
|
dev_t device = mVirtualDisk->getDevice();
|
|
|
|
auto i = mDisks.begin();
|
|
while (i != mDisks.end()) {
|
|
if ((*i)->getDevice() == device) {
|
|
(*i)->destroy();
|
|
i = mDisks.erase(i);
|
|
} else {
|
|
++i;
|
|
}
|
|
}
|
|
|
|
Loop::destroyByDevice(mVirtualDiskPath.c_str());
|
|
mVirtualDisk = nullptr;
|
|
}
|
|
|
|
if (access(kPathVirtualDisk, F_OK) == 0) {
|
|
unlink(kPathVirtualDisk);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int VolumeManager::setDebug(bool enable) {
|
|
mDebug = enable;
|
|
return 0;
|
|
}
|
|
|
|
int VolumeManager::start() {
|
|
ATRACE_NAME("VolumeManager::start");
|
|
|
|
// Always start from a clean slate by unmounting everything in
|
|
// 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);
|
|
mInternalEmulated = std::shared_ptr<android::vold::VolumeBase>(
|
|
new android::vold::EmulatedVolume("/data/media"));
|
|
mInternalEmulated->create();
|
|
|
|
// Consider creating a virtual disk
|
|
updateVirtualDisk();
|
|
|
|
return 0;
|
|
}
|
|
|
|
int VolumeManager::stop() {
|
|
CHECK(mInternalEmulated != nullptr);
|
|
mInternalEmulated->destroy();
|
|
mInternalEmulated = nullptr;
|
|
return 0;
|
|
}
|
|
|
|
void VolumeManager::handleBlockEvent(NetlinkEvent *evt) {
|
|
std::lock_guard<std::mutex> lock(mLock);
|
|
|
|
if (mDebug) {
|
|
LOG(VERBOSE) << "----------------";
|
|
LOG(VERBOSE) << "handleBlockEvent with action " << (int) evt->getAction();
|
|
evt->dump();
|
|
}
|
|
|
|
std::string eventPath(evt->findParam("DEVPATH")?evt->findParam("DEVPATH"):"");
|
|
std::string devType(evt->findParam("DEVTYPE")?evt->findParam("DEVTYPE"):"");
|
|
|
|
if (devType != "disk") return;
|
|
|
|
int major = atoi(evt->findParam("MAJOR"));
|
|
int minor = atoi(evt->findParam("MINOR"));
|
|
dev_t device = makedev(major, minor);
|
|
|
|
switch (evt->getAction()) {
|
|
case NetlinkEvent::Action::kAdd: {
|
|
for (const auto& source : mDiskSources) {
|
|
if (source->matches(eventPath)) {
|
|
// For now, assume that MMC and virtio-blk (the latter is
|
|
// emulator-specific; see Disk.cpp for details) devices are SD,
|
|
// and that everything else is USB
|
|
int flags = source->getFlags();
|
|
if (major == kMajorBlockMmc
|
|
|| (android::vold::IsRunningInEmulator()
|
|
&& major >= (int) kMajorBlockExperimentalMin
|
|
&& major <= (int) kMajorBlockExperimentalMax)) {
|
|
flags |= android::vold::Disk::Flags::kSd;
|
|
} else {
|
|
flags |= android::vold::Disk::Flags::kUsb;
|
|
}
|
|
|
|
auto disk = new android::vold::Disk(eventPath, device,
|
|
source->getNickname(), flags);
|
|
disk->create();
|
|
mDisks.push_back(std::shared_ptr<android::vold::Disk>(disk));
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case NetlinkEvent::Action::kChange: {
|
|
LOG(DEBUG) << "Disk at " << major << ":" << minor << " changed";
|
|
for (const auto& disk : mDisks) {
|
|
if (disk->getDevice() == device) {
|
|
disk->readMetadata();
|
|
disk->readPartitions();
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case NetlinkEvent::Action::kRemove: {
|
|
auto i = mDisks.begin();
|
|
while (i != mDisks.end()) {
|
|
if ((*i)->getDevice() == device) {
|
|
(*i)->destroy();
|
|
i = mDisks.erase(i);
|
|
} else {
|
|
++i;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
default: {
|
|
LOG(WARNING) << "Unexpected block event action " << (int) evt->getAction();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void VolumeManager::addDiskSource(const std::shared_ptr<DiskSource>& diskSource) {
|
|
std::lock_guard<std::mutex> lock(mLock);
|
|
mDiskSources.push_back(diskSource);
|
|
}
|
|
|
|
std::shared_ptr<android::vold::Disk> VolumeManager::findDisk(const std::string& id) {
|
|
for (auto disk : mDisks) {
|
|
if (disk->getId() == id) {
|
|
return disk;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
std::shared_ptr<android::vold::VolumeBase> VolumeManager::findVolume(const std::string& id) {
|
|
// Vold could receive "mount" after "shutdown" command in the extreme case.
|
|
// If this happens, mInternalEmulated will equal nullptr and
|
|
// we need to deal with it in order to avoid null pointer crash.
|
|
if (mInternalEmulated != nullptr && mInternalEmulated->getId() == id) {
|
|
return mInternalEmulated;
|
|
}
|
|
for (const auto& disk : mDisks) {
|
|
auto vol = disk->findVolume(id);
|
|
if (vol != nullptr) {
|
|
return vol;
|
|
}
|
|
}
|
|
for (const auto& vol : mObbVolumes) {
|
|
if (vol->getId() == id) {
|
|
return vol;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void VolumeManager::listVolumes(android::vold::VolumeBase::Type type,
|
|
std::list<std::string>& list) {
|
|
list.clear();
|
|
for (const auto& disk : mDisks) {
|
|
disk->listVolumes(type, list);
|
|
}
|
|
}
|
|
|
|
int VolumeManager::forgetPartition(const std::string& partGuid) {
|
|
std::string normalizedGuid;
|
|
if (android::vold::NormalizeHex(partGuid, normalizedGuid)) {
|
|
LOG(WARNING) << "Invalid GUID " << partGuid;
|
|
return -1;
|
|
}
|
|
|
|
std::string keyPath = android::vold::BuildKeyPath(normalizedGuid);
|
|
if (unlink(keyPath.c_str()) != 0) {
|
|
LOG(ERROR) << "Failed to unlink " << keyPath;
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int VolumeManager::linkPrimary(userid_t userId) {
|
|
std::string source(mPrimary->getPath());
|
|
if (mPrimary->getType() == android::vold::VolumeBase::Type::kEmulated) {
|
|
source = StringPrintf("%s/%d", source.c_str(), userId);
|
|
fs_prepare_dir(source.c_str(), 0755, AID_ROOT, AID_ROOT);
|
|
}
|
|
|
|
std::string target(StringPrintf("/mnt/user/%d/primary", userId));
|
|
if (TEMP_FAILURE_RETRY(unlink(target.c_str()))) {
|
|
if (errno != ENOENT) {
|
|
SLOGW("Failed to unlink %s: %s", target.c_str(), strerror(errno));
|
|
}
|
|
}
|
|
LOG(DEBUG) << "Linking " << source << " to " << target;
|
|
if (TEMP_FAILURE_RETRY(symlink(source.c_str(), target.c_str()))) {
|
|
SLOGW("Failed to link %s to %s: %s", source.c_str(), target.c_str(),
|
|
strerror(errno));
|
|
return -errno;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int VolumeManager::onUserAdded(userid_t userId, int userSerialNumber) {
|
|
mAddedUsers[userId] = userSerialNumber;
|
|
return 0;
|
|
}
|
|
|
|
int VolumeManager::onUserRemoved(userid_t userId) {
|
|
mAddedUsers.erase(userId);
|
|
return 0;
|
|
}
|
|
|
|
int VolumeManager::onUserStarted(userid_t userId) {
|
|
// Note that sometimes the system will spin up processes from Zygote
|
|
// before actually starting the user, so we're okay if Zygote
|
|
// already created this directory.
|
|
std::string path(StringPrintf("%s/%d", kPathUserMount, userId));
|
|
fs_prepare_dir(path.c_str(), 0755, AID_ROOT, AID_ROOT);
|
|
|
|
mStartedUsers.insert(userId);
|
|
if (mPrimary) {
|
|
linkPrimary(userId);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int VolumeManager::onUserStopped(userid_t userId) {
|
|
mStartedUsers.erase(userId);
|
|
return 0;
|
|
}
|
|
|
|
int VolumeManager::setPrimary(const std::shared_ptr<android::vold::VolumeBase>& vol) {
|
|
mPrimary = vol;
|
|
for (userid_t userId : mStartedUsers) {
|
|
linkPrimary(userId);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int unmount_tree(const char* path) {
|
|
size_t path_len = strlen(path);
|
|
|
|
FILE* fp = setmntent("/proc/mounts", "r");
|
|
if (fp == NULL) {
|
|
ALOGE("Error opening /proc/mounts: %s", strerror(errno));
|
|
return -errno;
|
|
}
|
|
|
|
// Some volumes can be stacked on each other, so force unmount in
|
|
// reverse order to give us the best chance of success.
|
|
std::list<std::string> toUnmount;
|
|
mntent* mentry;
|
|
while ((mentry = getmntent(fp)) != NULL) {
|
|
if (strncmp(mentry->mnt_dir, path, path_len) == 0) {
|
|
toUnmount.push_front(std::string(mentry->mnt_dir));
|
|
}
|
|
}
|
|
endmntent(fp);
|
|
|
|
for (const auto& path : toUnmount) {
|
|
if (umount2(path.c_str(), MNT_DETACH)) {
|
|
ALOGW("Failed to unmount %s: %s", path.c_str(), strerror(errno));
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int VolumeManager::remountUid(uid_t uid, const std::string& mode) {
|
|
LOG(DEBUG) << "Remounting " << uid << " as mode " << mode;
|
|
|
|
DIR* dir;
|
|
struct dirent* de;
|
|
char rootName[PATH_MAX];
|
|
char pidName[PATH_MAX];
|
|
int pidFd;
|
|
int nsFd;
|
|
struct stat sb;
|
|
pid_t child;
|
|
|
|
if (!(dir = opendir("/proc"))) {
|
|
PLOG(ERROR) << "Failed to opendir";
|
|
return -1;
|
|
}
|
|
|
|
// Figure out root namespace to compare against below
|
|
if (android::vold::SaneReadLinkAt(dirfd(dir), "1/ns/mnt", rootName, PATH_MAX) == -1) {
|
|
PLOG(ERROR) << "Failed to readlink";
|
|
closedir(dir);
|
|
return -1;
|
|
}
|
|
|
|
// Poke through all running PIDs look for apps running as UID
|
|
while ((de = readdir(dir))) {
|
|
pidFd = -1;
|
|
nsFd = -1;
|
|
|
|
pidFd = openat(dirfd(dir), de->d_name, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
|
|
if (pidFd < 0) {
|
|
goto next;
|
|
}
|
|
if (fstat(pidFd, &sb) != 0) {
|
|
PLOG(WARNING) << "Failed to stat " << de->d_name;
|
|
goto next;
|
|
}
|
|
if (sb.st_uid != uid) {
|
|
goto next;
|
|
}
|
|
|
|
// Matches so far, but refuse to touch if in root namespace
|
|
LOG(DEBUG) << "Found matching PID " << de->d_name;
|
|
if (android::vold::SaneReadLinkAt(pidFd, "ns/mnt", pidName, PATH_MAX) == -1) {
|
|
PLOG(WARNING) << "Failed to read namespace for " << de->d_name;
|
|
goto next;
|
|
}
|
|
if (!strcmp(rootName, pidName)) {
|
|
LOG(WARNING) << "Skipping due to root namespace";
|
|
goto next;
|
|
}
|
|
|
|
// We purposefully leave the namespace open across the fork
|
|
nsFd = openat(pidFd, "ns/mnt", O_RDONLY); // not O_CLOEXEC
|
|
if (nsFd < 0) {
|
|
PLOG(WARNING) << "Failed to open namespace for " << de->d_name;
|
|
goto next;
|
|
}
|
|
|
|
if (!(child = fork())) {
|
|
if (setns(nsFd, CLONE_NEWNS) != 0) {
|
|
PLOG(ERROR) << "Failed to setns for " << de->d_name;
|
|
_exit(1);
|
|
}
|
|
|
|
unmount_tree("/storage");
|
|
|
|
std::string storageSource;
|
|
if (mode == "default") {
|
|
storageSource = "/mnt/runtime/default";
|
|
} else if (mode == "read") {
|
|
storageSource = "/mnt/runtime/read";
|
|
} else if (mode == "write") {
|
|
storageSource = "/mnt/runtime/write";
|
|
} else {
|
|
// Sane default of no storage visible
|
|
_exit(0);
|
|
}
|
|
if (TEMP_FAILURE_RETRY(mount(storageSource.c_str(), "/storage",
|
|
NULL, MS_BIND | MS_REC, NULL)) == -1) {
|
|
PLOG(ERROR) << "Failed to mount " << storageSource << " for "
|
|
<< de->d_name;
|
|
_exit(1);
|
|
}
|
|
if (TEMP_FAILURE_RETRY(mount(NULL, "/storage", NULL,
|
|
MS_REC | MS_SLAVE, NULL)) == -1) {
|
|
PLOG(ERROR) << "Failed to set MS_SLAVE to /storage for "
|
|
<< de->d_name;
|
|
_exit(1);
|
|
}
|
|
|
|
// Mount user-specific symlink helper into place
|
|
userid_t user_id = multiuser_get_user_id(uid);
|
|
std::string userSource(StringPrintf("/mnt/user/%d", user_id));
|
|
if (TEMP_FAILURE_RETRY(mount(userSource.c_str(), "/storage/self",
|
|
NULL, MS_BIND, NULL)) == -1) {
|
|
PLOG(ERROR) << "Failed to mount " << userSource << " for "
|
|
<< de->d_name;
|
|
_exit(1);
|
|
}
|
|
|
|
_exit(0);
|
|
}
|
|
|
|
if (child == -1) {
|
|
PLOG(ERROR) << "Failed to fork";
|
|
goto next;
|
|
} else {
|
|
TEMP_FAILURE_RETRY(waitpid(child, nullptr, 0));
|
|
}
|
|
|
|
next:
|
|
close(nsFd);
|
|
close(pidFd);
|
|
}
|
|
closedir(dir);
|
|
return 0;
|
|
}
|
|
|
|
int VolumeManager::reset() {
|
|
// Tear down all existing disks/volumes and start from a blank slate so
|
|
// newly connected framework hears all events.
|
|
if (mInternalEmulated != nullptr) {
|
|
mInternalEmulated->destroy();
|
|
mInternalEmulated->create();
|
|
}
|
|
for (const auto& disk : mDisks) {
|
|
disk->destroy();
|
|
disk->create();
|
|
}
|
|
updateVirtualDisk();
|
|
mAddedUsers.clear();
|
|
mStartedUsers.clear();
|
|
return 0;
|
|
}
|
|
|
|
// Can be called twice (sequentially) during shutdown. should be safe for that.
|
|
int VolumeManager::shutdown() {
|
|
if (mInternalEmulated == nullptr) {
|
|
return 0; // already shutdown
|
|
}
|
|
shutting_down = true;
|
|
mInternalEmulated->destroy();
|
|
mInternalEmulated = nullptr;
|
|
for (const auto& disk : mDisks) {
|
|
disk->destroy();
|
|
}
|
|
mDisks.clear();
|
|
shutting_down = false;
|
|
return 0;
|
|
}
|
|
|
|
int VolumeManager::unmountAll() {
|
|
std::lock_guard<std::mutex> lock(mLock);
|
|
ATRACE_NAME("VolumeManager::unmountAll()");
|
|
|
|
// First, try gracefully unmounting all known devices
|
|
if (mInternalEmulated != nullptr) {
|
|
mInternalEmulated->unmount();
|
|
}
|
|
for (const auto& disk : mDisks) {
|
|
disk->unmountAll();
|
|
}
|
|
|
|
// Worst case we might have some stale mounts lurking around, so
|
|
// force unmount those just to be safe.
|
|
FILE* fp = setmntent("/proc/mounts", "r");
|
|
if (fp == NULL) {
|
|
SLOGE("Error opening /proc/mounts: %s", strerror(errno));
|
|
return -errno;
|
|
}
|
|
|
|
// Some volumes can be stacked on each other, so force unmount in
|
|
// reverse order to give us the best chance of success.
|
|
std::list<std::string> toUnmount;
|
|
mntent* mentry;
|
|
while ((mentry = getmntent(fp)) != NULL) {
|
|
if (strncmp(mentry->mnt_dir, "/mnt/", 5) == 0
|
|
|| strncmp(mentry->mnt_dir, "/storage/", 9) == 0) {
|
|
toUnmount.push_front(std::string(mentry->mnt_dir));
|
|
}
|
|
}
|
|
endmntent(fp);
|
|
|
|
for (const auto& path : toUnmount) {
|
|
LOG(DEBUG) << "Tearing down stale mount " << path;
|
|
android::vold::ForceUnmount(path);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
extern "C" int vold_unmountAll(void) {
|
|
VolumeManager *vm = VolumeManager::Instance();
|
|
return vm->unmountAll();
|
|
}
|
|
|
|
int VolumeManager::mkdirs(const char* path) {
|
|
// Only offer to create directories for paths managed by vold
|
|
if (strncmp(path, "/storage/", 9) == 0) {
|
|
// fs_mkdirs() does symlink checking and relative path enforcement
|
|
return fs_mkdirs(path, 0700);
|
|
} else {
|
|
SLOGE("Failed to find mounted volume for %s", path);
|
|
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 */);
|
|
}
|