Android 9.0中sdcard 的权限和挂载问题
前言
Android 从6.0 开始引入了Runtime permission,应用对于storage 进行读取、存储的时候,需要注册、申请对应的权限。Android 8.0中对于sdcard 读写只需要申请权限即可使用,可以在Android 9.0 中同样的应用执行同样的步骤,却提示了Permission denied。
本文将借此对sdcard 进行简单地剖析。代码基于版本Android 9.0
问题描述
1、应用中的代码
private boolean doCreate(File file) { try { File parentFile = file.getParentFile(); if (!isFileExists(parentFile)) { parentFile.mkdirs(); } file.createNewFile(); } catch (IOException e) { e.printStackTrace(); return false; } return true; } private boolean createFile(File file) { if (isFileExists(file)) return true; return doCreate(file); } /** * 通过此函数进行文件创建操作 */ private boolean createFile(String filePath) { Log.d(TAG, "==== createFile, filePath = " + filePath); File file = new File(filePath); return createFile(file); }
如代码,通过createFile() 来进行文件创建操作,因为是测试,其中的filePath 直接写死为:/storage/6344-0FEF
在应用的AndroidManifest.xml 中权限也已经给出:
2、问题log
01-02 05:47:32.711 24318 24318 W System.err: java.io.IOException: Permission denied01-02 05:47:32.711 24318 24318 W System.err: at java.io.UnixFileSystem.createFileExclusively0(Native Method)01-02 05:47:32.711 24318 24318 W System.err: at java.io.UnixFileSystem.createFileExclusively(UnixFileSystem.java:281)01-02 05:47:32.712 24318 24318 W System.err: at java.io.File.createNewFile(File.java:1008)01-02 05:47:32.712 24318 24318 W System.err: at com.shift.test.testfile.TestFileActivity.doCreate(TestFileActivity.java:126)01-02 05:47:32.712 24318 24318 W System.err: at com.shift.test.testfile.TestFileActivity.createFile(TestFileActivity.java:138)01-02 05:47:32.712 24318 24318 W System.err: at com.shift.test.testfile.TestFileActivity.createFile(TestFileActivity.java:145)01-02 05:47:32.713 24318 24318 W System.err: at com.shift.test.testfile.TestFileActivity.createInSdcard(TestFileActivity.java:110)01-02 05:47:32.713 24318 24318 W System.err: at com.shift.test.testfile.TestFileActivity.onClick(TestFileActivity.java:183)01-02 05:47:32.714 24318 24318 W System.err: at android.view.View.performClick(View.java:6597)01-02 05:47:32.714 24318 24318 W System.err: at android.view.View.performClickInternal(View.java:6574)01-02 05:47:32.714 24318 24318 W System.err: at android.view.View.access$3100(View.java:778)01-02 05:47:32.714 24318 24318 W System.err: at android.view.View$PerformClick.run(View.java:25889)01-02 05:47:32.714 24318 24318 W System.err: at android.os.Handler.handleCallback(Handler.java:873)01-02 05:47:32.714 24318 24318 W System.err: at android.os.Handler.dispatchMessage(Handler.java:99)01-02 05:47:32.714 24318 24318 W System.err: at android.os.Looper.loop(Looper.java:193)01-02 05:47:32.714 24318 24318 W System.err: at android.app.ActivityThread.main(ActivityThread.java:6692)01-02 05:47:32.714 24318 24318 W System.err: at java.lang.reflect.Method.invoke(Native Method)01-02 05:47:32.715 24318 24318 W System.err: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)01-02 05:47:32.715 24318 24318 W System.err: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
问题解析
通过log 可以看到最终对于File 的操作调用到UnixFileSystem.createFileExclusively0(),来看下source code:
/* -- File operations -- */ // Android-changed: Added thread policy check public boolean createFileExclusively(String path) throws IOException { BlockGuard.getThreadPolicy().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 checkJNIEXPORT jboolean JNICALLJava_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():
FDhandleOpen(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。
那么导致这个问题的原因,大概就是文件节点的权限给的不够,带着这个想法来看下文件节点。
问题剖析
首先来看下设备的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 -ltotal 36drwxr-xr-x 9 root everybody 32768 2018-12-29 03:53 6344-0FEF
msm8940_EVB:/mnt/runtime/read # ls -ltotal 36drwxr-xr-x 9 root everybody 32768 2018-12-29 03:53 6344-0FEF
msm8940_EVB:/mnt/runtime/default # ls -ltotal 36drwxrwx--x 9 root sdcard_rw 32768 2018-12-29 03:53 6344-0FEF
如果default 节点,group id 是sdcard_rw,而其他两个的group 为everybody,这就跟上面mount 的结果一致了。通过这里其实就可以发现出现sdcard 无法写入是因为mount 的时候并没有给出 w 权限。
PublicVolume
路径:system/vold/model/PublicVolume.cpp
sdcard 挂载的时候 vold 会调用到 PublicVolume 中的doMount():
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/sdcard.cpp:
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 确认是否给对应节点写权限。通过PublicVolume.domount() 函数得知对于外置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 是怎么来的!!
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 = app.uid; int[] gids = null; int mountExternal = Zygote.MOUNT_EXTERNAL_NONE; if (!app.isolated) { int[] permGids = null; try { checkTime(startTime, "startProcess: getting gids from package manager"); final IPackageManager pm = AppGlobals.getPackageManager(); permGids = pm.getPackageGids(app.info.packageName, MATCH_DEBUG_TRIAGED_MISSING, app.userId); StorageManagerInternal storageManagerInternal = LocalServices.getService( StorageManagerInternal.class); mountExternal = storageManagerInternal.getExternalStorageMountMode(uid, app.info.packageName); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } ... ...
其中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;
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 = policy.getMountMode(uid, packageName); if (policyMode == Zygote.MOUNT_EXTERNAL_NONE) { return Zygote.MOUNT_EXTERNAL_NONE; } mountMode = Math.min(mountMode, policyMode); } if (mountMode == Integer.MAX_VALUE) { return Zygote.MOUNT_EXTERNAL_NONE; } return mountMode; }
对注册进来的ExternalStorageMountPolicy 进行逐个查询,获取最小的mode 就是最后所需。而查询的函数就是getMountMode(),对于应用所注册的Policy 在PMS 中,详见PMS.systemReady():
StorageManagerInternal StorageManagerInternal = LocalServices.getService( StorageManagerInternal.class); StorageManagerInternal.addExternalStoragePolicy( new StorageManagerInternal.ExternalStorageMountPolicy() { @Override public int getMountMode(int uid, String packageName) { if (Process.isIsolated(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 会在下面继续解释。
startViaZygote()
从源码解析-Android中Zygote进程是如何fork一个APP进程的 一文中得知,AMS.startProcessLocked()之后会继续调用AMS.startProcess(),接着是ZygoteProcess.start(),并最终调用ZygoteProcess.startViaZygote():
private Process.ProcessStartResult 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 argsForZygote = new ArrayList(); // --runtime-args, --setuid=, --setgid=, // and --setgroups= must go first argsForZygote.add("--runtime-args"); argsForZygote.add("--setuid=" + uid); argsForZygote.add("--setgid=" + gid); argsForZygote.add("--runtime-flags=" + runtimeFlags); if (mountExternal == Zygote.MOUNT_EXTERNAL_DEFAULT) { argsForZygote.add("--mount-external-default"); } else if (mountExternal == Zygote.MOUNT_EXTERNAL_READ) { argsForZygote.add("--mount-external-read"); } else if (mountExternal == Zygote.MOUNT_EXTERNAL_WRITE) { argsForZygote.add("--mount-external-write"); } argsForZygote.add("--target-sdk-version=" + targetSdkVersion); // --setgroups is a comma-separated list if (gids != null && gids.length > 0) { StringBuilder sb = new StringBuilder(); sb.append("--setgroups="); int sz = gids.length; for (int i = 0; i < sz; i++) { if (i != 0) { sb.append(','); } sb.append(gids[i]); } argsForZygote.add(sb.toString()); } ... ... synchronized(mLock) { return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), argsForZygote); } }
根据不同的mount mode 传入不同的参数,最终传入zygote 中进行fork。
从源码解析-Android中Zygote进程是如何fork一个APP进程的 一文得知最后调用zygote.forkAndSpecialize():
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) { Trace.setTracingEnabled(true, runtimeFlags); // Note that this event ends at the end of handleChildProc, Trace.traceBegin(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 http://source.android.com/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(storageSource.string(), "/storage", NULL, MS_BIND | MS_REC | MS_SLAVE, NULL)) == -1) { *error_msg = CREATE_ERROR("Failed to mount %s to /storage: %s", storageSource.string(), 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(userSource.string(), 0751, 0, 0) == -1) { *error_msg = CREATE_ERROR("fs_prepare_dir failed on %s", userSource.string()); return false; } if (TEMP_FAILURE_RETRY(mount(userSource.string(), "/storage/self", NULL, MS_BIND, NULL)) == -1) { *error_msg = CREATE_ERROR("Failed to mount %s to /storage/self: %s", userSource.string(), 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 下;
与Android 8.0 差异
在8.0 中访问外置sdcard都需要添加上一个权限:
申请该权限的应用都会在group id 中添加上media_rw 和 sdcard_rw。
详见frameworks/base/data/etc/platform.xml
而在Android 9.0 中已经将sdcard_rw 这个group 去掉。
在PMS 注册ExternalStorageMountPolicy的地方:
StorageManagerInternal StorageManagerInternal = LocalServices.getService( StorageManagerInternal.class); StorageManagerInternal.addExternalStoragePolicy( new StorageManagerInternal.ExternalStorageMountPolicy() { @Override public int getMountMode(int uid, String packageName) { if (Process.isIsolated(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 上出现问题。
查看进程信息
方法一:
比如:我们想看文件浏览器是否有media_rw的权限,我们就先看ps,找到文件浏览器的pid
u0_a31 6653 217 702776 60112 SyS_epoll_ b6d21408 S com.android.fileexplorerroot 6681 1 786596 26748 futex_wait b6d065ec S app_processroot 6683 1 786596 26700 futex_wait b6ca85ec S app_processroot 6685 1 786596 26724 futex_wait b6d185ec S app_process
然后再去proc/pid下面看,这里的话就是proc/6653,然后可以cat status:
root@lte26007:/proc/6653 # cat statuscat statusName: id.fileexplorerState: S (sleeping)Tgid: 6653Pid: 6653PPid: 217TracerPid: 0Uid: 10031 10031 10031 10031Gid: 10031 10031 10031 10031FDSize: 256Groups: 1015 1023 9997 50031VmPeak: 991756 kBVmSize: 702776 kBVmLck: 0 kBVmPin: 0 kBVmHWM: 60640 kBVmRSS: 60112 kB
我们看到有Groups这项,media_rw应该是1023
我们可以使用id命令确认下:
id media_rwuid=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 -ltotal 32drwxrwx--- 9 media_rw media_rw 32768 2018-12-29 03:53 6344-0FEF
方法二:
我们可以去/system/etc/permissions目录的platform.xml查看media_rw对应的权限
然后再去文件浏览器源码中的的AndroidManifest.xml文件,如下代码,就知道有该权限
总结:
正常使用storage 读写操作,需要注意一下几步:
1、申请对应的storage 权限
在应用的AndroidManifest.xml 中注册storage 权限,并且能够grant 这些权限:
2、确定应用获取到正确的mount mode
目前来说这里同第 1 点,但后期可能会多加权限,例如之前的WRITE_MEDIA_STORAGE
3、确定mount 的节点的用户、用户组权限
进入目录/mnt/runtime/read 或 /mnt/runtime/write 或/mnt/runtime/default 下确认对应的文件操作权限。
例如,mount mode 明确指向了 /mnt/runtime/write 节点,但是用户组却没有给 w 权限,这就会导致Permission denied错误。
对于Android 9.0 来说,最开始mount /mnt/runtime/* 是在sdcard.cpp 中,修改mount 时候的umask 即可。
另一篇博文 论Android 9.0 外置sdcard 读写 对预置应用进行深度的挖掘和讨论。
更多相关文章
- Android赋予内置三方应用应用权限
- Android清单文件详解(六) ---- 节点的属性
- Android中应用调用系统权限
- Android 获取ROOT权限原理解析
- Android权限系统
- Android 悬浮窗权限各机型各系统适配大全(总结)
- Android app获取android.permission.BATTERY_STATS权限
- Android危险权限和权限组
- Android 6.0后强制弹出权限