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-0 FEFとして直接書かれています.
 
アプリケーションのAndroidManifestでxmlでは、次の権限も与えられています.
    
    

 
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)

 
もんだいかいせき
最終的にFileに対する操作がUnixFileSystemに呼び出されることがlogでわかる.createFileExclusively 0()では、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メソッドが呼び出されます.
詳細は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;
}

Open 64はライブラリ関数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ノードの場合groupidはsdcard_rw,他の2つの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";
            }
        }

    ここでは、gidがAID_であるmount sdcardのすべてのパラメータを指定します.EVERYBODY、これは上のmount情報に対応しています.
    また、full_によりwriteは、対応するノードに権限を書き込むかどうかを確認します.PublicVolume.domount()関数は、外付けsdcardに対して-wパラメータが入力されていないことを知っています.つまり、ここのfull_writeはfalseであり、/mnt/runtime/write/labelに対するumaskは0022、すなわちgroupとotherにw権限が与えられていないことがコードによって発見され、これが最終的にEACCESが現れる根本的な場所となった.
     
    Androidは外付けのsdcardを書かせないと思っているのか分からない.それともAndroidが存在するバグなのでしょうか?
    これはしばらく分かりませんが、次のバージョンが出てきてgoogleが相応の変更があるかどうかを確認するのを待っています.
    もちろん最初に言ったPermission deniedの問題を修正したいなら、ここのumaskを修正すれば間違いありません!
     
    ここで疑問に思うのは、なぜAndroid 8.0の時にsdcardの読み書き権限を外付けしていないのかという問題です.
    この問題については、以下で詳しく説明しますが、その前にstorageに対するgidがどのように来たのかを見てみましょう!!
     
    Storageのgidと権限制御
    アプリケーションを起動すると、zygoteを通じてプロセスを再forkすることを知っています.
    ソース解析-AndroidのZygoteプロセスがどのようにアプリプロセスをforkするかという文から、zygote forkプロセスのプロセス全体を知っていますが、AMSでは関数startProcessLockedを通じて入り、変数gids、mountExternalはzygote forkプロセスまで関数に従います.
    ソースコードを見てみましょう
        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を1つずつクエリーし、最小の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しているかの一文から、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するかの一文から、最後に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変数である.
    ここでは2つのことをしました.
  • 所与の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このグループは削除します.
     
    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の2つの権限が処理されました.
     
    そのため、同じようにこれらの権限を申請したアプリケーションがAndroid 8.0で読み書きができるのに、Android 9.0で問題が発生したのです.
     
    プロセス情報の表示
    方法1:
    例えば、ファイルブラウザに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があるのを見ました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

     
    方法2:
    私たちは/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読み書きは、プリセットアプリケーションを深く掘り起こし、議論している.