Android外部存储空间及动态权限授予原理

2024-03-25 03:59

本文主要是介绍Android外部存储空间及动态权限授予原理,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

外部存储空间


    在Android的世界中,应用程序可以使用的文件存储区域包括两个:内部存储空间、外部存储空间。这两个名称是在Android早期确定的,那时候大部分设备都提供内置的非易失性存储(内部存储空间)以及可移动的存储媒介,例如Micro SD卡(外部存储空间)。现在,很多设备将永久性存储空间划分为单独的“内部”和“外部”分区。因此,即使没有可移动存储媒介,这两种存储空间也始终存在,并且无论外部存储空间是否可移动,这两种存储空间的 API 行为都是相同的。

        在目前的Adnroid系统中,外部存储空间实际上是通过Linux Mount 和Bind Mount对内部数据目录"/data/media"的重新挂在,在真实的设备上我们通过mount命令可以看到系统对“/data/media"目录进行了多次挂载:

1|xxxx:/ # mount |grep "/data/media"
/data/media on /mnt/runtime/default/emulated type sdcardfs (rw,nosuid,nodev,noexec,noatime,fsuid=1023,fsgid=1023,gid=1015,multiuser,mask=6,derive_gid,default_normal,reserved=200MB)
/data/media on /storage/emulated type sdcardfs (rw,nosuid,nodev,noexec,noatime,fsuid=1023,fsgid=1023,gid=1015,multiuser,mask=6,derive_gid,default_normal,reserved=200MB)
/data/media on /mnt/runtime/read/emulated type sdcardfs (rw,nosuid,nodev,noexec,noatime,fsuid=1023,fsgid=1023,gid=9997,multiuser,mask=23,derive_gid,default_normal,reserved=200MB)
/data/media on /mnt/runtime/write/emulated type sdcardfs (rw,nosuid,nodev,noexec,noatime,fsuid=1023,fsgid=1023,gid=9997,multiuser,mask=7,derive_gid,default_normal,reserved=200MB)
/data/media on /mnt/runtime/full/emulated type sdcardfs (rw,nosuid,nodev,noexec,noatime,fsuid=1023,fsgid=1023,gid=9997,multiuser,mask=7,derive_gid,default_normal,reserved=200MB)

    /mnt/runtime/defalut/emulated,/mnt/runtime/read/emulated,/mnt/runtime/write/emulated,/mnt/runtime/full/emulated的挂载是在系统启动时完成,其中/mnt/runtime/default/emulated是一个新的挂载点,/mnt/runtime/read/emulated,/mnt/runtime/write/emulated,/mnt/runtime/full/emulated是bind mount出来的,这样做的好处是减少文件系统内核开销(inode、dentry),但是带来的问题是inode共享之后要做不同的权限控制需要动态生成inode的权限位,uid,gid,这些是sdcard_fs通过挂载参数来完成的。具体的挂载参考”system/core/sdcard/sdcard.cpp“中的源码,本文参考android10.0的代码:

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 {....}...
}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;
}static bool sdcardfs_setup_secondary(const std::string& default_path,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) {if (use_esdfs) {return sdcardfs_setup(source_path, dest_path, fsuid, fsgid, multi_user, userid, gid, mask,derive_gid, default_normal, unshared_obb, use_esdfs);} else {return sdcardfs_setup_bind_remount(default_path, dest_path, gid, mask);}
}static bool sdcardfs_setup_bind_remount(const std::string& source_path, const std::string& dest_path,gid_t gid, mode_t mask) {std::string opts = android::base::StringPrintf("mask=%d,gid=%d", mask, gid);if (mount(source_path.c_str(), dest_path.c_str(), nullptr,MS_BIND, nullptr) != 0) {                                  // ***bind mount***PLOG(ERROR) << "failed to bind mount sdcardfs filesystem";return false;}if (mount(source_path.c_str(), dest_path.c_str(), "none",MS_REMOUNT | MS_NOSUID | MS_NODEV | MS_NOEXEC | MS_NOATIME, opts.c_str()) != 0) {   // *** remount 修改挂载参数,sdcard_fs权限控制需要使用***PLOG(ERROR) << "failed to mount sdcardfs filesystem";if (umount2(dest_path.c_str(), MNT_DETACH))PLOG(WARNING) << "Failed to unmount bind";return false;}return true;
}

      /storage/emulated是在init.rc中挂载/storage间接挂载的,bind,slave:

mount none /mnt/runtime/default /storage bind rec
mount none none /storage slave rec

    上面分析的5处挂载都发生在root namespace空间,android为了能够做到动态权限管理的目的,Android使用了mount namespace,在Zygote fork应用进程的时候会通过unshare系统调用为应用进程创建一个mount namespace,在应用自己的mount namespace中根据AMS传递给Zygote的参数决定将/storage bind mount到 /mnt/runtime/defalut/emulated,/mnt/runtime/read/emulated,/mnt/runtime/write/emulated,/mnt/runtime/full/emulated中的一个。在应用权限发生变化时,权限管理模块会调用到vold对应用进程空间的/storage根据权限变动重新挂载,这块我们在后面动态权限授予章节结合代码具体分析。

        Zygote起来之后首先会在自己的自己的进程空间中执行unshare(CLONE_NEWNS)跳出root namespace进入到新的mount namespace中,执行UnmountTree("/storage"),unmount掉在root namespace中对/storage挂载点的挂载,在fork应用程序子进程的时候在子进程的命名空间中调用:

        mount(storage_source.string(), "/storage", nullptr,MS_BIND | MS_REC | MS_SLAVE, nullptr)),就能完成应用进程空间的真实挂载点绑定,根据权限的不同,应用通过外部存储路径/storage/emulated/0 访问到的实际上是/mnt/runtime/defalut/emulated,/mnt/runtime/read/emulated,/mnt/runtime/write/emulated中的一个。运行时关系如下图:

sdcard_fs


    sdcard_fs是google用来替代fuse接管文件系统权限而忽略真实磁盘文件系统权限(ext4,...)的一种堆叠文件系统,它是由三星的wrapfs改写而来。sdcard_fs并不真实访问磁盘,sdcard_fs会调用底层真实文件系统的方法进行真实的读写。由于还没有对sdcard_fs文件系统的源码进行研读,我们只分析一下和本文相关的对外部权限进行管控的逻辑。

        1. sdcard_fs进行文件的各种操作之前都会调用override_fsids方法(inode.c),在override_fsids方法中sdcard_fs会替换掉进程的struct cred,将cred->fsuid 和cred→sgid替换成真实文件系统的uid和gid,通过替换真实文件系统的uid和gid 通过sdcard_fs文件系统权限检查的方法调用总是能够反应到真实的文件系统上的调用执行:

const struct cred *override_fsids(struct sdcardfs_sb_info *sbi,struct sdcardfs_inode_data *data)
{struct cred *cred;const struct cred *old_cred;uid_t uid;cred = prepare_creds();if (!cred)return NULL;if (sbi->options.gid_derivation) {if (data->under_obb)uid = AID_MEDIA_OBB;elseuid = multiuser_get_uid(data->userid, sbi->options.fs_low_uid);} else {uid = sbi->options.fs_low_uid;    // *** fs_low_uid 是sdcard_fs挂载时传入的参数 fsuid ***}cred->fsuid = make_kuid(&init_user_ns, uid);cred->fsgid = make_kgid(&init_user_ns, sbi->options.fs_low_gid); // *** fs_low_gid 是sdcard_fs挂载时传入的参数fsgid***old_cred = override_creds(cred);return old_cred;
}

      比如我们挂载点/mnt/runtime/default/emulated,Android在挂载的时候传入的fsuid, fsgid都是1023,而1023就是/data/media目录的所属用户和组:

OP46B1:/ # ls -ll /data |grep media
drwxrwx---   3 media_rw media_rw  4096 2020-09-01 10:56:04.489999979 +0800 media
drwxrwx---   2 mediadrm mediadrm  4096 1970-02-12 02:26:35.651999975 +0800 mediadrm
OP46B1:/ # id media_rw
uid=1023(media_rw) gid=1023(media_rw) groups=1023(media_rw), context=u:r:su:s0

     2. sdcard_fs权限校验发生在访问真实文件系统之前,sdcardfs重写了inode_operation的permission方法:

static int sdcardfs_permission(struct vfsmount *mnt, struct inode *inode, int mask)
{int err;struct inode tmp;struct sdcardfs_inode_data *top = top_data_get(SDCARDFS_I(inode));if (IS_ERR(mnt))return PTR_ERR(mnt);if (!top)return -EINVAL;/** Permission check on sdcardfs inode.* Calling process should have AID_SDCARD_RW permission* Since generic_permission only needs i_mode, i_uid,* i_gid, and i_sb, we can create a fake inode to pass* this information down in.** The underlying code may attempt to take locks in some* cases for features we're not using, but if that changes,* locks must be dealt with to avoid undefined behavior.*/copy_attrs(&tmp, inode);tmp.i_uid = make_kuid(&init_user_ns, top->d_uid);  tmp.i_gid = make_kgid(&init_user_ns, get_gid(mnt, inode->i_sb, top));tmp.i_mode = (inode->i_mode & S_IFMT)| get_mode(mnt, SDCARDFS_I(inode), top);data_put(top);tmp.i_sb = inode->i_sb;if (IS_POSIXACL(inode))pr_warn("%s: This may be undefined behavior...\n", __func__);err = generic_permission(&tmp, mask);return err;
}

     sdcardfs_permission最重要的是对inode的权限位,gid,uid的替换,完成替换之后调用vfs的generic_permission进行文件系统权限校验。 

     tmp.i_uid = make_kuid(&init_user_ns, top→d_uid)最终取值是AID_ROOT也即是root用户,他是sdcard_fs在挂载时得到的,具体逻辑参考sdcardfs_read_super 和setup_derived_state方法:

static int sdcardfs_read_super(struct vfsmount *mnt, struct super_block *sb,const char *dev_name, void *raw_data, int silent)
{.../* setup permission policy */sb_info->obbpath_s = kzalloc(PATH_MAX, GFP_KERNEL);mutex_lock(&sdcardfs_super_list_lock);if (sb_info->options.multiuser) {setup_derived_state(d_inode(sb->s_root), PERM_PRE_ROOT,sb_info->options.fs_user_id, AID_ROOT);snprintf(sb_info->obbpath_s, PATH_MAX, "%s/obb", dev_name);} else {setup_derived_state(d_inode(sb->s_root), PERM_ROOT,sb_info->options.fs_user_id, AID_ROOT);snprintf(sb_info->obbpath_s, PATH_MAX, "%s/Android/obb", dev_name);}...
}void setup_derived_state(struct inode *inode, perm_t perm, userid_t userid,uid_t uid)
{...info->data->d_uid = uid;...
}

    tmp.i_gid = make_kgid(&init_user_ns, get_gid(mnt, inode->i_sb, top))调用get_gid方法来构造gid,在get_gid方法中通过vfsopt→gid来构造gid的值,而vfsopt→gid的值来源于挂载参数gid,/mnt/runtime/default/emulated的挂载参数gid是1015(sdcard_rw),而/mnt/runtime/full(read|write)/emulated的挂载参数gid是9997(everybody)

OP46B1:/ # id sdcard_rw
uid=1015(sdcard_rw) gid=1015(sdcard_rw) groups=1015(sdcard_rw), context=u:r:su:s0
OP46B1:/ # id everybody
uid=9997(everybody) gid=9997(everybody) groups=9997(everybody), context=u:r:su:s0
static inline int get_gid(struct vfsmount *mnt,struct super_block *sb,struct sdcardfs_inode_data *data)
{struct sdcardfs_vfsmount_options *vfsopts = mnt->data;struct sdcardfs_sb_info *sbi = SDCARDFS_SB(sb);if (vfsopts->gid == AID_SDCARD_RW && !sbi->options.default_normal)/* As an optimization, certain trusted system components only run* as owner but operate across all users. Since we're now handing* out the sdcard_rw GID only to trusted apps, we're okay relaxing* the user boundary enforcement for the default view. The UIDs* assigned to app directories are still multiuser aware.*/return AID_SDCARD_RW;elsereturn multiuser_get_uid(data->userid, vfsopts->gid); // *** /mnt/runtime/read/emulated 文件访问权限校验gid使用9997 ***
}

    tmp.i_mode = (inode->i_mode & S_IFMT) | get_mode(mnt, SDCARDFS_I(inode), top)调用get_mode方法来获取权限位,get_mode方法中最终要的一句代码是int visible_mode = 0775 & ~opts->mask, 而opts→mask来自于sdcard_fs挂载点挂载时传入的挂载参数mask,比如挂载点/mnt/runtime/read/emulated挂载时传入的参数mode=23,所以在everybody组中的用户对挂载点/mnt/runtime/read/emulated只有读权限没有写权限。源码参考:

static inline int get_mode(struct vfsmount *mnt,struct sdcardfs_inode_info *info,struct sdcardfs_inode_data *data)
{int owner_mode;int filtered_mode;struct sdcardfs_vfsmount_options *opts = mnt->data;int visible_mode = 0775 & ~opts->mask;    // *** opts->mask来自挂载参数mask ***if (data->perm == PERM_PRE_ROOT) {/* Top of multi-user view should always be visible to ensure* secondary users can traverse inside.*/visible_mode = 0711;} else if (data->under_android) {/* Block "other" access to Android directories, since only apps* belonging to a specific user should be in there; we still* leave +x open for the default view.*/if (opts->gid == AID_SDCARD_RW)visible_mode = visible_mode & ~0006;elsevisible_mode = visible_mode & ~0007;}owner_mode = info->lower_inode->i_mode & 0700;filtered_mode = visible_mode & (owner_mode | (owner_mode >> 3) | (owner_mode >> 6));return filtered_mode;
}

    讲到这里我们可以看一看上面提到的各个挂载点上的权限:

OP46B1:/ # ls /mnt/runtime/default/emulated/ -ll
total 4
drwxrwx--x 36 root sdcard_rw 4096 2020-09-01 01:43:00.486299234 +0800 0
OP46B1:/ # ls /mnt/runtime/read/emulated/ -ll
total 4
drwxr-x--- 36 root everybody 4096 2020-09-01 01:43:00.486299234 +0800 0
OP46B1:/ # ls /mnt/runtime/write/emulated/ -ll
total 4
drwxrwx--- 36 root everybody 4096 2020-09-01 01:43:00.486299234 +0800 0
OP46B1:/ # ls /mnt/runtime/full/emulated/ -ll
total 4
drwxrwx--- 36 root everybody 4096 2020-09-01 01:43:00.486299234 +0800 0
OP46B1:/ # ls /storage/emulated/ -ll
total 4
drwxrwx--x 36 root sdcard_rw 4096 2020-09-01 01:43:00.486299234 +0800 0

动态权限授予


    前面讲了外部存储空间和sdcard_fs的一些权限相关的核心逻辑,那么Android中如何做到动态权限控制的呢?大致情况是这样:

        1. 在App启动的时候,Zygote会根据应用权限的授予情况,在进程fork的时候为应用进程创建mount namespace,在应用程序所在的mount namespace中bind mount "/mnt/runtime/defalut/emulated,/mnt/runtime/read/emulated,/mnt/runtime/write/emulated,/mnt/runtime/full/emulated"中的一个;

        2.  App在启动的时候,Zygote会调用setgroups函数将9997(everybody)设置为应用程序进程所在的组;

        3. 在应用程序权限发生变化时(获得权限),权限管理模块会通知vold 重新mount "/mnt/runtime/defalut/emulated,/mnt/runtime/read/emulated,/mnt/runtime/write/emulated,/mnt/runtime/full/emulated"中的一个;

        因为所有的app进程都俗语9997组,所以当/storage bind mount 到/mnt/runtime/write 之后,app 进程对/storage/emulated/0下的文件就拥有了可读写权限。

举个例子:以操作今日头条为例(今日头条的包名时com.ss.android.article.news),在获得外部空间写入权限前后gid从1015 变为了9997。通过nsenter -t pid -m sh可以进入到今日头条进程的mount namespace空间中查看到操作前后如下内容:

OP46B1:/ # ps -elf |grep com.ss.android.article.news
u0_a208      19826  1914 85 11:24:18 ?    00:04:11 com.ss.android.article.news
u0_a208      20200  1914 55 11:24:25 ?    00:02:41 com.ss.android.article.news:sandboxed_process1
u0_a208      20291  1914 5 11:24:26 ?     00:00:13 com.ss.android.article.news:pushservice
u0_a208      20322  1914 4 11:24:26 ?     00:00:12 com.ss.android.article.news:push
u0_a208      21051  1914 7 11:24:42 ?     00:00:18 com.ss.android.article.news:miniapp0
root         22251 21988 5 11:29:15 pts/0 00:00:00 grep com.ss.android.article.news
OP46B1:/ # nsenter -t 19826 -m sh
OP46B1:/ # mount |grep "/storage/emulated"   <-------【获得外部存储权限前】
/data/media on /storage/emulated type sdcardfs (rw,nosuid,nodev,noexec,noatime,fsuid=1023,fsgid=1023,gid=1015,multiuser,
mask=6,derive_gid,default_normal,reserved=200MB)
OP46B1:/ # exit
OP46B1:/ # nsenter -t 19826 -m sh
OP46B1:/ # mount |grep "/storage/emulated"   <-------【获得外部存储权限后】
/data/media on /storage/emulated type sdcardfs (rw,nosuid,nodev,noexec,noatime,fsuid=1023,fsgid=1023,gid=9997,multiuser,
mask=7,derive_gid,default_normal,reserved=200MB)

     下面我们结合代码具体分析Android中外部存储动态权限的授予过程,首先在App启动的时候,AMS会调用到方法ProcessList.java#startProcessLocked,在startProcessLocked方法中会将9997作为gid参数传给Zygote:

// ProcessList.java
public static final int SHARED_USER_GID = 9997;
boolean startProcessLocked(ProcessRecord app, HostingRecord hostingRecord,boolean disableHiddenApiChecks, boolean mountExtStorageFull,String abiOverride) {...gids[0] = UserHandle.getSharedAppGid(UserHandle.getAppId(uid));gids[1] = UserHandle.getCacheAppGid(UserHandle.getAppId(uid));gids[2] = UserHandle.getUserGid(UserHandle.getUserId(uid));...}
// UserHandle.javapublic static int getUserGid(@UserIdInt int userId) {return getUid(userId, Process.SHARED_USER_GID);}

    接着流程进入jni程序 com_android_internal_os_Zygote.cpp#SpecializeCommon方法中,在这个方法中调用了MountEmulatedStorage完成app进程mount namespace创建和/storage bind挂载,并调用SetGids将9997设置为app进程所属组:

static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids,jint runtime_flags, jobjectArray rlimits,jlong permitted_capabilities, jlong effective_capabilities,jint mount_external, jstring managed_se_info,jstring managed_nice_name, bool is_system_server,bool is_child_zygote, jstring managed_instruction_set,jstring managed_app_data_dir) {...MountEmulatedStorage(uid, mount_external, use_native_bridge, fail_fn);...SetGids(env, gids, fail_fn);...
}static void MountEmulatedStorage(uid_t uid, jint mount_mode,bool force_mount_namespace,fail_fn_t fail_fn) {// See storage config details at http://source.android.com/tech/storage/ATRACE_CALL();String8 storage_source;if (mount_mode == MOUNT_EXTERNAL_DEFAULT) {storage_source = "/mnt/runtime/default";} else if (mount_mode == MOUNT_EXTERNAL_READ) {storage_source = "/mnt/runtime/read";} else if (mount_mode == MOUNT_EXTERNAL_WRITE|| mount_mode == MOUNT_EXTERNAL_LEGACY|| mount_mode == MOUNT_EXTERNAL_INSTALLER) {storage_source = "/mnt/runtime/write";} else if (mount_mode == MOUNT_EXTERNAL_FULL) {storage_source = "/mnt/runtime/full";} else if (mount_mode == MOUNT_EXTERNAL_NONE && !force_mount_namespace) {// Sane default of no storage visiblereturn;}// Create a second private mount namespace for our processif (unshare(CLONE_NEWNS) == -1) {fail_fn(CREATE_ERROR("Failed to unshare(): %s", strerror(errno)));}// Handle force_mount_namespace with MOUNT_EXTERNAL_NONE.if (mount_mode == MOUNT_EXTERNAL_NONE) {return;}if (TEMP_FAILURE_RETRY(mount(storage_source.string(), "/storage", nullptr,MS_BIND | MS_REC | MS_SLAVE, nullptr)) == -1) {fail_fn(CREATE_ERROR("Failed to mount %s to /storage: %s",storage_source.string(),strerror(errno)));}...
}static void SetGids(JNIEnv* env, jintArray managed_gids, fail_fn_t fail_fn) {...if (setgroups(gids.size(), reinterpret_cast<const gid_t*>(&gids[0])) == -1) {fail_fn(CREATE_ERROR("setgroups failed: %s, gids.size=%zu", strerror(errno), gids.size()));}
}

       当应用程序发生权限改变时(获得权限),逻辑流程大致如下:

        PermissionManangerService.java#grantRuntimePermission ->

        StorageManagerService.java#onExternalStoragePolicyChanged ->

        StorageManagerServic.java#remountUidExternalStorage ->

        VolumeManager.cpp#remountUid

         java 层只是一些过程调用,最后关键动作是Vold进程中的VolumeManager.cpp#remountUid方法,在remountUid方法中会通过遍历/porc的方法找到app uid对应的进程,并打开对应进程的“ns/mnt”,fork出一个零时的子进程,在子进程中通过setns切换app进程的mount namespace,在新的namespace中先unmount 掉/storage/挂载点,然后像在进程启动时Zygote做的一样在/storage/挂载点上重新bind mount 合适的目录:

int VolumeManager::remountUid(uid_t uid, int32_t mountMode) {std::string mode;switch (mountMode) {case VoldNativeService::REMOUNT_MODE_NONE:mode = "none";break;case VoldNativeService::REMOUNT_MODE_DEFAULT:mode = "default";break;case VoldNativeService::REMOUNT_MODE_READ:mode = "read";break;case VoldNativeService::REMOUNT_MODE_WRITE:case VoldNativeService::REMOUNT_MODE_LEGACY:case VoldNativeService::REMOUNT_MODE_INSTALLER:mode = "write";break;case VoldNativeService::REMOUNT_MODE_FULL:mode = "full";break;default:PLOG(ERROR) << "Unknown mode " << std::to_string(mountMode);return -1;}LOG(DEBUG) << "Remounting " << uid << " as mode " << mode;DIR* dir;struct dirent* de;std::string rootName;std::string pidName;int pidFd;int nsFd;struct stat sb;pid_t child;static bool apexUpdatable = android::sysprop::ApexProperties::updatable().value_or(false);if (!(dir = opendir("/proc"))) {PLOG(ERROR) << "Failed to opendir";return -1;}// Figure out root namespace to compare against belowif (!android::vold::Readlinkat(dirfd(dir), "1/ns/mnt", &rootName)) {PLOG(ERROR) << "Failed to read root namespace";closedir(dir);return -1;}// Poke through all running PIDs look for apps running as UIDwhile ((de = readdir(dir))) {pid_t pid;if (de->d_type != DT_DIR) continue;if (!android::base::ParseInt(de->d_name, &pid)) continue;pidFd = -1;nsFd = -1;,        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 namespaceLOG(DEBUG) << "Found matching PID " << de->d_name;if (!android::vold::Readlinkat(pidFd, "ns/mnt", &pidName)) {PLOG(WARNING) << "Failed to read namespace for " << de->d_name;goto next;}if (rootName == pidName) {LOG(WARNING) << "Skipping due to root namespace";goto next;}if (apexUpdatable) {std::string exeName;// When ro.apex.bionic_updatable is set to true,// some early native processes have mount namespaces that are different// from that of the init. Therefore, above check can't filter them out.// Since the propagation type of / is 'shared', unmounting /storage// for the early native processes affects other processes including// init. Filter out such processes by skipping if a process is a// non-Java process whose UID is < AID_APP_START. (The UID condition// is required to not filter out child processes spawned by apps.)if (!android::vold::Readlinkat(pidFd, "exe", &exeName)) {PLOG(WARNING) << "Failed to read exe name for " << de->d_name;goto next;}if (!StartsWith(exeName, "/system/bin/app_process") && sb.st_uid < AID_APP_START) {LOG(WARNING) << "Skipping due to native system process";goto next;}}// We purposefully leave the namespace open across the fork// NOLINTNEXTLINE(android-cloexec-open): Deliberately not O_CLOEXECnsFd = openat(pidFd, "ns/mnt", O_RDONLY);if (nsFd < 0) {PLOG(WARNING) << "Failed to open namespace for " << de->d_name;goto next;}if (!(child = fork())) {if (setns(nsFd, CLONE_NEWNS) != 0) {   // 切换 mount namespacePLOG(ERROR) << "Failed to setns for " << de->d_name;_exit(1);}android::vold::UnmountTree("/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 if (mode == "full") {storageSource = "/mnt/runtime/full";} 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 placeuserid_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;
}

总结


    Android外部存储空间的实现比较复杂,主要是借助了Linux文件系统的机制,运用到的关机技术有sdscard_fs、mnt namespace、bind mount。sdcard_fs接管了底层文件系统的权限控制,通过bind mount避免了为每个进程mount带来的内核文件系统节点开销,mnt namespace保证每个应用进程有自己的挂载点,并在运行时通过在vold中为进程切换新的mount namespace来实现了动态授予权限的母的。

主要参考资料:

魅族内核团队:http://kernel.meizu.com/android-m-external-storage.html

这篇关于Android外部存储空间及动态权限授予原理的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/843783

相关文章

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

第10章 中断和动态时钟显示

第10章 中断和动态时钟显示 从本章开始,按照书籍的划分,第10章开始就进入保护模式(Protected Mode)部分了,感觉从这里开始难度突然就增加了。 书中介绍了为什么有中断(Interrupt)的设计,中断的几种方式:外部硬件中断、内部中断和软中断。通过中断做了一个会走的时钟和屏幕上输入字符的程序。 我自己理解中断的一些作用: 为了更好的利用处理器的性能。协同快速和慢速设备一起工作

深入探索协同过滤:从原理到推荐模块案例

文章目录 前言一、协同过滤1. 基于用户的协同过滤(UserCF)2. 基于物品的协同过滤(ItemCF)3. 相似度计算方法 二、相似度计算方法1. 欧氏距离2. 皮尔逊相关系数3. 杰卡德相似系数4. 余弦相似度 三、推荐模块案例1.基于文章的协同过滤推荐功能2.基于用户的协同过滤推荐功能 前言     在信息过载的时代,推荐系统成为连接用户与内容的桥梁。本文聚焦于

动态规划---打家劫舍

题目: 你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。 给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。 思路: 动态规划五部曲: 1.确定dp数组及含义 dp数组是一维数组,dp[i]代表

hdu4407(容斥原理)

题意:给一串数字1,2,......n,两个操作:1、修改第k个数字,2、查询区间[l,r]中与n互质的数之和。 解题思路:咱一看,像线段树,但是如果用线段树做,那么每个区间一定要记录所有的素因子,这样会超内存。然后我就做不来了。后来看了题解,原来是用容斥原理来做的。还记得这道题目吗?求区间[1,r]中与p互质的数的个数,如果不会的话就先去做那题吧。现在这题是求区间[l,r]中与n互质的数的和

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

Android平台播放RTSP流的几种方案探究(VLC VS ExoPlayer VS SmartPlayer)

技术背景 好多开发者需要遴选Android平台RTSP直播播放器的时候,不知道如何选的好,本文针对常用的方案,做个大概的说明: 1. 使用VLC for Android VLC Media Player(VLC多媒体播放器),最初命名为VideoLAN客户端,是VideoLAN品牌产品,是VideoLAN计划的多媒体播放器。它支持众多音频与视频解码器及文件格式,并支持DVD影音光盘,VCD影

代码随想录冲冲冲 Day39 动态规划Part7

198. 打家劫舍 dp数组的意义是在第i位的时候偷的最大钱数是多少 如果nums的size为0 总价值当然就是0 如果nums的size为1 总价值是nums[0] 遍历顺序就是从小到大遍历 之后是递推公式 对于dp[i]的最大价值来说有两种可能 1.偷第i个 那么最大价值就是dp[i-2]+nums[i] 2.不偷第i个 那么价值就是dp[i-1] 之后取这两个的最大值就是d

android-opencv-jni

//------------------start opencv--------------------@Override public void onResume(){ super.onResume(); //通过OpenCV引擎服务加载并初始化OpenCV类库,所谓OpenCV引擎服务即是 //OpenCV_2.4.3.2_Manager_2.4_*.apk程序包,存

hdu4407容斥原理

题意: 有一个元素为 1~n 的数列{An},有2种操作(1000次): 1、求某段区间 [a,b] 中与 p 互质的数的和。 2、将数列中某个位置元素的值改变。 import java.io.BufferedInputStream;import java.io.BufferedReader;import java.io.IOException;import java.io.Inpu