Android vold (卷管理) 传记
戳蓝字“牛晓伟”关注我哦!
用心坚持输出易读、有趣、有深度、高质量、体系化的技术文章
本文摘要
本篇文章同样延续自述和对话的方式来介绍vold(卷管理进程),通过本文您将了解到它在Android系统中到底起了哪些作用?它是如何监听外部存储设备的热插拔事件?如何管理所有的卷?管理的卷到底有啥用?(基于android13代码分析)
Androidnative系统进程系列的几篇文章如下:
Android系统native进程之我是init进程
Android系统native进程之属性能力的设计魅力
Android系统native进程之进程杀手--lmkd
Android系统native进程之日志系统--logd、logcat
Android系统native进程之我是installd进程
Part1我是谁
“诸位上眼了!自古以来名人都有传记,我虽然没有它们有名,但我也想有自己的传记,以备后人能记得我。并且在Android系统中我大小也算个有头有脸的‘人物’。”
“呸!不害臊,你也敢自称有名,还敢有自己的传记,我敢肯定非常多的人都不认识你。”
“这位小兄弟,说话别那么刻薄,我虽然没有ActivityManagerService等有名,那不是我不想出名,而是因为我的性格使然,我一出生就是一个demon进程,也就是我是一个‘低调内敛的性格’,我一直都是默默的在幕后工作着,我虽然不咋有名但是我还是有理想的,我希望我的传记能让大家及后人记住我。并且我有很多的兄弟如lmkd、logd、installd等,它们也出了自己的传记lmkd的传记、logd传记、installd传记,那我的传记就先从介绍我自己开始吧。”
“诸位客官好啊,我英文名是vold,它是 volume deamon 英文单词的缩写,翻译为中文就是卷守护进程,我的主要工作职责就是管理所有的卷,加密/解密CE、DE类型的目录,对存储设备或卷进行挂载、卸载、格式化等操作。”
“这里的卷是啥子意思吗?卷对我们app进程来说有啥作用?还有能用直白的话说明吗?最好能举例子,这样结合例子大家肯定会对你有深入了解”一个进程问到。
“这位仁兄提了很好的建议,我先解释下卷,卷用正式官方语解释的话:卷通常指的是存储设备或存储介质上的一个独立区域,用于存储文件和数据。在操作系统中,一个硬盘可以被分为多个分区,每个分区可以被格式化为一个独立的卷。”
“用大白话解释:外部存储设备如SD卡,闪存等,它们是可以被划分为一个或多个独立的区域,每个区域对应的是自己的文件系统比如A区是ext2、B区是NTFS (windows默认文件系统),卷只有被挂载后才可以使用。其中卷又有虚拟卷 (EmulatedVolume) 、obb卷 (ObbVolume) 、私有卷 (PrivateVolume)、公有卷 (public Volume)、stub卷 (StubVolume)。”
“卷的作用,你们app应该使用过Environment类的getExternalStoragePublicDirectory等方法,把数据存储到外部存储的文件中 (存储区域分为内部存储和外部存储) ,那这个外部存储其实就是指的一个存储设备上的卷,文件就是存储在这个卷上的。app进程如果需要往外部存储存文件的话,需要从StorageManagerService拿到可用卷,而StorageManagerService存储的卷是从我这拿的。有了可用卷整个外部存储才能使用。”
“加密/解密CE和DE类型目录又是做啥呢?关于CE和DE类型目录下面会有详细介绍,和大家先探讨个问题假如Android设备的内部存储设备不加密的话,会有啥危害?比如你的手机被一个不讲道德的高人捡到,那高人就可以经过一些手段拿到内部存储设备上的数据,这是不是很危险啊,想想当年的啥照事件。而内部存储设备加密又分DE和CE两种,而我vold是有对CE和DE类型目录进行加密和解密的能力的”
我是vold,一个具有root权限的系统native进程,我是存储系统最核心的的成员 (存储系统的文章),主要为app存储文件提供服务,我管理着所有的卷,同时还可以加密/解密 CE和DE类型的目录。
Part2我的出生
我的父亲是init进程,因为它的子进程是非常非常多的,这么多子进程何时创建、创建之前需要执行哪些命令又更是多上加多,这么多的信息它完全是无招架之力,为了解决这个问题它创建了init脚本语言,哪个子进程需要创建,则配置自己的init脚本语言即可,下面是我的脚本语言:
service vold /system/bin/vold \
--blkid_context=u:r:blkid:s0 --blkid_untrusted_context=u:r:blkid_untrusted:s0 \
--fsck_context=u:r:fsck:s0 --fsck_untrusted_context=u:r:fsck_untrusted:s0
class core
ioprio be 2
task_profiles ProcessCapacityHigh
shutdown critical
group root reserved_disk
reboot_on_failure reboot,vold-failed
上面的脚本语言会告诉init进程,我的进程名字叫vold,在fork成功后会执行//system/bin/vold/ 可执行文件 (后面是需要传递的参数),class core:代表vold进程是属于core组的,而//system/bin/vold/ 可执行文件会执行下面的方法
//文件路径:system/vold/main.cpp
int main(int argc, char** argv) {
省略代码......
//初始化VolumeManager、NetlinkManager
VolumeManager* vm;
NetlinkManager* nm;
//解析脚本文件中传递的参数
parse_args(argc, argv);
省略代码......
/* Create our singleton managers */
if (!(vm = VolumeManager::Instance())) {
LOG(ERROR) << "Unable to create VolumeManager";
exit(1);
}
if (!(nm = NetlinkManager::Instance())) {
LOG(ERROR) << "Unable to create NetlinkManager";
exit(1);
}
if (android::base::GetBoolProperty("vold.debug", false)) {
vm->setDebug(true);
}
if (vm->start()) {
PLOG(ERROR) << "Unable to start VolumeManager";
exit(1);
}
VoldConfigs configs = {};
省略代码......
//VoldNativeService它是一个binder服务,start方法会把它发布到ServiceManager中
if (android::vold::VoldNativeService::start() != android::OK) {
LOG(ERROR) << "Unable to start VoldNativeService";
exit(1);
}
//监听外部存储设备
if (nm->start()) {
PLOG(ERROR) << "Unable to start NetlinkManager";
exit(1);
}
省略代码......
android::IPCThreadState::self()->joinThreadPool();
LOG(INFO) << "vold shutting down";
exit(0);
}
执行完上面的方法后,就代表具有root权限的系统native类型的vold进程 已经启动了,VoldNativeService是我的“接口人”,它是一个binder服务,Android进程的各位大佬们,如果你们想使用我提供的能力,请从ServiceManager中通过vold关键字可以找到我的binder代理,进而可以与VoldNativeService通信。
Part3 我的价值
你们人类一直在谈各种价值观,而我也不例外我也是有我的价值的,如果我没有价值,Android系统早把我干掉了。我的价值就是提供卷管理、挂载userdata分区、解密CE和DE类型目录、为app创建data和obb目录等能力,为建设一个健壮、高效的存储系统而努力。
Part4 卷管理
大家也都知道我的中文名字是卷守护进程,卷的管理那必当是我最重中之重的功能,既然是管理卷,那卷从何来呢?那就介绍给大家我是如何收集卷的吧。
卷从何来?
我vold管理的卷主要有三种:虚拟卷 (EmulatedVolume)、外部存储设备卷、obb卷。虚拟卷一般指的是真正访问目录为/data/media/的虚拟卷 (关于data/media的介绍可以看Android存储系统成长记文章) ,在android13上它的主要作用是提供一个虚拟的外部存储空间。外部存储设备卷就是指比如SD卡上的卷。关于obb卷不在这讨论。那就来介绍下虚拟卷和外部存储卷是从何而来。
对了真正管理所有卷的是NetlinkManager类,下面代码中的三个属性就对应了三种卷。
//文件路径:/system/vold/VolumeManager.h
//外部存储设备
std::list<std::shared_ptr<android::vold::Disk>> mDisks;
//obb卷
std::list<std::shared_ptr<android::vold::VolumeBase>> mObbVolumes;
//虚拟卷
std::list<std::shared_ptr<android::vold::VolumeBase>> mInternalEmulatedVolumes;
外部存储设备卷
那就来看下如何收集外部存储设备的卷,那就有请uevent、netlink、VolumeManager、NetlinkManager、NetlinkHandler它们出场,因为这个功能是它们共同实现的,先来看一个它们之间协同工作实现收集卷的功能图吧。
结合上图,收集外部存储设备卷的过程如下:
如插入SD卡,内核会发送uevent事件 (会携带设备的关键信息) NetLinkHandler把uevent事件发送给VolumeManager VolumeManager根据设备 (Disk)信息解析卷等信息
看了整个工作流程,那就有请各位来做下自我介绍,uevent先来吧。
uevent:“大家好,我的名字叫uevent,这个名字其实是 userspace event的缩写,翻译为中文是用户空间事件,你们肯定非常关心我的用途,我有非常重要的用途:那就是内核有关于设备相关的事件都会生成一个uevent,这个uevent会发送到用户空间,因此我的名字叫uevent。内核设备相关的事件有比如SD卡插入、删除等等。反正就是内核设备相关的事件都会发送一个uevent到用户空间。而发送是需要netlink这个通道的。”
netlink:“我是netlink,我的主要作用是搭建用户空间与内核空间的通道,官方对我的解释如下,请大家自行取阅。”
netlink 是 Linux 内核提供的一个用户空间与内核空间之间进行通信的机制。与传统的系统调用和 ioctl 相比,netlink 提供了更强大、更灵活的网络通信能力。它允许用户空间的应用程序与内核模块之间进行双向的、异步的通信
NetlinkManager:“大家好啊,我是NetlinkManager类,如netlink介绍我的主要作用就是用代码来实现内核空间与用户空间的通道,而我的启动是在vold进程启动的时候开始的。启动后我会把NetlinkHandler启动,那有请它。”
下面是NetlinkManager启动相关代码,请大家自行取阅。
//文件路径:/system/vold/main.cpp
//在vold进程fork成功后会执行main方法
int main(int argc, char** argv) {
省略代码......
NetlinkManager* nm;
//获取NetlinkManager实例,它是单例
if (!(nm = NetlinkManager::Instance())) {
LOG(ERROR) << "Unable to create NetlinkManager";
exit(1);
}
//启动NetlinkManager,它会与内核建立netlink通道
if (nm->start()) {
PLOG(ERROR) << "Unable to start NetlinkManager";
exit(1);
}
省略代码......
}
//文件路径:/system/vold/NetlinkManager.cpp
int NetlinkManager::start() {
省略代码......
//创建socket
if ((mSock = socket(PF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC, NETLINK_KOBJECT_UEVENT)) < 0) {
PLOG(ERROR) << "Unable to create uevent socket";
return -1;
}
mHandler = new NetlinkHandler(mSock);
//调用NetlinkHandler的start方法,这样内核有uevent事件,它就可以收到了
if (mHandler->start()) {
PLOG(ERROR) << "Unable to start NetlinkHandler";
goto out;
}
return 0;
out:
close(mSock);
return -1;
}
NetlinkHandler:“大家好,我是NetlinkHandler类,通过名字也能看出我与netlink有关,我的主要作用就是拿到uevent事件后,传递给VolumeManager,那有请VolumeManager。”
VolumeManager:“我是VolumeManager类,大家上眼了看我的名字就知道我是干啥的,我管理着所有的卷 (volume)。我的启动同样也在vold进程启动的时候开始,下面是部分代码,可自行取阅。”
//文件路径:/system/vold/main.cpp
//在vold进程fork成功后会执行main方法
int main(int argc, char** argv) {
省略代码......
NetlinkManager* nm;
/* Create our singleton managers */
if (!(vm = VolumeManager::Instance())) {
LOG(ERROR) << "Unable to create VolumeManager";
exit(1);
}
if (!(nm = NetlinkManager::Instance())) {
LOG(ERROR) << "Unable to create NetlinkManager";
exit(1);
}
省略代码......
//调用VolumeManager的start方法
if (vm->start()) {
PLOG(ERROR) << "Unable to start VolumeManager";
exit(1);
}
省略代码......
}
//文件路径:/system/vold/VolumeManager.cpp
int VolumeManager::start() {
省略代码......
//卸载所有的卷,disk
// Always start from a clean slate by unmounting everything in
// directories that we own, in case we crashed.
unmountAll();
省略代码......
//创建path为 /data/media 的虚拟卷
auto vol = std::shared_ptr<android::vold::VolumeBase>(
new android::vold::EmulatedVolume("/data/media", 0));
vol->setMountUserId(0);
vol->create();
//把虚拟卷放入mInternalEmulatedVolumes
mInternalEmulatedVolumes.push_back(vol);
省略代码......
return 0;
}
VolumeManager接着说:“等我启动成功后,若有uevent事件,NetlinkHandler就会调用我的handleBlockEvent方法告知我,我就会把外部存储设备解析为Disk对象,Disk对象会创建对应的卷 (volume),下面是对应代码,可自行取阅。”
//文件路径:/system/vold/NetlinkHandler.cpp
//从kernel层有事件的话,会调用onEvent方法
void NetlinkHandler::onEvent(NetlinkEvent* evt) {
VolumeManager* vm = VolumeManager::Instance();
const char* subsys = evt->getSubsystem();
省略代码......
//只处理block类型的设备
if (std::string(subsys) == "block") {
//调用VolumeManager的handleBlockEvent方法
vm->handleBlockEvent(evt);
}
}
//文件路径:/system/vold/VolumeManager.cpp
void VolumeManager::handleBlockEvent(NetlinkEvent* evt) {
std::lock_guard<std::mutex> lock(mLock);
//获取设备相关信息
std::string eventPath(evt->findParam("DEVPATH") ? evt->findParam("DEVPATH") : "");
std::string devType(evt->findParam("DEVTYPE") ? evt->findParam("DEVTYPE") : "");
//不是disk则返回
if (devType != "disk") return;
//获取disk的major、minor信息
int major = std::stoi(evt->findParam("MAJOR"));
int minor = std::stoi(evt->findParam("MINOR"));
dev_t device = makedev(major, minor);
switch (evt->getAction()) {
//disk add事件
case NetlinkEvent::Action::kAdd: {
//只有mDiskSources内的才会被添加
for (const auto& source : mDiskSources) {
if (source->matches(eventPath)) {
省略代码......
//构建disk
auto disk =
new android::vold::Disk(eventPath, device, source->getNickname(), flags);
//处理disk add事件,查看下面handleDiskAdded方法
handleDiskAdded(std::shared_ptr<android::vold::Disk>(disk));
break;
}
}
break;
}
case NetlinkEvent::Action::kChange: {
LOG(VERBOSE) << "Disk at " << major << ":" << minor << " changed";
//处理disk变化事件
handleDiskChanged(device);
break;
}
case NetlinkEvent::Action::kRemove: {
//处理disk remove事件
handleDiskRemoved(device);
break;
}
default: {
LOG(WARNING) << "Unexpected block event action " << (int)evt->getAction();
break;
}
}
}
void VolumeManager::handleDiskAdded(const std::shared_ptr<android::vold::Disk>& disk) {
//锁屏界面显示或者userid:0 还没有开始运行,则把disk添加到mPendingDisks,等待安全了在处理mPendingDisks的disk
bool userZeroStarted = mStartedUsers.find(0) != mStartedUsers.end();
if (mSecureKeyguardShowing) {
LOG(INFO) << "Found disk at " << disk->getEventPath()
<< " but delaying scan due to secure keyguard";
mPendingDisks.push_back(disk);
} else if (!userZeroStarted) {
LOG(INFO) << "Found disk at " << disk->getEventPath()
<< " but delaying scan due to user zero not having started";
mPendingDisks.push_back(disk);
} else {
//若锁屏界面没显示并且userid:0 也开始运行了,则调用disk的create方法,create方法会创建卷信息,并且把disk添加到mDisks
disk->create();
mDisks.push_back(disk);
}
}
小结
外部存储设备卷的收集主要的参与类是VolumeManager、NetlinkManager、NetlinkHandler,收集卷的过程:NetlinkManager负责创建netlink (它是内核空间与用户空间的通信通道),在netlink通信通道上传递的是uevent事件,如果内核有uevent事件会通知到NetlinkHandler,进而在通知到VolumeManager,VolumeManager根据是add、remove、change事件再去进一步的处理,如果是add事件则会创建Disk,Disk会创建对应的卷 (volume)。
不管是Disk的创建还是卷 (volume)的创建或者状态的改变都会把这样信息同步给StorageManagerService,而StorageManagerService则会把创建的Disk和卷信息保存起来,供app来使用。
虚拟卷
虚拟卷的对应类是EmulatedVolume,那就有请它来介绍下。
EmulatedVolume:“大家好,我是虚拟卷,在解释我的用处之前,先来介绍下外部存储,外部存储的文件是可以共享的,在android13上外部存储其实是一个从 /data 目录划分出一部分区域作为外部存储的,而划分出的区域的目录是 /data/media,也就是说这时候的外部存储是一个虚拟的外部存储。为了配合虚拟外部存储那就需要我来出马了,在vold进程启动或者设备user第一次运行的时候会创建path为 /data/media的EmulatedVolume。我虚拟卷也同样只有挂载了才能使用,而具体啥时候挂载则需要StorageManagerService来告知vold进程。”
同样虚拟卷的创建和状态变化也是需要通知给StorageManagerService,而StorageManagerService也会把虚拟卷保存下来,供app来使用。关于虚拟卷更多的内容可以看Android存储系统成长记文章
下面是部分代码,自行取阅
//文件路径:/system/vold/VolumeManager.cpp
//在VolumeManager的start方法会创建虚拟卷,vold进程启动的时候会调用start方法
int VolumeManager::start() {
省略代码......
//创建虚拟卷
auto vol = std::shared_ptr<android::vold::VolumeBase>(
new android::vold::EmulatedVolume("/data/media", 0));
vol->setMountUserId(0);
vol->create();
mInternalEmulatedVolumes.push_back(vol);
// Consider creating a virtual disk
updateVirtualDisk();
return 0;
}
//在设备user第一次启动的时候会调用下面方法,而调用onUserStarted方法是StorageManagerService会通过binder调用来调用该方法
int VolumeManager::onUserStarted(userid_t userId) {
LOG(INFO) << "onUserStarted: " << userId;
//当前user没有启动,则开始执行createEmulatedVolumesForUser
if (mStartedUsers.find(userId) == mStartedUsers.end()) {
//调用下面的createEmulatedVolumesForUser方法
createEmulatedVolumesForUser(userId);
}
mStartedUsers.insert(userId);
createPendingDisksIfNeeded();
return 0;
}
void VolumeManager::createEmulatedVolumesForUser(userid_t userId) {
LOG(INFO) << "niu vold VolumeManager::createEmulatedVolumesForUser userId:" << userId;
//创建目录为/data/media的虚拟卷
// Create unstacked EmulatedVolumes for the user
auto vol = std::shared_ptr<android::vold::VolumeBase>(
new android::vold::EmulatedVolume("/data/media", userId));
vol->setMountUserId(userId);
mInternalEmulatedVolumes.push_back(vol);
//调用create方法开始创建
vol->create();
省略代码......
}
总结
从外部存储设备卷和虚拟卷两个方向介绍了vold进程的管理的卷从何来,而具体管理卷的类是VolumeManager,vold进程管理的卷和Disk信息都会通知给StorageManagerService,StorageManagerService会把Disk和卷都保存起来,当app在往外部存储存储文件的时候,会通过binder调用从StorageManagerService获取可用卷,进而存储文件。
Part5 挂载userdata分区
介绍完毕卷管理,在来介绍我的另外一个重要功能挂载userdata分区,对应的能力接口是mountFstab,这个接口不当当只会挂载userdata分区,还会挂载别的分区。咱们现在只关心userdata分区的挂载。
fstab
在介绍挂载之前,先来介绍下fstab
fstab : 是 Linux 和其他类 Unix 系统中的一个重要文件,它用于存储文件系统的静态信息。具体来说,fstab 文件列出了系统上所有的文件系统(包括交换分区)以及它们应该如何被挂载到文件系统中
如下是fstab内容的例子:
# Android fstab file.
#<src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
# system分区挂载于 /system 目录
system /system ext4 ro,barrier=1 wait,slotselect,avb=vbmeta_system,logical,first_stage_mount
system_ext /system_ext ext4 ro,barrier=1 wait,slotselect,avb=vbmeta_system,logical,first_stage_mount
vendor /vendor ext4 ro,barrier=1 wait,slotselect,avb=vbmeta,logical,first_stage_mount
product /product ext4 ro,barrier=1 wait,slotselect,avb,logical,first_stage_mount
/dev/block/by-name/metadata /metadata ext4 noatime,nosuid,nodev,discard,data=journal,commit=1 wait,formattable,first_stage_mount,check
/dev/block/bootdevice/by-name/modem /vendor/firmware_mnt vfat ro,shortname=lower,uid=0,gid=1000,dmask=227,fmask=337,context=u:object_r:firmware_file:s0 wait,slotselect
/dev/block/bootdevice/by-name/misc /misc emmc defaults defaults
# userdata分区挂载于 /data 目录
/dev/block/bootdevice/by-name/userdata /data f2fs noatime,nosuid,nodev,discard,reserve_root=32768,resgid=1065,fsync_mode=nobarrier latemount,wait,check,quota,formattable,fileencryption=ice,reservedsize=128M,sysfs_path=/dev/sys/block/bootdevice,keydirectory=/metadata/vold/metadata_encryption,checkpoint=fs
/devices/platform/soc/a600000.ssusb/a600000.dwc3* auto vfat defaults voldmanaged=usb:auto
/dev/block/zram0 none swap defaults zramsize=2147483648,max_comp_streams=8,zram_backingdev_size=512M
开始挂载
挂载userdata分区其实就是把 /dev/block/bootdevice/by-name/userdata (在上面的fstab文件中有定义) 挂载到 /data目录,/data目录的作用主要是包含了各种各样的app数据、用户数据等等,如内部存储 (根目录/data/user/userid) 和外部存储 (真正数据根目录 /data/media/userid) 都是位于/data目录下面 (userid是当前设备的对应的用户id,如果不存在多用户,则它的值是0) 。在如 /data/system目录下面存放了系统的各种jar、so库比如framework.jar就在这目录下面。
下面是data目录下面各种目录的一个截图,请自行取阅
小结
data目录如此的重要,在设备启动的时候肯定是需要挂载userdata分区到/data目录的,这样才能通过/data目录访问各种数据,而挂载userdata分区的时机是在init进程启动的时候,挂载完毕后这时候/data目录下面的CE和DE类型的目录是处于加密状态的,只有解密了才能使用,那就来看下解密能力吧。
Part5 解密CE和DE类型目录
关于解密CE和DE类型目录的内容可以看Android存储系统成长记,就不在这赘述了。
Part6 为app创建data和obb目录
为app创建data和obb目录,为啥我要提供这个能力呢?
在解释原因之前,先说明一点,这里的data和obb目录是位于外部存储下面的 (外部存储的根目录是 /storage/emulated/userid) ,可不是位于内部存储 (内部存储的根目录是 /data/data 或者 /data/user/userid) 目录下面。而app的data和obb目录路径是:/storage/emulated/userid/Android/data/packagename和/storage/emulated/userid/Android/obb/packagename (packagename是包名) 。
data和obb目录是位于外部存储根目录下面,并且这俩目录有个特点就是它们的uid是与app的appid是一致的,也就是说只有当前app进程才可以访问自己的data和obb目录。虽然StorageManagerService位于systemserver进程,但是systemserver进程是没有为某个uid创建对应目录的权限的,而我vold进程是具有root权限的并且我也持有虚拟卷,因此为app创建data和obb目录的功能就被我“包揽”了。
下面是部分代码,请自行取阅
//文件路径:/system/vold/VoldNativeService.cpp
//在创建app目录的时候会通过binder调用调用到该方法
binder::Status VoldNativeService::setupAppDir(const std::string& path, int32_t appUid) {
ENFORCE_SYSTEM_OR_ROOT;
CHECK_ARGUMENT_PATH(path);
ACQUIRE_LOCK;
//调用下面的setupAppDir方法
return translate(VolumeManager::Instance()->setupAppDir(path, appUid));
}
//path为要创建的目录,一般为/storage/emulated/userid/Android/data/packagename
int VolumeManager::setupAppDir(const std::string& path, int32_t appUid, bool fixupExistingOnly,
bool skipIfDirExists) {
// Only offer to create directories for paths managed by vold
//不是以 /storage开头的则直接返回,表明它不是一个正常的外部存储路径
if (!StartsWith(path, "/storage/")) {
LOG(ERROR) << "Failed to find mounted volume for " << path;
return -EINVAL;
}
// Find the volume it belongs to
auto filter_fn = [&](const VolumeBase& vol) {
if (vol.getState() != VolumeBase::State::kMounted) {
// The volume must be mounted
return false;
}
if (!vol.isVisibleForWrite()) {
// App dirs should only be created for writable volumes.
return false;
}
if (vol.getInternalPath().empty()) {
return false;
}
if (vol.getMountUserId() != USER_UNKNOWN &&
vol.getMountUserId() != multiuser_get_user_id(appUid)) {
// The app dir must be created on a volume with the same user-id
return false;
}
if (!path.empty() && StartsWith(path, vol.getPath())) {
return true;
}
return false;
};
//查找到对应的卷,这个卷是EmulatedVolume (它的真正目录是 /data/media)
auto volume = findVolumeWithFilter(filter_fn);
//没找到返回
if (volume == nullptr) {
LOG(ERROR) << "Failed to find mounted volume for " << path;
return -EINVAL;
}
// Convert paths to lower filesystem paths to avoid making FUSE requests for these reasons:
// 1. A FUSE request from vold puts vold at risk of hanging if the FUSE daemon is down
// 2. The FUSE daemon prevents requests on /mnt/user/0/emulated/<userid != 0> and a request
// on /storage/emulated/10 means /mnt/user/0/emulated/10
const std::string lowerPath =
volume->getInternalPath() + path.substr(volume->getPath().length()); //niu volume->getInternalPath()为/data/media
//volumeRoot为 /data/media/userid
const std::string volumeRoot = volume->getRootPath(); // eg /data/media/0
const int access_result = access(lowerPath.c_str(), F_OK);
if (fixupExistingOnly && access_result != 0) {
// Nothing to fixup
return OK;
}
if (skipIfDirExists && access_result == 0) {
// It's safe to assume it's ok as it will be used for zygote to bind mount dir only,
// which the dir doesn't need to have correct permission for now yet.
return OK;
}
省略代码......
//调用下面的PrepareAppDirFromRoot方法
// Create the app paths we need from the root
return PrepareAppDirFromRoot(lowerPath, volumeRoot, appUid, fixupExistingOnly);
}
int PrepareAppDirFromRoot(const std::string& path, const std::string& root, int appUid,
bool fixupExisting) {
long projectId;
size_t pos;
int ret = 0;
bool sdcardfsSupport = IsSdcardfsUsed();
//root:/data/media/0,主要是创建 root+/Android root+/Android/data root+/Android/obb root+/Android/media
// Make sure the Android/ directories exist and are setup correctly
ret = PrepareAndroidDirs(root);
if (ret != 0) {
LOG(ERROR) << "Failed to prepare Android/ directories.";
return ret;
}
// Now create the application-specific subdir(s)
// path is something like /data/media/0/Android/data/com.foo/files
// First, chop off the volume root, eg /data/media/0
std::string pathFromRoot = path.substr(root.length()); //niu pathFromRoot为/Android/data/com.foo/files
uid_t uid = appUid;
gid_t gid = AID_MEDIA_RW;
std::vector<gid_t> additionalGids;
std::string appDir;
// Check that the next part matches one of the allowed Android/ dirs
if (StartsWith(pathFromRoot, kAppDataDir)) {
appDir = kAppDataDir; //niu /Android/data/
if (!sdcardfsSupport) {
gid = AID_EXT_DATA_RW;
// Also add the app's own UID as a group; since apps belong to a group
// that matches their UID, this ensures that they will always have access to
// the files created in these dirs, even if they are created by other processes
additionalGids.push_back(uid);
}
} else if (StartsWith(pathFromRoot, kAppMediaDir)) {
appDir = kAppMediaDir; //niu /Android/media
if (!sdcardfsSupport) {
gid = AID_MEDIA_RW;
}
} else if (StartsWith(pathFromRoot, kAppObbDir)) {
appDir = kAppObbDir; //niu /Android/obb/
if (!sdcardfsSupport) {
gid = AID_EXT_OBB_RW;
// See comments for kAppDataDir above
additionalGids.push_back(uid);
}
} else {
LOG(ERROR) << "Invalid application directory: " << path;
return -EINVAL;
}
// mode = 770, plus sticky bit on directory to inherit GID when apps
// create subdirs
mode_t mode = S_IRWXU | S_IRWXG | S_ISGID;
// the project ID for application-specific directories is directly
// derived from their uid
// Chop off the generic application-specific part, eg /Android/data/
// this leaves us with something like com.foo/files/
std::string leftToCreate = pathFromRoot.substr(appDir.length()); //niu packagename/files/
if (!EndsWith(leftToCreate, "/")) {
leftToCreate += "/";
}
std::string pathToCreate = root + appDir; //niu /data/media/userid/Android/data
int depth = 0;
// Derive initial project ID
if (appDir == kAppDataDir || appDir == kAppMediaDir) {
projectId = uid - AID_APP_START + PROJECT_ID_EXT_DATA_START;
} else if (appDir == kAppObbDir) {
projectId = uid - AID_APP_START + PROJECT_ID_EXT_OBB_START;
}
while ((pos = leftToCreate.find('/')) != std::string::npos) {
std::string component = leftToCreate.substr(0, pos + 1);
leftToCreate = leftToCreate.erase(0, pos + 1);
pathToCreate = pathToCreate + component;
if (appDir == kAppDataDir && depth == 1 && component == "cache/") {
// All dirs use the "app" project ID, except for the cache dirs in
// Android/data, eg Android/data/com.foo/cache
// Note that this "sticks" - eg subdirs of this dir need the same
// project ID.
projectId = uid - AID_APP_START + PROJECT_ID_EXT_CACHE_START;
}
if (fixupExisting && access(pathToCreate.c_str(), F_OK) == 0) {
// Fixup all files in this existing directory with the correct UID/GID
// and project ID.
ret = FixupAppDir(pathToCreate, mode, uid, gid, projectId);
} else {
ret = PrepareDirWithProjectId(pathToCreate, mode, uid, gid, projectId);
}
if (ret != 0) {
return ret;
}
if (depth == 0) {
// Set the default ACL on the top-level application-specific directories,
// to ensure that even if applications run with a umask of 0077,
// new directories within these directories will allow the GID
// specified here to write; this is necessary for apps like
// installers and MTP, that require access here.
//
// See man (5) acl for more details.
ret = SetDefaultAcl(pathToCreate, mode, uid, gid, additionalGids);
if (ret != 0) {
return ret;
}
if (!sdcardfsSupport) {
// Set project ID inheritance, so that future subdirectories inherit the
// same project ID
ret = SetQuotaInherit(pathToCreate);
if (ret != 0) {
return ret;
}
}
}
depth++;
}
return OK;
}
Part6 总结
我是一个非常重要的具有root权限的系统native进程,由于我的性格生下来就低调、不热于表现,导致我不怎么出名,但是你们app确实一直在使用我提供的服务,如果没有我你们app就根本没有办法存储文件,因为存储文件是需要往内部存储或者外部存储下存储的,而内部存储和外部存储都需要挂载userdata分区到 /data 目录 (这可是我提供的能力)才达到了可用状态,即使挂载了也需要我提供的解密CE和DE类型目录的能力,要不 /data 目录下的这些目录都是加密的,加密状态下你们app绝对用不了的。
我管理着所有的卷,你们app在往外部存储存储文件的时候确实是从StorageManagerService获取的,但是别忘记StorageManagerService保存的卷的源头可是在我这。
这就是我的个人传记,希望在多年后大家能依然记得我,我毕竟在Android世界存在过。