前言

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 读写 对预置应用进行深度的挖掘和讨论。

 

 

更多相关文章

  1. Android赋予内置三方应用应用权限
  2. Android清单文件详解(六) ---- 节点的属性
  3. Android中应用调用系统权限
  4. Android 获取ROOT权限原理解析
  5. Android权限系统
  6. Android 悬浮窗权限各机型各系统适配大全(总结)
  7. Android app获取android.permission.BATTERY_STATS权限
  8. Android危险权限和权限组
  9. Android 6.0后强制弹出权限

随机推荐

  1. 接口之命令模式
  2. 如何在2D阵列中找到局部最小值?
  3. Java中累计时间的计算(以小时为最终结果)
  4. Java读取Unicode文件(UTF-8等)时碰到的BOM
  5. java类与对象,用程序解释
  6. idea配置完tomcat启动键为什么还是灰的
  7. java数组的拷贝四种方法:for、clone、Syst
  8. “java.exe”已退出,代码为 1。
  9. 经典算法问题的java实现
  10. java高并发测试实例(精确到几百纳秒)