欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android存储系统-外置存储权权限管理实现

程序员文章站 2022-06-28 15:07:28
前言在Android存储系统-使用fuse来管理外置存储 一文中我们介绍了Android fuse 来实现灵活的权限管理,我们知道获取不同的外置存储权限使用的外置存储目录是不同的, 获取外置存储读权限的应用程序看到的fuse目录为/mnt/runtime/read/${label}目录,获取了外置存储写权限的应用程序使用的是fuse的/mnt/runtime/write/${label}目录,而没有获取任何外置...

前言

        在Android存储系统-使用fuse来管理外置存储 一文中我们介绍了Android fuse 来实现灵活的权限管理,我们知道获取不同的外置存储权限使用的外置存储目录是不同的, 获取外置存储读权限的应用程序看到的fuse目录为/mnt/runtime/read/${label}目录,获取了外置存储写权限的应用程序使用的是fuse的/mnt/runtime/write/${label}目录,而没有获取任何外置存储权限的应用看到的fuse目录为/mnt/runtime/default/${label}, 这一点是如何做到的呢? 在权限组管理方面,是如何将应用程序添加到AID_EVERYBODY组的呢? 外置存储权限又是如何动态授权和撤销的呢? 今天我们就来说明这个问题。

Android 设计

        Android在启动应用程序进程的时候,会通过PackageManagerService 来查询应用所属的组以及是否获取了应用外置存储权限。这些信息都会当做参数传递给zygote服务, zygote服务 fork应用进程后,根据参数中的组信息来设置应用进程所属于的组。再根据不同的外置存储权限,利用Linux的mount bind技术,将对应的fuse目录挂载到外置存储目录,这样以后读写外置存储目录,实际上写入到了fuse目录,这样就利用到了fuse权限管理能力。 另外Android还使用的命名空间技术, 每个应用使用主空间的一个副本,当应用程序外置存储权限改变的时候,只需要重新挂载该命名空间即可, 好了不多说,直接看代码。

代码分析

        Andrioid 启动应用的时候会在ActivityManagerService中调用startProcessLocked函数,代码如下:

frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

    private final void startProcessLocked(ProcessRecord app, String hostingType,
            String hostingNameStr, String abiOverride, String entryPoint, String[] entryPointArgs) {
        ......
        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);
                    MountServiceInternal mountServiceInternal = LocalServices.getService(
                            MountServiceInternal.class);
                    mountExternal = mountServiceInternal.getExternalStorageMountMode(uid,
                            app.info.packageName);
                } catch (RemoteException e) {
                    throw e.rethrowAsRuntimeException();
                }
    ......
   Process.ProcessStartResult startResult = Process.start(entryPoint,
               app.processName, uid, uid, gids, debugFlags, mountExternal,
                app.info.targetSdkVersion, app.info.seinfo, requiredAbi, instructionSet,
                app.info.dataDir, entryPointArgs);
......

        应用进程启动之前要准备一系列参数,这些参数最终会传递给zygote进程,用于fork应用进程。这里面有关外置存储权限管理的两个参数就是permGids和mountExternal。我们先来看permGids如何获取。

frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

    @Override
    public int[] getPackageGids(String packageName, int flags, int userId) {
        if (!sUserManager.exists(userId)) return null;
        flags = updateFlagsForPackage(flags, userId, packageName);
        enforceCrossUserPermission(Binder.getCallingUid(), userId,
                false /* requireFullPermission */, false /* checkShell */,
                "getPackageGids");

        // reader
        synchronized (mPackages) {
            final PackageParser.Package p = mPackages.get(packageName);
            if (p != null && p.isMatch(flags)) {
                PackageSetting ps = (PackageSetting) p.mExtras;
                return ps.getPermissionsState().computeGids(userId);
            }
            if ((flags & MATCH_UNINSTALLED_PACKAGES) != 0) {
                final PackageSetting ps = mSettings.mPackages.get(packageName);
                if (ps != null && ps.isMatch(flags)) {
                    return ps.getPermissionsState().computeGids(userId);
                }
            }
        }

        return null;
    }

        getPackageGids函数通过权限管理计算gid,也就是ps.getPermissionsState().computeGids(userId)这个函数。

frameworks/base/services/core/java/com/android/server/pm/PermissionsState.java

427     /**
428      * Compute the Linux gids for a given device user from the permissions
429      * granted to this user. Note that these are computed to avoid additional
430      * state as they are rarely accessed.
431      *
432      * @param userId The device user id.
433      * @return The gids for the device user.
434      */
435     public int[] computeGids(int userId) {
436         enforceValidUserId(userId);
437 
438         int[] gids = mGlobalGids;
439 
440         if (mPermissions != null) {
441             final int permissionCount = mPermissions.size();
442             for (int i = 0; i < permissionCount; i++) {
443                 String permission = mPermissions.keyAt(i);
444                 if (!hasPermission(permission, userId)) {
445                     continue;
446                 }
447                 PermissionData permissionData = mPermissions.valueAt(i);
448                 final int[] permGids = permissionData.computeGids(userId);
449                 if (permGids != NO_GIDS) {
450                     gids = appendInts(gids, permGids);
451                 }
452             }
453         }
454
455         return gids;
456     }
                                                  

        447-451行如果应用获取了权限,就看下该权限是否关联了gid,如果关联了gid就放在返回结果的数组里面。最后返回应用获取的所有权限关联的gid,那么哪些权限会关联gid呢,在PackageManagerService构造函数中会进行初始化。注意这里mGroupGids是应用默认的gid,也在PackageManagerService构造函数中设置。

frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

    public PackageManagerService(Context context, Installer installer,
            boolean factoryTest, boolean onlyCore) {
       ......
       SystemConfig systemConfig = SystemConfig.getInstance();
        mGlobalGids = systemConfig.getGlobalGids();
       ......
       // Propagate permission configuration in to package manager.
            ArrayMap<String, SystemConfig.PermissionEntry> permConfig
                    = systemConfig.getPermissions();
            for (int i=0; i<permConfig.size(); i++) {
                SystemConfig.PermissionEntry perm = permConfig.valueAt(i);
                BasePermission bp = mSettings.mPermissions.get(perm.name);
                if (bp == null) {
                    bp = new BasePermission(perm.name, "android", BasePermission.TYPE_BUILTIN);
                    mSettings.mPermissions.put(perm.name, bp);
                }
                if (perm.gids != null) {
                    bp.setGids(perm.gids, perm.perUser);
                }
            }
         ......
   }

        这里可以看出,gid是从systemConfig.getPermissions()取出来的, systemConfig是SystemConfig 类的实例。

frameworks/base/core/java/com/android/server/SystemConfig.java

    public ArrayMap<String, PermissionEntry> getPermissions() {
        return mPermissions;
    }

    SystemConfig() {
        // Read configuration from system
        readPermissions(Environment.buildPath(
                Environment.getRootDirectory(), "etc", "sysconfig"), ALLOW_ALL);
        // Read configuration from the old permissions dir
        readPermissions(Environment.buildPath(
                Environment.getRootDirectory(), "etc", "permissions"), ALLOW_ALL);
        // Allow ODM to customize system configs around libs, features and apps
        int odmPermissionFlag = ALLOW_LIBS | ALLOW_FEATURES | ALLOW_APP_CONFIGS;
        readPermissions(Environment.buildPath(
                Environment.getOdmDirectory(), "etc", "sysconfig"), odmPermissionFlag);
        readPermissions(Environment.buildPath(
                Environment.getOdmDirectory(), "etc", "permissions"), odmPermissionFlag);
        // Only allow OEM to customize features
        readPermissions(Environment.buildPath(
                Environment.getOemDirectory(), "etc", "sysconfig"), ALLOW_FEATURES);
        readPermissions(Environment.buildPath(
                Environment.getOemDirectory(), "etc", "permissions"), ALLOW_FEATURES);
    }

        SystemConfig初始化过程会读取一些列文件,来获取权限和gid的关系,举个例子,在我的手机上
/system/etc/permissions/platform.xml文件内容如下。

    <assign-permission name="android.permission.MODIFY_AUDIO_SETTINGS" uid="media" />
    <assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="media" />
    <assign-permission name="android.permission.WAKE_LOCK" uid="media" />
    <assign-permission name="android.permission.UPDATE_DEVICE_STATS" uid="media" />
    <assign-permission name="android.permission.UPDATE_APP_OPS_STATS" uid="media" />
    <assign-permission name="android.permission.GET_PROCESS_STATE_AND_OOM_SCORE" uid="media" />
    <assign-permission name="android.permission.INTERNET" uid="media" />

    <assign-permission name="android.permission.INTERNET" uid="shell" />
    <permission name="android.permission.WRITE_MEDIA_STORAGE" >
        <group gid="media_rw" />
    </permission>

<split-permission name="android.permission.WRITE_EXTERNAL_STORAGE">
        <new-permission name="android.permission.READ_EXTERNAL_STORAGE" />
    </split-permission>
    <split-permission name="android.permission.WRITE_EXTERNAL_STORAGE">
        <new-permission name="android.permission.READ_EXTERNAL_STORAGE" />
    </split-permission>

        android.permission.WRITE_MEDIA_STORAGE 权限关联的gid是media_rw。 具体读文件的代码就不分析了,感兴趣的读者可以自行分析下。 这里我们可以看到读写外置存储不关联任何组。android.permission.WRITE_MEDIA_STORAGE 权限关联的gid是media_rw,是为了兼容老版本。老版本Android 声明WRITE_MEDIA_STORAGE权限就可以读写外置存储是因为它属于media_rw组。SystemConfig.mGlobalGids里面包含AID_EVERYBODY。读过Android存储系统-使用fuse来管理外置存储都是知道android的fuse管理外置存储使用AID_EVERYBODY作为属主提供读写权限。

         到现在我们知道了应用进程启动的gids参数如何获取,下面来看下mountExternal参数如何获取。

frameworks/base/services/core/java/com/android/server/MountService.java

        @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;
        }

         mountMode主要通过ExternalStorageMountPolicy中获取, 其中一个policy就是PackageManagerService。

frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

    @Override
    public void systemReady() {
     mountServiceInternal.addExternalStoragePolicy(
                new MountServiceInternal.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权限则mountMode 为 MOUNT_EXTERNAL_DEFAULT,没有获取读权限则mountMode 为MOUNT_EXTERNAL_DEFAULT, 获得了读外置存储权限但是没有获得写外置存户权限则mountMode 为MOUNT_EXTERNAL_READ, 获得了读和写外置存储权限则mountMode 为 MOUNT_EXTERNAL_READ。 到此位置启动应用程序的mountExternal权限也有了。 下面来看下进程启动过程怎么设置参数。

frameworks/base/core/java/android/os/Process.java

                                  final String niceName,
                                  int uid, int gid, int[] gids,
                                  int debugFlags, int mountExternal,
                                  int targetSdkVersion,
                                  String seInfo,
                                  String abi,
                                  String instructionSet,
                                  String appDataDir,
                                  String[] zygoteArgs) {
        try {
            return startViaZygote(processClass, niceName, uid, gid, gids,
                    debugFlags, mountExternal, targetSdkVersion, seInfo,
                    abi, instructionSet, appDataDir, zygoteArgs);
        } catch (ZygoteStartFailedEx ex) {
            Log.e(LOG_TAG,
                    "Starting VM process through Zygote failed");
            throw new RuntimeException(
                    "Starting VM process through Zygote failed", ex);
        }
    }

    private static ProcessStartResult startViaZygote(final String processClass,
                                  final String niceName,
                                  final int uid, final int gid,
                                  final int[] gids,
                                  int debugFlags, int mountExternal,
                                  int targetSdkVersion,
                                  String seInfo,
                                  String abi,
                                  String instructionSet,
                                  String appDataDir,
                                  String[] extraArgs)
                                  throws ZygoteStartFailedEx {
        synchronized(Process.class) {
            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);
            ......
            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");
            }
            ......
            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());
            }

        ......
            return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), argsForZygote);
        }
    }

        --setgroups= 参数指定gids, 多个gid使用逗号分割。-mount-external-read参数表示应用获取到了读外置存储权限。–mount-external-write参数表示应用获得了写外置存储权限,–mount-external-default表示应用没有获取读写外置存储权限。

         调用zygote进程的过程我们就不分析了,下面来看下zygote如何使用上述参数。

// Utility routine to fork zygote and specialize the child process.
static pid_t ForkAndSpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray javaGids,
                                     jint debug_flags, jobjectArray javaRlimits,
                                     jlong permittedCapabilities, jlong effectiveCapabilities,
                                     jint mount_external,
                                     jstring java_se_info, jstring java_se_name,
                                     bool is_system_server, jintArray fdsToClose,
                                     jstring instructionSet, jstring dataDir) {
    ......
  pid_t pid = fork();
  if (pid == 0) {
        
    if (!MountEmulatedStorage(uid, mount_external, use_native_bridge)) {
      ALOGW("Failed to mount emulated storage: %s", strerror(errno));
      if (errno == ENOTCONN || errno == EROFS) {
        // When device is actively encrypting, we get ENOTCONN here
        // since FUSE was mounted before the framework restarted.
        // When encrypted device is booting, we get EROFS since
        // FUSE hasn't been created yet by init.
        // In either case, continue without external storage.
      } else {
        RuntimeAbort(env, __LINE__, "Cannot continue without emulated storage");
      }
    }
  }
  ......
  SetGids(env, javaGids);
  ......
}

         zygote 在fork子进程后,子进程还有特权,先调用MountEmulatedStorage函数来挂载外置存储,然后调用SetGids来设置进程属于的组。我们先看MountEmulatedStorage函数。

296 // Create a private mount namespace and bind mount appropriate emulated
297 // storage for the given user.
298 static bool MountEmulatedStorage(uid_t uid, jint mount_mode,
299         bool force_mount_namespace) {
300     // See storage config details at http://source.android.com/tech/storage/
301 
302     // Create a second private mount namespace for our process
303     if (unshare(CLONE_NEWNS) == -1) {  // 1 创建一个私有的mount命令空间,脱离原来共享的命名空间。
304         ALOGW("Failed to unshare(): %s", strerror(errno));
305         return false;
306     }
307 
308     String8 storageSource;
309     if (mount_mode == MOUNT_EXTERNAL_DEFAULT) { //2 根据应用获取的权限选择使用的fuse目录。
310         storageSource = "/mnt/runtime/default";
311     } else if (mount_mode == MOUNT_EXTERNAL_READ) {
312         storageSource = "/mnt/runtime/read";
313     } else if (mount_mode == MOUNT_EXTERNAL_WRITE) {
314         storageSource = "/mnt/runtime/write";
315     } else {
316         // Sane default of no storage visible
317         return true;
318     }
319     if (TEMP_FAILURE_RETRY(mount(storageSource.string(), "/storage",   // 3 bind /storage 到 storageSource
320             NULL, MS_BIND | MS_REC | MS_SLAVE, NULL)) == -1) {
321         ALOGW("Failed to mount %s to /storage: %s", storageSource.string(), strerror(errno));
322         return false;
323     }
324 
325     // Mount user-specific symlink helper into place
326     userid_t user_id = multiuser_get_user_id(uid);
327     const String8 userSource(String8::format("/mnt/user/%d", user_id));
328     if (fs_prepare_dir(userSource.string(), 0751, 0, 0) == -1) {
329         return false;
330     }
331     if (TEMP_FAILURE_RETRY(mount(userSource.string(), "/storage/self", // 4 /storage/self 指向 /mnt/user/\${userid}
332             NULL, MS_BIND, NULL)) == -1) {
333         ALOGW("Failed to mount %s to /storage/self: %s", userSource.string(), strerror(errno));
334         return false;
335     }
336 
337     return true;
338 }

         上述代码分为四个步骤:
1、创建一个私有的mount命令空间,脱离原来共享的命名空间。
2、根据应用获取的权限选择使用的fuse目录。
3、 /storage 目录bind到 storageSource, 也就是之后操作/storage目录实际上都是操作storageSource。
4、/storage/self 目录bind 到 /mnt/user/${userid}

         结合我们前面的Android存储系统-MountService 和vold 对外置存储的管理(3)Android存储系统-使用fuse来管理外置存储 我们可以得到一个完整的应用进程视角的目录结构。

/sdcard -> /storage/self/primary (软链接) system/core/rootdir/init.rc 中设置

/mnt/user/${userid}/primary -> /storage/emulated/${userid}/ ( 软链接) vold中用户创建时设置

/storage/self/ -> /mnt/user/${userid}/(mount bind) zygote MountEmulatedStorage 函数中设置

/storage/ -> /mnt/runtime/[default|read|write]/ ( mount bind) zygote MountEmulatedStorage 函数中设置

/mnt/runtime/[default|read|write]/emulated -> /data/media (fuse) (可能label不是emulated,这里只是拿emulated举例)。

         最终看到的主存储结构如下

/sdcard - > /storage/self/primary -> /storage/emulated/${userid}/ -> /mnt/runtime/[default|read|write]/emulated/${userid}/ -> /data/media/${userid}

        所以应用读写/sdcard目录会转到读写主存储,读写主存储会转到读写/storage/${primary_label}/${userid}/ , 读写 /storage/${primary_label}/${userid}/会被转到读写/mnt/runtime/[default|read|write]/${primary_label}/${userid}/ 这个fuse文件目录,然后fuse又把实际操作转到 主存储路径下的 media/${userid} 目录下。

        再来看下SetGids函数

// Calls POSIX setgroups() using the int[] object as an argument.
// A NULL argument is tolerated.
static void SetGids(JNIEnv* env, jintArray javaGids) {
  if (javaGids == NULL) {
    return;
  }

  ScopedIntArrayRO gids(env, javaGids);
  if (gids.get() == NULL) {
    RuntimeAbort(env, __LINE__, "Getting gids int array failed");
  }
  int rc = setgroups(gids.size(), reinterpret_cast<const gid_t*>(&gids[0]));
  if (rc == -1) {
    std::ostringstream oss;
    oss << "setgroups failed: " << strerror(errno) << ", gids.size=" << gids.size();
    RuntimeAbort(env, __LINE__, oss.str().c_str());
  }
}

        实际调用setgroups来完成设置。

        最后我们来看下动态授予外置存储权限如何实现:

frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

    @Override
    public void grantRuntimePermission(String packageName, String name, final int userId) {
        ......
         // Only need to do this if user is initialized. Otherwise it's a new user
        // and there are no processes running as the user yet and there's no need
        // to make an expensive call to remount processes for the changed permissions.
        if (READ_EXTERNAL_STORAGE.equals(name)
                || WRITE_EXTERNAL_STORAGE.equals(name)) {
            final long token = Binder.clearCallingIdentity();
            try {
                if (sUserManager.isInitialized(userId)) {
                    MountServiceInternal mountServiceInternal = LocalServices.getService(
                            MountServiceInternal.class);
                    mountServiceInternal.onExternalStoragePolicyChanged(uid, packageName);
                }
            } finally {
                Binder.restoreCallingIdentity(token);
            }
        }
    }

        应用授权后如果该权限是读外置存储权限或者写外置存储权限,就会调用mountServiceInternal.onExternalStoragePolicyChanged(uid, packageName) 通过MountService。

frameworks/base/services/core/java/com/android/server/MountService.java

    private void remountUidExternalStorage(int uid, int mode) {
        waitForReady();

        String modeName = "none";
        switch (mode) {
            case Zygote.MOUNT_EXTERNAL_DEFAULT: {
                modeName = "default";
            } break;

            case Zygote.MOUNT_EXTERNAL_READ: {
                modeName = "read";
            } break;

            case Zygote.MOUNT_EXTERNAL_WRITE: {
                modeName = "write";
            } break;
        }

        try {
            mConnector.execute("volume", "remount_uid", uid, modeName);
        } catch (NativeDaemonConnectorException e) {
            Slog.w(TAG, "Failed to remount UID " + uid + " as " + modeName + ": " + e);
        }
    }

        MountService 给vold进程发送一个remount_uid命令。关于vold进程的消息处理细节在Android存储系统-MountService 和vold 对外置存储的管理(1) 这篇文章,我们直接看下vold怎样处理的remount_uid命令。

 508 int VolumeManager::remountUid(uid_t uid, const std::string& mode) {
 509     LOG(DEBUG) << "Remounting " << uid << " as mode " << mode;
 510 
 511     DIR* dir;
 512     struct dirent* de;
 513     char rootName[PATH_MAX];
 514     char pidName[PATH_MAX];
 515     int pidFd;
 516     int nsFd;
 517     struct stat sb;
 518     pid_t child;
 519 
 520     if (!(dir = opendir("/proc"))) {
 521         PLOG(ERROR) << "Failed to opendir";
 522         return -1;
 523     }
 524 
 525     // Figure out root namespace to compare against below
 526     if (android::vold::SaneReadLinkAt(dirfd(dir), "1/ns/mnt", rootName, PATH_MAX) == -1) { // 1. 读/proc/1/ns/mnt, 获取init进程的mount空间名称保存到rootName
 527         PLOG(ERROR) << "Failed to readlink";
 528         closedir(dir);
 529         return -1;
 530     }
 531 
 532     // Poke through all running PIDs look for apps running as UID
 533     while ((de = readdir(dir))) { // 遍历/proc/ 下所有文件夹
 534         pidFd = -1;
 535         nsFd = -1;
 536 
 537         pidFd = openat(dirfd(dir), de->d_name, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
 538         if (pidFd < 0) {
 539             goto next;
 540         }
 541         if (fstat(pidFd, &sb) != 0) {
 542             PLOG(WARNING) << "Failed to stat " << de->d_name;
 543             goto next;
 544         }
 545         if (sb.st_uid != uid) { // 2 获取文件夹的uid
 546             goto next;
 547         }
 548 
 549         // Matches so far, but refuse to touch if in root namespace
 550         LOG(DEBUG) << "Found matching PID " << de->d_name;
 551         if (android::vold::SaneReadLinkAt(pidFd, "ns/mnt", pidName, PATH_MAX) == -1) { //3 读取/proc/${x}/ns/mnt,获取进程的mount空间
 552             PLOG(WARNING) << "Failed to read namespace for " << de->d_name;
 553             goto next;
 554         }
 555         if (!strcmp(rootName, pidName)) { //4 直接跳过mount空间为rootName的空间
 556             LOG(WARNING) << "Skipping due to root namespace";
 557             goto next;
 558         }
 559 
 560         // We purposefully leave the namespace open across the fork
 561         nsFd = openat(pidFd, "ns/mnt", O_RDONLY);
 562         if (nsFd < 0) {
 563             PLOG(WARNING) << "Failed to open namespace for " << de->d_name;
 564             goto next;
560         // We purposefully leave the namespace open across the fork
 561         nsFd = openat(pidFd, "ns/mnt", O_RDONLY);  // 4 打开命名空间
 562         if (nsFd < 0) {
 563             PLOG(WARNING) << "Failed to open namespace for " << de->d_name;
 564             goto next;
 565         }
 566 
 567         if (!(child = fork())) {
 568             if (setns(nsFd, CLONE_NEWNS) != 0) { // 5 设置下面后续操作的mount空间
 569                 PLOG(ERROR) << "Failed to setns for " << de->d_name;
 570                 _exit(1);
 571             }
 572             //6 后续操作和 zygote::MountEmulatedStorage() 基本一致 
 573             unmount_tree("/storage");  
 574 
 575             std::string storageSource;
 576             if (mode == "default") {
 577                 storageSource = "/mnt/runtime/default";
 578             } else if (mode == "read") {
 579                 storageSource = "/mnt/runtime/read";
 580             } else if (mode == "write") {
 581                 storageSource = "/mnt/runtime/write";
 582             } else {
 583                 // Sane default of no storage visible
 584                 _exit(0);
 585             }
 586             if (TEMP_FAILURE_RETRY(mount(storageSource.c_str(), "/storage",
 587                     NULL, MS_BIND | MS_REC | MS_SLAVE, NULL)) == -1) {
 588                 PLOG(ERROR) << "Failed to mount " << storageSource << " for "
 589                         << de->d_name;
 590                 _exit(1);
 591             }
 592 
 593             // Mount user-specific symlink helper into place
 594             userid_t user_id = multiuser_get_user_id(uid);
 595             std::string userSource(StringPrintf("/mnt/user/%d", user_id));
 596             if (TEMP_FAILURE_RETRY(mount(userSource.c_str(), "/storage/self",
 597                     NULL, MS_BIND, NULL)) == -1) {
 598                 PLOG(ERROR) << "Failed to mount " << userSource << " for "
 599                         << de->d_name;
 600                 _exit(1);
 601             }
 602 
 603             _exit(0);
 604         }
 605 
 606         if (child == -1) {
 607             PLOG(ERROR) << "Failed to fork";
 608             goto next;
 609         } else {
 610             TEMP_FAILURE_RETRY(waitpid(child, nullptr, 0));
 611         }
 612 
 613 next:
 614         close(nsFd);
 615         close(pidFd);
 616     }
 617     closedir(dir);
 618     return 0;
 619 }

        remountUid函数的操作就是遍历/proc目录,读取/proc/${pid}/ns/mnt 目录,找到uid匹配的进程和mount空间,在该mount空间中重新绑定外置存储目录。之后应用程序就可以愉快的操作外置存储了。

        最后来看下撤掉读写外置存储权限的函数
frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

   @Override
    public void revokeRuntimePermission(String packageName, String name, int userId) {
        ......
        killUid(appId, userId, KILL_APP_REASON_PERMISSIONS_REVOKED);
    }

        撤销权限的过程简单粗暴,直接杀死应用程序,因为应用程序可能已经打开了一些文件,如果不杀死,可以继续写入数据。

本文地址:https://blog.csdn.net/woai110120130/article/details/107904009

相关标签: android 存储