文章来源:/shift_wwx/article/details/85633801
请转载的朋友表明出处,请支持原创~~
0. 前言
Android 从6.0 开始引入了Runtime permission,应用对于storage 进行读取、存储的时候,需要注册、申请对应的权限。Android 8.0中对于sdcard 读写只需要申请权限即可使用,可以在Android 9.0 中同样的应用执行同样的步骤,却提示了Permission denied。
本文将借此对sdcard 进行简单地剖析。代码基于版本Android 9.0
1. 问题描述
1.1 应用中的代码
private boolean doCreate(File file) {
try {
File parentFile = ();
if (!isFileExists(parentFile)) {
();
}
();
} catch (IOException e) {
();
return false;
}
return true;
}
private boolean createFile(File file) {
if (isFileExists(file))
return true;
return doCreate(file);
}
/**
* 通过此函数进行文件创建操作
*/
private boolean createFile(String filePath) {
(TAG, "==== createFile, filePath = " + filePath);
File file = new File(filePath);
return createFile(file);
}
private boolean isFileExists(File file) {
return file != null && ();
}
如代码,通过createFile() 来进行文件创建操作,因为是测试,其中的filePath 直接写死为:/storage/6344-0FEF/
在应用的 中权限也已经给出:
<uses-permission android:name=".READ_EXTERNAL_STORAGE"/>
<uses-permission android:name=".WRITE_EXTERNAL_STORAGE"/>
1.2 问题log
01-02 05:47:32.711 24318 24318 W : : Permission denied
01-02 05:47:32.711 24318 24318 W : at .createFileExclusively0(Native Method)
01-02 05:47:32.711 24318 24318 W : at (:281)
01-02 05:47:32.712 24318 24318 W : at (:1008)
01-02 05:47:32.712 24318 24318 W : at (:126)
01-02 05:47:32.712 24318 24318 W : at (:138)
01-02 05:47:32.712 24318 24318 W : at (:145)
01-02 05:47:32.713 24318 24318 W : at (:110)
01-02 05:47:32.713 24318 24318 W : at (:183)
01-02 05:47:32.714 24318 24318 W : at (:6597)
01-02 05:47:32.714 24318 24318 W : at (:6574)
01-02 05:47:32.714 24318 24318 W : at $3100(:778)
01-02 05:47:32.714 24318 24318 W : at $(:25889)
01-02 05:47:32.714 24318 24318 W : at (:873)
01-02 05:47:32.714 24318 24318 W : at (:99)
01-02 05:47:32.714 24318 24318 W : at (:193)
01-02 05:47:32.714 24318 24318 W : at (:6692)
01-02 05:47:32.714 24318 24318 W : at (Native Method)
01-02 05:47:32.715 24318 24318 W : at $(:493)
01-02 05:47:32.715 24318 24318 W : at (:858)
2. 问题解析
通过log 可以看到最终对于File 的操作调用到UnixFileSystem.createFileExclusively0(),来看下source code:
/* -- File operations -- */
// Android-changed: Added thread policy check
public boolean createFileExclusively(String path) throws IOException {
().onWriteToDisk();
return createFileExclusively0(path);
}
private native boolean createFileExclusively0(String path) throws IOException;
最终调用的是native 的方法createFileExclusively0()
详见 libcore/ojluni/src/main/native/UnixFileSystem_md.c
// Android-changed: Name changed because of added thread policy check
JNIEXPORT jboolean JNICALL
Java_java_io_UnixFileSystem_createFileExclusively0(JNIEnv *env, jclass cls,
jstring pathname)
{
jboolean rv = JNI_FALSE;
WITH_PLATFORM_STRING(env, pathname, path) {
FD fd;
/* The root directory always exists */
if (strcmp (path, "/")) {
fd = handleOpen(path, O_RDWR | O_CREAT | O_EXCL, 0666);
ALOGD("path = %s, fd = %d, errno = %d", path, fd, errno);
if (fd < 0) {
if (errno != EEXIST)
JNU_ThrowIOExceptionWithLastError(env, path);
} else {
if (close(fd) == -1)
JNU_ThrowIOExceptionWithLastError(env, path);
rv = JNI_TRUE;
}
}
} END_PLATFORM_STRING(env, path);
return rv;
}
如果path 不为空的时候会调用handleOpen():
FD
handleOpen(const char *path, int oflag, int mode) {
FD fd;
RESTARTABLE(open64(path, oflag, mode), fd);
if (fd != -1) {
struct stat64 buf64;
int result;
RESTARTABLE(fstat64(fd, &buf64), result);
if (result != -1) {
if (S_ISDIR(buf64.st_mode)) {
close(fd);
errno = EISDIR;
fd = -1;
}
} else {
close(fd);
fd = -1;
}
}
return fd;
}
而open64就是库函数open,也就是说在open 的时候出现了error,errno 为13,也就是EACCES,即Permission denied。
那么导致这个问题的原因,大概就是文件节点的权限给的不够,带着这个想法来看下文件节点。
3. 问题剖析
首先来看下设备的mount 情况:
/dev/block/vold/public:179,65 on /mnt/media_rw/6344-0FEF type vfat (rw,dirsync,nosuid,nodev,noexec,noatime,uid=1023,gid=1023,fmask=0007,dmask=0007,allow_utime=0020,codepage=437,iocharset=iso8859-1,shortname=mixed,utf8,errors=remount-ro)
/mnt/media_rw/6344-0FEF on /mnt/runtime/default/6344-0FEF type sdcardfs (rw,nosuid,nodev,noexec,noatime,fsuid=1023,fsgid=1023,gid=1015,mask=6)
/mnt/media_rw/6344-0FEF on /storage/6344-0FEF type sdcardfs (rw,nosuid,nodev,noexec,noatime,fsuid=1023,fsgid=1023,gid=1015,mask=6)
/mnt/media_rw/6344-0FEF on /mnt/runtime/read/6344-0FEF type sdcardfs (rw,nosuid,nodev,noexec,noatime,fsuid=1023,fsgid=1023,gid=9997,mask=18)
/mnt/media_rw/6344-0FEF on /mnt/runtime/write/6344-0FEF type sdcardfs (rw,nosuid,nodev,noexec,noatime,fsuid=1023,fsgid=1023,gid=9997,mask=18)
目前系统用的是sdcardfs 文件系统,较之前fuse 效率更高。
另外,得知:
- /mnt/runtime/default 的gid 为1015,也就是sdcard_rw;mask 为6,也就是other 没有rw权限;
- /mnt/runtime/read 的gid 为9997,也就是everybody;mask 为18,也就是group、other都没有w 权限;
- /mnt/runtime/write 的gid 为9997,也就是everybody;mask 为18,也就是group、other都没有w 权限;
gid 与名称详细信息可以看source code,路径为system/core/include/cutils/android_filesystem_config.h
#define AID_SDCARD_RW 1015 /* external storage write access */
#define AID_MEDIA_RW 1023 /* internal media storage write access */
#define AID_EVERYBODY 9997 /* shared between all apps in the same profile */
来看下这几个节点:
msm8940_EVB:/mnt/runtime/write # ls -l
total 36
drwxr-xr-x 9 root everybody 32768 2018-12-29 03:53 6344-0FEF
msm8940_EVB:/mnt/runtime/read # ls -l
total 36
drwxr-xr-x 9 root everybody 32768 2018-12-29 03:53 6344-0FEF
msm8940_EVB:/mnt/runtime/default # ls -l
total 36
drwxrwx--x 9 root sdcard_rw 32768 2018-12-29 03:53 6344-0FEF
如果default 节点,group id 是sdcard_rw,而其他两个的group 为everybody,这就跟上面mount 的结果一致了。通过这里其实就可以发现出现sdcard 无法写入是因为mount 的时候并没有给出 w 权限。
3.1 PublicVolume
sdcard 挂载的时候 vold 会调用到 PublicVolume 中的 doMount():
system/vold/model/
status_t PublicVolume::doMount() {
...
...
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());
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 (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)) {
PLOG(ERROR) << getId() << " failed to create FUSE mount points";
return -errno;
}
dev_t before = GetDevice(mFuseWrite);
if (!(mFusePid = fork())) {
if (getMountFlags() & MountFlags::kPrimary) {
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(),
NULL)) {
PLOG(ERROR) << "Failed to exec";
}
} else {
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)) {
PLOG(ERROR) << "Failed to exec";
}
}
LOG(ERROR) << "FUSE exiting";
_exit(1);
}
...
...
return OK;
}
最终会将一些mount 的参数,通过execl 函数实现,而函数参数kFusePath 为:
static const char* kFusePath = "/system/bin/sdcard";
通过代码得知对于外置 sdcard 参数中并没有 -w,导致了下面mount 的时候没有给 w 权限,下面会继续分析。
最终会通过可执行程序sdcard 实现mount():
system/core/sdcard/
int main(int argc, char **argv) {
...
...
int opt;
while ((opt = getopt(argc, argv, "u:g:U:mwGi")) != -1) {
switch (opt) {
case 'u':
uid = strtoul(optarg, NULL, 10);
break;
case 'g':
gid = strtoul(optarg, NULL, 10);
break;
case 'U':
userid = strtoul(optarg, NULL, 10);
break;
case 'm':
multi_user = true;
break;
case 'w':
full_write = true;
break;
case 'G':
derive_gid = true;
break;
case 'i':
default_normal = true;
break;
case '?':
default:
return usage();
}
}
for (i = optind; i < argc; i++) {
char* arg = argv[i];
if (!source_path) {
source_path = arg;
} else if (!label) {
label = arg;
} else {
LOG(ERROR) << "too many arguments";
return usage();
}
}
if (!source_path) {
LOG(ERROR) << "no source path specified";
return usage();
}
if (!label) {
LOG(ERROR) << "no label specified";
return usage();
}
if (!uid || !gid) {
LOG(ERROR) << "uid and gid must be nonzero";
return usage();
}
rlim.rlim_cur = 8192;
rlim.rlim_max = 8192;
if (setrlimit(RLIMIT_NOFILE, &rlim) == -1) {
PLOG(ERROR) << "setting RLIMIT_NOFILE failed";
}
while ((fs_read_atomic_int("/data/.layout_version", &fs_version) == -1) || (fs_version < 3)) {
LOG(ERROR) << "installd fs upgrade not yet complete; waiting...";
sleep(1);
}
run_sdcardfs(source_path, label, uid, gid, userid, multi_user, full_write, derive_gid,
default_normal, !should_use_sdcardfs());
return 1;
}
注意:
- full_write 是否给与所有用户 w 权限
- default_normal 是否使用default_normal 模式
- should_use_sdcardfs() 是否使用sdcardfs,默认是true
解析从PublicVolume 传过来的参数,最终运行run_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 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;
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, 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, 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, use_esdfs)) {
LOG(FATAL) << "failed to sdcardfs_setup";
}
} else {
// Physical storage is readable by all users on device, but
// the Android directories are masked off to a single user
// deep inside attr_from_stat().
if (!sdcardfs_setup(source_path, dest_path_default, uid, gid, multi_user, userid,
AID_SDCARD_RW, 0006, derive_gid, default_normal, use_esdfs) ||
!sdcardfs_setup_secondary(dest_path_default, source_path, dest_path_read, uid, gid,
multi_user, userid, AID_EVERYBODY, full_write ? 0027 : 0022,
derive_gid, default_normal, use_esdfs) ||
!sdcardfs_setup_secondary(dest_path_default, source_path, dest_path_write, uid, gid,
multi_user, userid, AID_EVERYBODY, full_write ? 0007 : 0022,
derive_gid, default_normal, use_esdfs)) {
LOG(FATAL) << "failed to sdcardfs_setup";
}
}
这里指定了mount sdcard所有参数,其中gid 为AID_EVERYBODY,这就与上面mount 信息对应了。
另外,会根据full_write 确认是否给对应节点写权限。通过() 函数得知对于外置sdcard 并没有传入-w参数,也就是这里的full_write 为false,而通过代码发现对于/mnt/runtime/write/label 的 umask 为0022,也就是group 和other 都没有给w 权限,这就是导致最终出现EACCES 的根本所在了。
不清楚是否Android 认为外置的sdcard 就不让写?还是这是Android 存在的bug?
这个暂时不清楚,等待下一个版本出来确认google 是否会有相应的更改。
当然如果想要修改最开始提到的Permission denied 的问题,修改这里的umask 肯定就可以了!
这里会有个疑问,为什么Android 8.0的时候没有出现外置sdcard 读写权限的问题呢?
对于这个问题,下面会有详细的解释,但在这之前我们来看下应用的对于storage 的gid 是怎么来的!!
4. Storage 的gid 和权限控制
在启动应用的时候我们知道会通过zygote 重新fork 一个进程。
从源码解析-Android中Zygote进程是如何fork一个APP进程的 一文中我们知道zygote fork 进程的整个流程,而在AMS 中就是通过函数startProcessLocked 进入,而其中的变量 gids、mountExternal 会一直跟随函数一直到zygote fork 进程。
来看下source code:
private final boolean startProcessLocked(ProcessRecord app, String hostingType,
String hostingNameStr, boolean disableHiddenApiChecks, String abiOverride) {
...
...
int uid = ;
int[] gids = null;
int mountExternal = Zygote.MOUNT_EXTERNAL_NONE;
if (!) {
int[] permGids = null;
try {
checkTime(startTime, "startProcess: getting gids from package manager");
final IPackageManager pm = ();
permGids = (,
MATCH_DEBUG_TRIAGED_MISSING, );
StorageManagerInternal storageManagerInternal = (
);
mountExternal = (uid,
);
} catch (RemoteException e) {
throw ();
}
...
...
其中gid 是通过PMS 解析获取,应用中使用到的权限涉及的group id 都在这里。
mountExternal 获取的是外置设备mount 类型,分别是:
/** No external storage should be mounted. */
public static final int MOUNT_EXTERNAL_NONE = IVold.REMOUNT_MODE_NONE;
/** Default external storage should be mounted. */
public static final int MOUNT_EXTERNAL_DEFAULT = IVold.REMOUNT_MODE_DEFAULT;
/** Read-only external storage should be mounted. */
public static final int MOUNT_EXTERNAL_READ = IVold.REMOUNT_MODE_READ;
/** Read-write external storage should be mounted. */
public static final int MOUNT_EXTERNAL_WRITE = IVold.REMOUNT_MODE_WRITE;
4.1 mountExternal
先来看下getExternalStorageMountMode():
@Override
public int getExternalStorageMountMode(int uid, String packageName) {
// No locking - CopyOnWriteArrayList
int mountMode = Integer.MAX_VALUE;
for (ExternalStorageMountPolicy policy : mPolicies) {
final int policyMode = (uid, packageName);
if (policyMode == Zygote.MOUNT_EXTERNAL_NONE) {
return Zygote.MOUNT_EXTERNAL_NONE;
}
mountMode = (mountMode, policyMode);
}
if (mountMode == Integer.MAX_VALUE) {
return Zygote.MOUNT_EXTERNAL_NONE;
}
return mountMode;
}
对注册进来的ExternalStorageMountPolicy 进行逐个查询,获取最小的mode 就是最后所需。而查询的函数就是getMountMode(),对于应用所注册的Policy 在PMS 中,详见():
StorageManagerInternal StorageManagerInternal = (
);
(
new () {
@Override
public int getMountMode(int uid, String packageName) {
if ((uid)) {
return Zygote.MOUNT_EXTERNAL_NONE;
}
if (checkUidPermission(READ_EXTERNAL_STORAGE, uid) == PERMISSION_DENIED) {
return Zygote.MOUNT_EXTERNAL_DEFAULT;
}
if (checkUidPermission(WRITE_EXTERNAL_STORAGE, uid) == PERMISSION_DENIED) {
return Zygote.MOUNT_EXTERNAL_READ;
}
return Zygote.MOUNT_EXTERNAL_WRITE;
}
@Override
public boolean hasExternalStorage(int uid, String packageName) {
return true;
}
});
这里就是处理storage 权限的地方,如果READ_EXTERNAL_STORAGE 权限都没有申请,那么默认mount 的mode 为MOUNT_EXTERNAL_DEFAULT (需要特殊权限,例如sdcard_rw);如果只给了READ_EXTERNAL_STORAGE,而没有给WRITE_EXTERNAL_STORAGE,那么mount mode 为MOUNT_EXTERNAL_READ (只读权限);如果两个权限都申请,那么mount mode 为MOUNT_EXTERNAL_WRITE (读写权限)。mount mode 会在下面继续解释。
4.2 startViaZygote()
从源码解析-Android中Zygote进程是如何fork一个APP进程的 一文中得知,()之后会继续调用(),接着是(),并最终调用():
private startViaZygote(final String processClass,
final String niceName,
final int uid, final int gid,
final int[] gids,
int runtimeFlags, int mountExternal,
int targetSdkVersion,
String seInfo,
String abi,
String instructionSet,
String appDataDir,
String invokeWith,
boolean startChildZygote,
String[] extraArgs)
throws ZygoteStartFailedEx {
ArrayList<String> argsForZygote = new ArrayList<String>();
// --runtime-args, --setuid=, --setgid=,
// and --setgroups= must go first
("--runtime-args");
("--setu--setg--runtime-flags=" + runtimeFlags);
if (mountExternal == Zygote.MOUNT_EXTERNAL_DEFAULT) {
("--mount-external-default");
} else if (mountExternal == Zygote.MOUNT_EXTERNAL_READ) {
("--mount-external-read");
} else if (mountExternal == Zygote.MOUNT_EXTERNAL_WRITE) {
("--mount-external-write");
}
("--target-sdk-version=" + targetSdkVersion);
// --setgroups is a comma-separated list
if (gids != null && > 0) {
StringBuilder sb = new StringBuilder();
("--setgroups=");
int sz = ;
for (int i = 0; i < sz; i++) {
if (i != 0) {
(',');
}
(gids[i]);
}
(());
}
...
...
synchronized(mLock) {
return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), argsForZygote);
}
}
根据不同的mount mode 传入不同的参数,最终传入zygote 中进行fork。
从源码解析-Android中Zygote进程是如何fork一个APP进程的 一文得知最后调用():
public static int forkAndSpecialize(int uid, int gid, int[] gids, int runtimeFlags,
int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose,
int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir) {
VM_HOOKS.preFork();
// Resets nice priority for zygote process.
resetNicePriority();
int pid = nativeForkAndSpecialize(
uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose,
fdsToIgnore, startChildZygote, instructionSet, appDataDir);
// Enable tracing as soon as possible for the child process.
if (pid == 0) {
(true, runtimeFlags);
// Note that this event ends at the end of handleChildProc,
(Trace.TRACE_TAG_ACTIVITY_MANAGER, "PostFork");
}
VM_HOOKS.postForkCommon();
return pid;
}
这里nativeForkAndSpecialize() 最后调用到JNI 中com_android_internal_os_Zygote.cpp 中,并触发函数MountEmulatedStorage():
static bool MountEmulatedStorage(uid_t uid, jint mount_mode,
bool force_mount_namespace, std::string* error_msg) {
// See storage config details at /tech/storage/
String8 storageSource;
if (mount_mode == MOUNT_EXTERNAL_DEFAULT) {
storageSource = "/mnt/runtime/default";
} else if (mount_mode == MOUNT_EXTERNAL_READ) {
storageSource = "/mnt/runtime/read";
} else if (mount_mode == MOUNT_EXTERNAL_WRITE) {
storageSource = "/mnt/runtime/write";
} else if (!force_mount_namespace) {
// Sane default of no storage visible
return true;
}
// Create a second private mount namespace for our process
if (unshare(CLONE_NEWNS) == -1) {
*error_msg = CREATE_ERROR("Failed to unshare(): %s", strerror(errno));
return false;
}
// Handle force_mount_namespace with MOUNT_EXTERNAL_NONE.
if (mount_mode == MOUNT_EXTERNAL_NONE) {
return true;
}
if (TEMP_FAILURE_RETRY(mount((), "/storage",
NULL, MS_BIND | MS_REC | MS_SLAVE, NULL)) == -1) {
*error_msg = CREATE_ERROR("Failed to mount %s to /storage: %s",
(),
strerror(errno));
return false;
}
// Mount user-specific symlink helper into place
userid_t user_id = multiuser_get_user_id(uid);
const String8 userSource(String8::format("/mnt/user/%d", user_id));
if (fs_prepare_dir((), 0751, 0, 0) == -1) {
*error_msg = CREATE_ERROR("fs_prepare_dir failed on %s", ());
return false;
}
if (TEMP_FAILURE_RETRY(mount((), "/storage/self",
NULL, MS_BIND, NULL)) == -1) {
*error_msg = CREATE_ERROR("Failed to mount %s to /storage/self: %s",
(),
strerror(errno));
return false;
}
return true;
}
从PMS 中提到的mount mode 就是这里的mount_mode,也就是全程传入的mountExternal 变量。
这里做了两件事:
- 针对给定的user,将指定的/mnt/runtime/read 或/mnt/runtime/write 或/mnt/runtime/default 挂载到/storage目录下;
- 创建对应的user 私有的storage 目录,并将其挂载到/storage/self 下;
5. 与Android 8.0 差异
在8.0 中访问外置sdcard都需要添加上一个权限:
frameworks/base/data/etc/
<permission name=".WRITE_MEDIA_STORAGE" >
<group g />
<group g />
</permission>
申请该权限的应用都会在group id 中添加上media_rw 和 sdcard_rw。
而在Android 9.0 中已经将sdcard_rw 这个group 去掉。
在PMS 注册 ExternalStorageMountPolicy 的地方:
StorageManagerInternal StorageManagerInternal = (
);
(
new () {
@Override
public int getMountMode(int uid, String packageName) {
if ((uid)) {
return Zygote.MOUNT_EXTERNAL_NONE;
}
if (checkUidPermission(WRITE_MEDIA_STORAGE, uid) == PERMISSION_GRANTED) {
return Zygote.MOUNT_EXTERNAL_DEFAULT;
}
if (checkUidPermission(READ_EXTERNAL_STORAGE, uid) == PERMISSION_DENIED) {
return Zygote.MOUNT_EXTERNAL_DEFAULT;
}
if (checkUidPermission(WRITE_EXTERNAL_STORAGE, uid) == PERMISSION_DENIED) {
return Zygote.MOUNT_EXTERNAL_READ;
}
return Zygote.MOUNT_EXTERNAL_WRITE;
}
@Override
public boolean hasExternalStorage(int uid, String packageName) {
return true;
}
});
默认申请了 WRITE_MEDIA_STORAGE 权限后,mount mode 设置为MOUNT_EXTERNAL_DEFAULT,在zygote中也会默认mount 到 /mnt/runtime/default,而该节点的group id 为sdcard_rw,即在sdcard_rw组内的应用都是可以在该节点上读写。
但是,在Android 9.0 上只针对READ_EXTERNAL_STORAGE 和 WRITE_EXTERNAL_STORAGE两个权限进行了处理。
所以,这就是为什么同样申请了这几个权限的应用在Android 8.0 上是可以进行读写,却在Android 9.0 上出现问题。
6. 查看进程信息
方法一:
比如:我们想看文件浏览器是否有media_rw的权限,我们就先看ps,找到文件浏览器的pid
u0_a31 6653 217 702776 60112 SyS_epoll_ b6d21408 S
root 6681 1 786596 26748 futex_wait b6d065ec S app_process
root 6683 1 786596 26700 futex_wait b6ca85ec S app_process
root 6685 1 786596 26724 futex_wait b6d185ec S app_process
然后再去proc/pid下面看,这里的话就是proc/6653,然后可以cat status:
root@lte26007:/proc/6653 # cat status
cat status
Name:
State: S (sleeping)
Tgid: 6653
Pid: 6653
PPid: 217
TracerPid: 0
Uid: 10031 10031 10031 10031
Gid: 10031 10031 10031 10031
FDSize: 256
Groups: 1015 1023 9997 50031
VmPeak: 991756 kB
VmSize: 702776 kB
VmLck: 0 kB
VmPin: 0 kB
VmHWM: 60640 kB
VmRSS: 60112 kB
我们看到有Groups这项,media_rw应该是1023
我们可以使用id命令确认下:
id media_rw
uid=1023(media_rw) gid=1023(media_rw) groups=1023(media_rw), context=u:r:su:s0
确实是1023,这样就确定文件浏览器应用确实有media_rw的权限
msm8940_EVB:/mnt/media_rw # ls -l
total 32
drwxrwx--- 9 media_rw media_rw 32768 2018-12-29 03:53 6344-0FEF
方法二:
我们可以去/system/etc/permissions目录的查看media_rw对应的权限
<permission name=".WRITE_MEDIA_STORAGE" >
<group g />
<group g />
</permission>
然后再去文件浏览器源码中的的文件,如下代码,就知道有该权限
<uses-permission android:name=".WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name=".READ_EXTERNAL_STORAGE" />
<uses-permission android:name=".MOUNT_UNMOUNT_FILESYSTEMS" />
<uses-permission android:name=".WRITE_MEDIA_STORAGE" />
<uses-permission android:name=".WAKE_LOCK"/>
7. 总结
正常使用storage 读写操作,需要注意一下几步:
- 申请对应的storage 权限
在应用的 中注册storage 权限,并且能够grant 这些权限:
<uses-permission android:name=".READ_EXTERNAL_STORAGE"/>
<uses-permission android:name=".WRITE_EXTERNAL_STORAGE"/>
- 确定应用获取到正确的mount mode
目前来说这里同第 1 点,但后期可能会多加权限,例如之前的WRITE_MEDIA_STORAGE
- 确定mount 的节点的用户、用户组权限
进入目录 /mnt/runtime/read 或 /mnt/runtime/write 或/mnt/runtime/default 下确认对应的文件操作权限。
例如,mount mode 明确指向了 /mnt/runtime/write 节点,但是用户组却没有给 w 权限,这就会导致Permission denied错误。
对于Android 9.0 来说,最开始 mount /mnt/runtime/* 是在 中,修改mount 时候的umask 即可。
另一篇博文 论Android 9.0 外置sdcard 读写 对预置应用进行深度的挖掘和讨论。