Android 9.0中sdcard 的权限和挂载问题

时间:2024-11-09 08:50:05

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