Android 9.0中sdcard 的权限和挂载问题
阅读原文时间:2021年04月20日阅读:1

前言

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 中权限也已经给出:

    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

2、问题log

01-02 05:47:32.711 24318 24318 W System.err: java.io.IOException: Permission denied
01-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 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。

那么导致这个问题的原因,大概就是文件节点的权限给的不够,带着这个想法来看下文件节点。

问题剖析

首先来看下设备的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 权限。

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/labelumask 为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<String> argsForZygote = new ArrayList<String>();

        // --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都需要添加上一个权限:

    <permission name="android.permission.WRITE_MEDIA_STORAGE" >
        <group gid="media_rw" />
        <group gid="sdcard_rw" />
    </permission>

申请该权限的应用都会在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.fileexplorer
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:   id.fileexplorer
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目录的platform.xml查看media_rw对应的权限

    <permission name="android.permission.WRITE_MEDIA_STORAGE" >
        <group gid="media_rw" />
        <group gid="sdcard_rw" />
    </permission>

然后再去文件浏览器源码中的的AndroidManifest.xml文件,如下代码,就知道有该权限

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
    <uses-permission android:name="android.permission.WRITE_MEDIA_STORAGE" />
    <uses-permission android:name="android.permission.WAKE_LOCK"/>

总结:

正常使用storage 读写操作,需要注意一下几步:

1、申请对应的storage 权限

在应用的AndroidManifest.xml 中注册storage 权限,并且能够grant 这些权限:

    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

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