前言
在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);
}
撤销权限的过程简单粗暴,直接杀死应用程序,因为应用程序可能已经打开了一些文件,如果不杀死,可以继续写入数据。