2023年11月29日发(作者:)

AndroidVold简介(三)

之前两篇主要介绍了Vold的架构以及运⾏机制,本篇主要来介绍下Vold是内置存储和外置存储的mount流程。前⾯已经介绍过,⽆论什么

存储,最终都会调⽤doMount()这个虚函数,对于不同类型的则会有不同的mount流程(即挂在到不同的⽂件系统下);对于内置存储,使⽤

EmulatedVolume类来处理,⽽外接sd卡或者OTG设备则基本上都使⽤PublicVolume类来处理,因此uevent传递上来的消息会通知到

vold到底这个存储介质属于什么类型。

⾸先来看下EmulatedVolume处理流程,⾸先会判断Primary标志,表明是内置主要的存储,⼀般都会跑进这个逻辑,因为只有data分区

下的存储才会⾛这边;接着创建/mnt/runtime/default/emulated等四个⽬录,这个是Google针对sdcardfs添加的⽤来处理app之间相

互的访问权限。拥有不同权限的app会bind mount到对应的⽬录,例如只有读权限的app则会bind到/mnt/runtime/read/emulated。

status_t EmulatedVolume::doMount() {

// 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";

}

mFuseDefault = StringPrintf("/mnt/runtime/default/%s", label.c_str()); //

默认权限,⼀般是只读权限;

mFuseRead = StringPrintf("/mnt/runtime/read/%s", label.c_str()); //

读权限

mFuseWrite = StringPrintf("/mnt/runtime/write/%s", label.c_str()); //

写权限

mFuseFull = StringPrintf("/mnt/runtime/full/%s", label.c_str()); //

所有权限

setInternalPath(mRawPath);

setPath(StringPrintf("/storage/%s", label.c_str()));

//

创建对应的四个⽬录

if (fs_prepare_dir(mFuseDefault.c_str(), 0700, AID_ROOT, AID_ROOT) ||

fs_prepare_dir(mFuseRead.c_str(), 0700, AID_ROOT, AID_ROOT) ||

fs_prepare_dir(mFuseWrite.c_str(), 0700, AID_ROOT, AID_ROOT) ||

fs_prepare_dir(mFuseFull.c_str(), 0700, AID_ROOT, AID_ROOT)) {

PLOG(ERROR) << getId() << " failed to create mount points";

return -errno;

}

dev_t before = GetDevice(mFuseFull);

//sdcard

创建⼦进程,启动进程来处理具体的挂在流程;

if (!(mFusePid = fork())) {

// clang-format off

if (execl(kFusePath, kFusePath, //sdcard

执⾏进程

"-u", "1023", // AID_MEDIA_RW uid

挂载使⽤的

"-g", "1023", // AID_MEDIA_RW gid

挂载使⽤的

"-m",

"-w",

"-G",

"-i",

"-o",

mRawPath.c_str(),

label.c_str(),

NULL)) {

// clang-format on

PLOG(ERROR) << "Failed to exec";

}

LOG(ERROR) << "FUSE exiting";

_exit(1);

}

if (mFusePid == -1) {

PLOG(ERROR) << getId() << " failed to fork";

PLOG(ERROR) << getId() << " failed to fork";

return -errno;

}

nsecs_t start = systemTime(SYSTEM_TIME_BOOTTIME);

while (before == GetDevice(mFuseFull)) {

LOG(DEBUG) << "Waiting for FUSE to ";

usleep(50000); // 50ms

nsecs_t now = systemTime(SYSTEM_TIME_BOOTTIME);

if (nanoseconds_to_milliseconds(now - start) > 5000) {

LOG(WARNING) << "Timed out while waiting for FUSE to spin up";

return -ETIMEDOUT;

}

}

/* sdcardfs will have exited already. FUSE will still be running */

TEMP_FAILURE_RETRY(waitpid(mFusePid, nullptr, 0));

mFusePid = 0;

return OK;

}

真正挂载的命令是在⽂件中执⾏的,从代码中⼜看到了熟悉的四个⽬录,不错,就是针对不同权限给的四个⽬录;四个⽬录都

会挂载到/storage/emulated/⽬录下,区别就在于挂载的时候给的参数不同,包括传递的属性掩码以及⽤户组,只有default是传递的

1023,其他都是9997(AID_EVERYBODY)。这都是挂载到了sdcardfs下,如果有esdfs则会挂载到对应的⽂件系统,⼀般没有这个标

志,也就都使⽤了sdcardfs。

static void run_sdcardfs(const std::string& source_path, const std::string& label, uid_t uid,

gid_t gid, userid_t userid, bool multi_user, bool full_write,

bool derive_gid, bool default_normal, bool unshared_obb, bool use_esdfs) {

std::string dest_path_default = "/mnt/runtime/default/" + label;

std::string dest_path_read = "/mnt/runtime/read/" + label;

std::string dest_path_write = "/mnt/runtime/write/" + label;

std::string dest_path_full = "/mnt/runtime/full/" + label;

umask(0);

if (multi_user) {

// Multi-user storage is fully isolated per user, so "other"

// permissions are completely masked off.

if (!sdcardfs_setup(source_path, dest_path_default, uid, gid, multi_user, userid,

AID_SDCARD_RW, 0006, derive_gid, default_normal, unshared_obb,

use_esdfs) ||

!sdcardfs_setup_secondary(dest_path_default, source_path, dest_path_read, uid, gid,

multi_user, userid, AID_EVERYBODY, 0027, derive_gid,

default_normal, unshared_obb, use_esdfs) ||

!sdcardfs_setup_secondary(dest_path_default, source_path, dest_path_write, uid, gid,

multi_user, userid, AID_EVERYBODY, full_write ? 0007 : 0027,

derive_gid, default_normal, unshared_obb, use_esdfs) ||

!sdcardfs_setup_secondary(dest_path_default, source_path, dest_path_full, uid, gid,

multi_user, userid, AID_EVERYBODY, 0007, derive_gid,

default_normal, unshared_obb, use_esdfs)) {

LOG(FATAL) << "failed to sdcardfs_setup";

}

} else {

...

}

}

// Will abort if priv-dropping fails.

drop_privs(uid, gid);

if (multi_user) {

std::string obb_path = source_path + "/obb";

fs_prepare_dir(obb_path.c_str(), 0775, uid, gid);

}

exit(0);

}

static bool sdcardfs_setup(const std::string& source_path, const std::string& dest_path,

uid_t fsuid, gid_t fsgid, bool multi_user, userid_t userid, gid_t gid,

mode_t mask, bool derive_gid, bool default_normal, bool unshared_obb,

bool use_esdfs) {

// Add new options at the end of the vector.

std::vector<std::string> new_opts_list;

if (multi_user) new_opts_list.push_back("multiuser,");

if (derive_gid) new_opts_list.push_back("derive_gid,");

if (default_normal) new_opts_list.push_back("default_normal,");

if (unshared_obb) new_opts_list.push_back("unshared_obb,");

// Try several attempts, each time with one less option, to gracefully

// handle older kernels that aren't updated yet.

for (int i = 0; i <= new_opts_list.size(); ++i) {

std::string new_opts;

for (int j = 0; j < new_opts_list.size() - i; ++j) {

new_opts += new_opts_list[j];

}

auto opts = android::base::StringPrintf("fsuid=%d,fsgid=%d,%smask=%d,userid=%d,gid=%d",

fsuid, fsgid, new_opts.c_str(), mask, userid, gid);

if (mount(source_path.c_str(), dest_path.c_str(), use_esdfs ? "esdfs" : "sdcardfs",

MS_NOSUID | MS_NODEV | MS_NOEXEC | MS_NOATIME, opts.c_str()) == -1) {

PLOG(WARNING) << "Failed to mount sdcardfs with options " << opts;

} else {

return true;

}

}

return false;

}

sdcardfs并不是真正传统的⽂件系统,他只是Google为了控制访问权限在原本⽂件系统上增加的⼀个保护壳,真正的⽂件系统还是得看

data分区是使⽤哪⼀个⽂件系统,⼀般是ext4或者f2fs。这样的话内置存储就有两个⽂件系统⽀持,⽂件的操作还是在底层⽂件系统中完

成,sdcardfs⽤来做进⼀步的检查和控制。

第⼆个来看⼀下PublicVolume这个类型的挂载流程,和EmulatedVolume挂载类似,但是挂载不再只使⽤sdcard,⽽是先将整个存储介

质挂载到storage下,有vfat和exfat两种类型,当然如果有其他⽂件系统也可以增加,例如ntfs等。这个相当于优先挂载底层⽂件系统,如

果有需要则再挂载到sdcardfs中,⽽这个条件则取决于是否对外可见,由变量kVisible来判断。

status_t PublicVolume::doMount() {

readMetadata();

if (mFsType == "vfat" && vfat::IsSupported()) {

if (vfat::Check(mDevPath)) {

LOG(ERROR) << getId() << " failed filesystem check";

return -EIO;

}

} else if (mFsType == "exfat" && exfat::IsSupported()) {

if (exfat::Check(mDevPath)) {

LOG(ERROR) << getId() << " failed filesystem check";

return -EIO;

}

} else {

LOG(ERROR) << getId() << " unsupported filesystem " << mFsType;

return -EIO;

}

// Use UUID as stable name, if available

std::string stableName = getId();

if (!mFsUuid.empty()) {

stableName = mFsUuid;

stableName = mFsUuid;

}

mRawPath = StringPrintf("/mnt/media_rw/%s", stableName.c_str());

mFuseDefault = StringPrintf("/mnt/runtime/default/%s", stableName.c_str());

mFuseRead = StringPrintf("/mnt/runtime/read/%s", stableName.c_str());

mFuseWrite = StringPrintf("/mnt/runtime/write/%s", stableName.c_str());

mFuseFull = StringPrintf("/mnt/runtime/full/%s", stableName.c_str());

setInternalPath(mRawPath);

if (getMountFlags() & MountFlags::kVisible) {

setPath(StringPrintf("/storage/%s", stableName.c_str()));

} else {

setPath(mRawPath);

}

if (fs_prepare_dir(mRawPath.c_str(), 0700, AID_ROOT, AID_ROOT)) {

PLOG(ERROR) << getId() << " failed to create mount points";

return -errno;

}

if (mFsType == "vfat") {

if (vfat::Mount(mDevPath, mRawPath, false, false, false, AID_MEDIA_RW, AID_MEDIA_RW, 0007,

true)) {

//mount(mDevPath, mRawPath, "vfat", flags, mountData.c_str());

PLOG(ERROR) << getId() << " failed to mount " << mDevPath;

return -EIO;

}

} else if (mFsType == "exfat") {

if (exfat::Mount(mDevPath, mRawPath, AID_MEDIA_RW, AID_MEDIA_RW, 0007)) {

//mount(mDevPath, mRawPath, "exfat", flags, mountData.c_str());

PLOG(ERROR) << getId() << " failed to mount " << mDevPath;

return -EIO;

}

}

if (getMountFlags() & MountFlags::kPrimary) {

initAsecStage();

}

if (!(getMountFlags() & MountFlags::kVisible)) {

// Not visible to apps, so no need to spin up FUSE

return OK;

}

if (fs_prepare_dir(mFuseDefault.c_str(), 0700, AID_ROOT, AID_ROOT) ||

fs_prepare_dir(mFuseRead.c_str(), 0700, AID_ROOT, AID_ROOT) ||

fs_prepare_dir(mFuseWrite.c_str(), 0700, AID_ROOT, AID_ROOT) ||

fs_prepare_dir(mFuseFull.c_str(), 0700, AID_ROOT, AID_ROOT)) {

PLOG(ERROR) << getId() << " failed to create FUSE mount points";

return -errno;

}

dev_t before = GetDevice(mFuseFull);

if (!(mFusePid = fork())) {

if (getMountFlags() & MountFlags::kPrimary) {

// clang-format off

if (execl(kFusePath, kFusePath,

"-u", "1023", // AID_MEDIA_RW

"-g", "1023", // AID_MEDIA_RW

"-U", std::to_string(getMountUserId()).c_str(),

"-w",

mRawPath.c_str(),

stableName.c_str(),

stableName.c_str(),

NULL)) {

// clang-format on

PLOG(ERROR) << "Failed to exec";

}

} else {

// clang-format off

if (execl(kFusePath, kFusePath,

"-u", "1023", // AID_MEDIA_RW

"-g", "1023", // AID_MEDIA_RW

"-U", std::to_string(getMountUserId()).c_str(),

mRawPath.c_str(),

stableName.c_str(),

NULL)) {

// clang-format on

PLOG(ERROR) << "Failed to exec";

}

}

LOG(ERROR) << "FUSE exiting";

_exit(1);

}

if (mFusePid == -1) {

PLOG(ERROR) << getId() << " failed to fork";

return -errno;

}

nsecs_t start = systemTime(SYSTEM_TIME_BOOTTIME);

while (before == GetDevice(mFuseFull)) {

LOG(DEBUG) << "Waiting for FUSE to ";

usleep(50000); // 50ms

nsecs_t now = systemTime(SYSTEM_TIME_BOOTTIME);

if (nanoseconds_to_milliseconds(now - start) > 5000) {

LOG(WARNING) << "Timed out while waiting for FUSE to spin up";

return -ETIMEDOUT;

}

}

/* sdcardfs will have exited already. FUSE will still be running */

TEMP_FAILURE_RETRY(waitpid(mFusePid, nullptr, 0));

mFusePid = 0;

return OK;

}

以上就是vold处理存储介质挂载的流程,具体的mount流程处于kernel部分,不同的⽂件系统有不同的特性,mount流程也会不⼀样,通

过系统调⽤由VFS来分配是由哪个⽂件系统来处理。

本⽚代码针对的是Android Q,在R版本Google已经强制开始使⽤Fuse,并且从kernel中取出sdcardfs,所以vold挂载时使⽤Fuse。