summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJeff Sharkey <jsharkey@android.com>2015-07-03 18:08:41 -0700
committerJeff Sharkey <jsharkey@android.com>2015-07-04 17:08:42 -0700
commit6dce4964b4d1a13d276d95730b8fb09d6a5a8d04 (patch)
tree3f5affa3fc23aae4ee9e9d7788cf80af4a5a498c
parent85be0c4e21ba6a2b74a8546403c6da03a343e5aa (diff)
downloadframeworks_base-6dce4964b4d1a13d276d95730b8fb09d6a5a8d04.zip
frameworks_base-6dce4964b4d1a13d276d95730b8fb09d6a5a8d04.tar.gz
frameworks_base-6dce4964b4d1a13d276d95730b8fb09d6a5a8d04.tar.bz2
Reconcile private volumes when mounted.
Many things can happen while a private volume is ejected, so we need to reconcile newly mounted volumes against known state. First, user IDs can be recycled, so we store the serial number in the extended attributes of the /data/user/[id] directory inode. Since a serial number is always unique, we can quickly determine if a user directory "10" really belongs to the current user "10". When we detect a mismatched serial number, we destroy all data belonging to that user. Gracefully handles upgrade case and assumes current serial number is valid when none is defined. Second, we destroy apps that we find no record of, either due to uninstallation while the volume was unmounted, or reinstallation on another volume. When mounting a volume, ensure that data directories exist for all current users. Similarly, create data directories on all mounted volumes when creating a user. When forgetting a volume, gracefully uninstall any apps that had been installed on that volume. Bug: 20674082, 20275572 Change-Id: I4e3448837f7c03daf00d71681ebdc96e3d8b9cc9
-rw-r--r--core/java/android/content/pm/PackageManager.java16
-rw-r--r--core/java/android/content/pm/PackageParser.java5
-rw-r--r--core/java/android/os/Environment.java28
-rw-r--r--core/java/android/os/storage/StorageManager.java15
-rw-r--r--services/core/java/com/android/server/MountService.java3
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java5
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerService.java66
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java194
-rw-r--r--services/core/java/com/android/server/pm/Settings.java6
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java105
10 files changed, 360 insertions, 83 deletions
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index dd1c5c2..1a4378b 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -4441,22 +4441,6 @@ public abstract class PackageManager {
public abstract @NonNull PackageInstaller getPackageInstaller();
/**
- * Returns the data directory for a particular package and user.
- *
- * @hide
- */
- public static File getDataDirForUser(String volumeUuid, String packageName, int userId) {
- // TODO: This should be shared with Installer's knowledge of user directory
- final File base;
- if (TextUtils.isEmpty(volumeUuid)) {
- base = Environment.getDataDirectory();
- } else {
- base = new File("/mnt/expand/" + volumeUuid);
- }
- return new File(base, "user/" + userId + "/" + packageName);
- }
-
- /**
* Adds a {@link CrossProfileIntentFilter}. After calling this method all intents sent from the
* user with id sourceUserId can also be be resolved by activities in the user with id
* targetUserId if they match the specified intent filter.
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 64376c1..48ffb98 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -36,6 +36,7 @@ import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.os.Build;
import android.os.Bundle;
+import android.os.Environment;
import android.os.FileUtils;
import android.os.PatternMatcher;
import android.os.UserHandle;
@@ -4785,7 +4786,7 @@ public class PackageParser {
// Make shallow copy so we can store the metadata/libraries safely
ApplicationInfo ai = new ApplicationInfo(p.applicationInfo);
ai.uid = UserHandle.getUid(userId, ai.uid);
- ai.dataDir = PackageManager.getDataDirForUser(ai.volumeUuid, ai.packageName, userId)
+ ai.dataDir = Environment.getDataUserPackageDirectory(ai.volumeUuid, userId, ai.packageName)
.getAbsolutePath();
if ((flags & PackageManager.GET_META_DATA) != 0) {
ai.metaData = p.mAppMetaData;
@@ -4812,7 +4813,7 @@ public class PackageParser {
// make a copy.
ai = new ApplicationInfo(ai);
ai.uid = UserHandle.getUid(userId, ai.uid);
- ai.dataDir = PackageManager.getDataDirForUser(ai.volumeUuid, ai.packageName, userId)
+ ai.dataDir = Environment.getDataUserPackageDirectory(ai.volumeUuid, userId, ai.packageName)
.getAbsolutePath();
if (state.stopped) {
ai.flags |= ApplicationInfo.FLAG_STOPPED;
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index 8e0584a..2080856 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -244,14 +244,36 @@ public class Environment {
}
/** {@hide} */
- public static File getDataAppDirectory(String volumeUuid) {
+ public static File getDataDirectory(String volumeUuid) {
if (TextUtils.isEmpty(volumeUuid)) {
- return new File("/data/app");
+ return new File("/data");
} else {
- return new File("/mnt/expand/" + volumeUuid + "/app");
+ return new File("/mnt/expand/" + volumeUuid);
}
}
+ /** {@hide} */
+ public static File getDataAppDirectory(String volumeUuid) {
+ return new File(getDataDirectory(volumeUuid), "app");
+ }
+
+ /** {@hide} */
+ public static File getDataUserDirectory(String volumeUuid) {
+ return new File(getDataDirectory(volumeUuid), "user");
+ }
+
+ /** {@hide} */
+ public static File getDataUserDirectory(String volumeUuid, int userId) {
+ return new File(getDataUserDirectory(volumeUuid), String.valueOf(userId));
+ }
+
+ /** {@hide} */
+ public static File getDataUserPackageDirectory(String volumeUuid, int userId,
+ String packageName) {
+ // TODO: keep consistent with installd
+ return new File(getDataUserDirectory(volumeUuid, userId), packageName);
+ }
+
/**
* Return the primary external storage directory. This directory may not
* currently be accessible if it has been mounted by the user on their
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index aab68e9..d28766f 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -586,6 +586,21 @@ public class StorageManager {
}
/** {@hide} */
+ public @NonNull List<VolumeInfo> getWritablePrivateVolumes() {
+ try {
+ final ArrayList<VolumeInfo> res = new ArrayList<>();
+ for (VolumeInfo vol : mMountService.getVolumes(0)) {
+ if (vol.getType() == VolumeInfo.TYPE_PRIVATE && vol.isMountedWritable()) {
+ res.add(vol);
+ }
+ }
+ return res;
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ /** {@hide} */
public @NonNull List<VolumeRecord> getVolumeRecords() {
try {
return Arrays.asList(mMountService.getVolumeRecords(0));
diff --git a/services/core/java/com/android/server/MountService.java b/services/core/java/com/android/server/MountService.java
index c82ba24..dc0c471 100644
--- a/services/core/java/com/android/server/MountService.java
+++ b/services/core/java/com/android/server/MountService.java
@@ -2831,7 +2831,8 @@ class MountService extends IMountService.Stub
Slog.i(TAG, "Trying to bind to DefaultContainerService");
Intent service = new Intent().setComponent(DEFAULT_CONTAINER_COMPONENT);
- if (mContext.bindService(service, mDefContainerConn, Context.BIND_AUTO_CREATE)) {
+ if (mContext.bindServiceAsUser(service, mDefContainerConn, Context.BIND_AUTO_CREATE,
+ UserHandle.OWNER)) {
mBound = true;
return true;
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 477c26c..4217c59 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -20342,8 +20342,9 @@ public final class ActivityManagerService extends ActivityManagerNative
if (info == null) return null;
ApplicationInfo newInfo = new ApplicationInfo(info);
newInfo.uid = applyUserId(info.uid, userId);
- newInfo.dataDir = PackageManager.getDataDirForUser(info.volumeUuid, info.packageName,
- userId).getAbsolutePath();
+ newInfo.dataDir = Environment
+ .getDataUserPackageDirectory(info.volumeUuid, userId, info.packageName)
+ .getAbsolutePath();
return newInfo;
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index ca24e3a..2abd924 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -219,29 +219,17 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
synchronized (mSessions) {
readSessionsLocked();
- final File internalStagingDir = buildInternalStagingDir();
- final ArraySet<File> unclaimedStages = Sets.newArraySet(
- internalStagingDir.listFiles(sStageFilter));
+ reconcileStagesLocked(StorageManager.UUID_PRIVATE_INTERNAL);
+
final ArraySet<File> unclaimedIcons = Sets.newArraySet(
mSessionsDir.listFiles());
// Ignore stages and icons claimed by active sessions
for (int i = 0; i < mSessions.size(); i++) {
final PackageInstallerSession session = mSessions.valueAt(i);
- unclaimedStages.remove(session.stageDir);
unclaimedIcons.remove(buildAppIconFile(session.sessionId));
}
- // Clean up orphaned staging directories
- for (File stage : unclaimedStages) {
- Slog.w(TAG, "Deleting orphan stage " + stage);
- if (stage.isDirectory()) {
- mPm.mInstaller.rmPackageDir(stage.getAbsolutePath());
- } else {
- stage.delete();
- }
- }
-
// Clean up orphaned icons
for (File icon : unclaimedIcons) {
Slog.w(TAG, "Deleting orphan icon " + icon);
@@ -255,6 +243,36 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
mStorage = mContext.getSystemService(StorageManager.class);
}
+ private void reconcileStagesLocked(String volumeUuid) {
+ final File stagingDir = buildStagingDir(volumeUuid);
+ final ArraySet<File> unclaimedStages = Sets.newArraySet(
+ stagingDir.listFiles(sStageFilter));
+
+ // Ignore stages claimed by active sessions
+ for (int i = 0; i < mSessions.size(); i++) {
+ final PackageInstallerSession session = mSessions.valueAt(i);
+ unclaimedStages.remove(session.stageDir);
+ }
+
+ // Clean up orphaned staging directories
+ for (File stage : unclaimedStages) {
+ Slog.w(TAG, "Deleting orphan stage " + stage);
+ synchronized (mPm.mInstallLock) {
+ if (stage.isDirectory()) {
+ mPm.mInstaller.rmPackageDir(stage.getAbsolutePath());
+ } else {
+ stage.delete();
+ }
+ }
+ }
+ }
+
+ public void onPrivateVolumeMounted(String volumeUuid) {
+ synchronized (mSessions) {
+ reconcileStagesLocked(volumeUuid);
+ }
+ }
+
public void onSecureContainersAvailable() {
synchronized (mSessions) {
final ArraySet<String> unclaimed = new ArraySet<>();
@@ -713,25 +731,11 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
throw new IllegalStateException("Failed to allocate session ID");
}
- private File buildInternalStagingDir() {
- return new File(Environment.getDataDirectory(), "app");
- }
-
- private File buildStagingDir(String volumeUuid) throws FileNotFoundException {
- if (volumeUuid == null) {
- return buildInternalStagingDir();
- } else {
- final VolumeInfo vol = mStorage.findVolumeByUuid(volumeUuid);
- if (vol != null && vol.type == VolumeInfo.TYPE_PRIVATE
- && vol.isMountedWritable()) {
- return new File(vol.path, "app");
- } else {
- throw new FileNotFoundException("Failed to find volume for UUID " + volumeUuid);
- }
- }
+ private File buildStagingDir(String volumeUuid) {
+ return Environment.getDataAppDirectory(volumeUuid);
}
- private File buildStageDir(String volumeUuid, int sessionId) throws FileNotFoundException {
+ private File buildStageDir(String volumeUuid, int sessionId) {
final File stagingDir = buildStagingDir(volumeUuid);
return new File(stagingDir, "vmdl" + sessionId + ".tmp");
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 305eb8e..9a11397 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -191,6 +191,7 @@ import libcore.io.IoUtils;
import libcore.util.EmptyArray;
import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IMediaContainerService;
import com.android.internal.app.ResolverActivity;
import com.android.internal.content.NativeLibraryHelper;
@@ -430,6 +431,7 @@ public class PackageManagerService extends IPackageManager.Stub {
// Used for privilege escalation. MUST NOT BE CALLED WITH mPackages
// LOCK HELD. Can be called with mInstallLock held.
+ @GuardedBy("mInstallLock")
final Installer mInstaller;
/** Directory where installed third-party apps stored */
@@ -457,6 +459,7 @@ public class PackageManagerService extends IPackageManager.Stub {
// Keys are String (package name), values are Package. This also serves
// as the lock for the global state. Methods that must be called with
// this lock held have the prefix "LP".
+ @GuardedBy("mPackages")
final ArrayMap<String, PackageParser.Package> mPackages =
new ArrayMap<String, PackageParser.Package>();
@@ -1607,9 +1610,19 @@ public class PackageManagerService extends IPackageManager.Stub {
public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) {
if (vol.type == VolumeInfo.TYPE_PRIVATE) {
if (vol.state == VolumeInfo.STATE_MOUNTED) {
- // TODO: ensure that private directories exist for all active users
- // TODO: remove user data whose serial number doesn't match
+ final String volumeUuid = vol.getFsUuid();
+
+ // Clean up any users or apps that were removed or recreated
+ // while this volume was missing
+ reconcileUsers(volumeUuid);
+ reconcileApps(volumeUuid);
+
+ // Clean up any install sessions that expired or were
+ // cancelled while this volume was missing
+ mInstallerService.onPrivateVolumeMounted(volumeUuid);
+
loadPrivatePackages(vol);
+
} else if (vol.state == VolumeInfo.STATE_EJECTING) {
unloadPrivatePackages(vol);
}
@@ -1626,7 +1639,17 @@ public class PackageManagerService extends IPackageManager.Stub {
@Override
public void onVolumeForgotten(String fsUuid) {
- // TODO: remove all packages hosted on this uuid
+ // Remove any apps installed on the forgotten volume
+ synchronized (mPackages) {
+ final List<PackageSetting> packages = mSettings.getVolumePackagesLPr(fsUuid);
+ for (PackageSetting ps : packages) {
+ Slog.d(TAG, "Destroying " + ps.name + " because volume was forgotten");
+ deletePackage(ps.name, new LegacyPackageDeleteObserver(null).getBinder(),
+ UserHandle.USER_OWNER, PackageManager.DELETE_ALL_USERS);
+ }
+
+ mSettings.writeLPr();
+ }
}
};
@@ -2772,8 +2795,9 @@ public class PackageManagerService extends IPackageManager.Stub {
pkg.applicationInfo.packageName = packageName;
pkg.applicationInfo.flags = ps.pkgFlags | ApplicationInfo.FLAG_IS_DATA_ONLY;
pkg.applicationInfo.privateFlags = ps.pkgPrivateFlags;
- pkg.applicationInfo.dataDir = PackageManager.getDataDirForUser(ps.volumeUuid,
- packageName, userId).getAbsolutePath();
+ pkg.applicationInfo.dataDir = Environment
+ .getDataUserPackageDirectory(ps.volumeUuid, userId, packageName)
+ .getAbsolutePath();
pkg.applicationInfo.primaryCpuAbi = ps.primaryCpuAbiString;
pkg.applicationInfo.secondaryCpuAbi = ps.secondaryCpuAbiString;
}
@@ -6617,8 +6641,8 @@ public class PackageManagerService extends IPackageManager.Stub {
} else {
// This is a normal package, need to make its data directory.
- dataPath = PackageManager.getDataDirForUser(pkg.volumeUuid, pkg.packageName,
- UserHandle.USER_OWNER);
+ dataPath = Environment.getDataUserPackageDirectory(pkg.volumeUuid,
+ UserHandle.USER_OWNER, pkg.packageName);
boolean uidError = false;
if (dataPath.exists()) {
@@ -6772,6 +6796,18 @@ public class PackageManagerService extends IPackageManager.Stub {
if (DEBUG_INSTALL) Slog.i(TAG, "Linking native library dir for " + path);
final int[] userIds = sUserManager.getUserIds();
synchronized (mInstallLock) {
+ // Make sure all user data directories are ready to roll; we're okay
+ // if they already exist
+ if (!TextUtils.isEmpty(pkg.volumeUuid)) {
+ for (int userId : userIds) {
+ if (userId != 0) {
+ mInstaller.createUserData(pkg.volumeUuid, pkg.packageName,
+ UserHandle.getUid(userId, pkg.applicationInfo.uid), userId,
+ pkg.applicationInfo.seinfo);
+ }
+ }
+ }
+
// Create a native library symlink only if we have native libraries
// and if the native libraries are 32 bit libraries. We do not provide
// this symlink for 64 bit libraries.
@@ -11443,8 +11479,8 @@ public class PackageManagerService extends IPackageManager.Stub {
String pkgName = pkg.packageName;
if (DEBUG_INSTALL) Slog.d(TAG, "installNewPackageLI: " + pkg);
- final boolean dataDirExists = PackageManager.getDataDirForUser(volumeUuid, pkgName,
- UserHandle.USER_OWNER).exists();
+ final boolean dataDirExists = Environment
+ .getDataUserPackageDirectory(volumeUuid, UserHandle.USER_OWNER, pkgName).exists();
synchronized(mPackages) {
if (mSettings.mRenamedPackages.containsKey(pkgName)) {
// A package with the same name is already installed, though
@@ -12301,6 +12337,8 @@ public class PackageManagerService extends IPackageManager.Stub {
final IPackageDeleteObserver2 observer, final int userId, final int flags) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.DELETE_PACKAGES, null);
+ Preconditions.checkNotNull(packageName);
+ Preconditions.checkNotNull(observer);
final int uid = Binder.getCallingUid();
if (UserHandle.getUserId(uid) != userId) {
mContext.enforceCallingPermission(
@@ -15239,6 +15277,127 @@ public class PackageManagerService extends IPackageManager.Stub {
sendResourcesChangedBroadcast(false, false, unloaded, null);
}
+ /**
+ * Examine all users present on given mounted volume, and destroy data
+ * belonging to users that are no longer valid, or whose user ID has been
+ * recycled.
+ */
+ private void reconcileUsers(String volumeUuid) {
+ final File[] files = Environment.getDataUserDirectory(volumeUuid).listFiles();
+ if (ArrayUtils.isEmpty(files)) {
+ Slog.d(TAG, "No users found on " + volumeUuid);
+ return;
+ }
+
+ for (File file : files) {
+ if (!file.isDirectory()) continue;
+
+ final int userId;
+ final UserInfo info;
+ try {
+ userId = Integer.parseInt(file.getName());
+ info = sUserManager.getUserInfo(userId);
+ } catch (NumberFormatException e) {
+ Slog.w(TAG, "Invalid user directory " + file);
+ continue;
+ }
+
+ boolean destroyUser = false;
+ if (info == null) {
+ logCriticalInfo(Log.WARN, "Destroying user directory " + file
+ + " because no matching user was found");
+ destroyUser = true;
+ } else {
+ try {
+ UserManagerService.enforceSerialNumber(file, info.serialNumber);
+ } catch (IOException e) {
+ logCriticalInfo(Log.WARN, "Destroying user directory " + file
+ + " because we failed to enforce serial number: " + e);
+ destroyUser = true;
+ }
+ }
+
+ if (destroyUser) {
+ synchronized (mInstallLock) {
+ mInstaller.removeUserDataDirs(volumeUuid, userId);
+ }
+ }
+ }
+
+ final UserManager um = mContext.getSystemService(UserManager.class);
+ for (UserInfo user : um.getUsers()) {
+ final File userDir = Environment.getDataUserDirectory(volumeUuid, user.id);
+ if (userDir.exists()) continue;
+
+ try {
+ UserManagerService.prepareUserDirectory(userDir);
+ UserManagerService.enforceSerialNumber(userDir, user.serialNumber);
+ } catch (IOException e) {
+ Log.wtf(TAG, "Failed to create user directory on " + volumeUuid, e);
+ }
+ }
+ }
+
+ /**
+ * Examine all apps present on given mounted volume, and destroy apps that
+ * aren't expected, either due to uninstallation or reinstallation on
+ * another volume.
+ */
+ private void reconcileApps(String volumeUuid) {
+ final File[] files = Environment.getDataAppDirectory(volumeUuid).listFiles();
+ if (ArrayUtils.isEmpty(files)) {
+ Slog.d(TAG, "No apps found on " + volumeUuid);
+ return;
+ }
+
+ for (File file : files) {
+ final boolean isPackage = (isApkFile(file) || file.isDirectory())
+ && !PackageInstallerService.isStageName(file.getName());
+ if (!isPackage) {
+ // Ignore entries which are not packages
+ continue;
+ }
+
+ boolean destroyApp = false;
+ String packageName = null;
+ try {
+ final PackageLite pkg = PackageParser.parsePackageLite(file,
+ PackageParser.PARSE_MUST_BE_APK);
+ packageName = pkg.packageName;
+
+ synchronized (mPackages) {
+ final PackageSetting ps = mSettings.mPackages.get(packageName);
+ if (ps == null) {
+ logCriticalInfo(Log.WARN, "Destroying " + packageName + " on + "
+ + volumeUuid + " because we found no install record");
+ destroyApp = true;
+ } else if (!TextUtils.equals(volumeUuid, ps.volumeUuid)) {
+ logCriticalInfo(Log.WARN, "Destroying " + packageName + " on "
+ + volumeUuid + " because we expected it on " + ps.volumeUuid);
+ destroyApp = true;
+ }
+ }
+
+ } catch (PackageParserException e) {
+ logCriticalInfo(Log.WARN, "Destroying " + file + " due to parse failure: " + e);
+ destroyApp = true;
+ }
+
+ if (destroyApp) {
+ synchronized (mInstallLock) {
+ if (packageName != null) {
+ removeDataDirsLI(volumeUuid, packageName);
+ }
+ if (file.isDirectory()) {
+ mInstaller.rmPackageDir(file.getAbsolutePath());
+ } else {
+ file.delete();
+ }
+ }
+ }
+ }
+ }
+
private void unfreezePackage(String packageName) {
synchronized (mPackages) {
final PackageSetting ps = mSettings.mPackages.get(packageName);
@@ -15538,14 +15697,11 @@ public class PackageManagerService extends IPackageManager.Stub {
// Technically, we shouldn't be doing this with the package lock
// held. However, this is very rare, and there is already so much
// other disk I/O going on, that we'll let it slide for now.
- final StorageManager storage = StorageManager.from(mContext);
- final List<VolumeInfo> vols = storage.getVolumes();
- for (VolumeInfo vol : vols) {
- if (vol.getType() == VolumeInfo.TYPE_PRIVATE && vol.isMountedWritable()) {
- final String volumeUuid = vol.getFsUuid();
- if (DEBUG_INSTALL) Slog.d(TAG, "Removing user data on volume " + volumeUuid);
- mInstaller.removeUserDataDirs(volumeUuid, userHandle);
- }
+ final StorageManager storage = mContext.getSystemService(StorageManager.class);
+ for (VolumeInfo vol : storage.getWritablePrivateVolumes()) {
+ final String volumeUuid = vol.getFsUuid();
+ if (DEBUG_INSTALL) Slog.d(TAG, "Removing user data on volume " + volumeUuid);
+ mInstaller.removeUserDataDirs(volumeUuid, userHandle);
}
}
mUserNeedsBadging.delete(userHandle);
@@ -15599,10 +15755,10 @@ public class PackageManagerService extends IPackageManager.Stub {
}
/** Called by UserManagerService */
- void createNewUserLILPw(int userHandle, File path) {
+ void createNewUserLILPw(int userHandle) {
if (mInstaller != null) {
mInstaller.createUserConfig(userHandle);
- mSettings.createNewUserLILPw(this, mInstaller, userHandle, path);
+ mSettings.createNewUserLILPw(this, mInstaller, userHandle);
applyFactoryDefaultBrowserLPw(userHandle);
}
}
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 0ad2b4a..9c23af3 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -3613,11 +3613,7 @@ final class Settings {
}
}
- void createNewUserLILPw(PackageManagerService service, Installer installer,
- int userHandle, File path) {
- path.mkdir();
- FileUtils.setPermissions(path.toString(), FileUtils.S_IRWXU | FileUtils.S_IRWXG
- | FileUtils.S_IXOTH, -1, -1);
+ void createNewUserLILPw(PackageManagerService service, Installer installer, int userHandle) {
for (PackageSetting ps : mPackages.values()) {
if (ps.pkg == null || ps.pkg.applicationInfo == null) {
continue;
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 4300df6..1a79b4e 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -42,6 +42,11 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManager;
+import android.os.storage.StorageManager;
+import android.os.storage.VolumeInfo;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
import android.util.AtomicFile;
import android.util.Log;
import android.util.Slog;
@@ -137,6 +142,8 @@ public class UserManagerService extends IUserManager.Stub {
static final int WRITE_USER_MSG = 1;
static final int WRITE_USER_DELAY = 2*1000; // 2 seconds
+ private static final String XATTR_SERIAL = "user.serial";
+
private final Context mContext;
private final PackageManagerService mPm;
private final Object mInstallLock;
@@ -146,7 +153,6 @@ public class UserManagerService extends IUserManager.Stub {
private final File mUsersDir;
private final File mUserListFile;
- private final File mBaseUserPath;
private final SparseArray<UserInfo> mUsers = new SparseArray<UserInfo>();
private final SparseArray<Bundle> mUserRestrictions = new SparseArray<Bundle>();
@@ -210,7 +216,6 @@ public class UserManagerService extends IUserManager.Stub {
// Make zeroth user directory, for services to migrate their files to that location
File userZeroDir = new File(mUsersDir, "0");
userZeroDir.mkdirs();
- mBaseUserPath = baseUserPath;
FileUtils.setPermissions(mUsersDir.toString(),
FileUtils.S_IRWXU|FileUtils.S_IRWXG
|FileUtils.S_IROTH|FileUtils.S_IXOTH,
@@ -1237,7 +1242,6 @@ public class UserManagerService extends IUserManager.Stub {
}
int userId = getNextAvailableIdLocked();
userInfo = new UserInfo(userId, name, null, flags);
- File userPath = new File(mBaseUserPath, Integer.toString(userId));
userInfo.serialNumber = mNextSerialNumber++;
long now = System.currentTimeMillis();
userInfo.creationTime = (now > EPOCH_PLUS_30_YEARS) ? now : 0;
@@ -1252,7 +1256,19 @@ public class UserManagerService extends IUserManager.Stub {
}
userInfo.profileGroupId = parent.profileGroupId;
}
- mPm.createNewUserLILPw(userId, userPath);
+ final StorageManager storage = mContext.getSystemService(StorageManager.class);
+ for (VolumeInfo vol : storage.getWritablePrivateVolumes()) {
+ final String volumeUuid = vol.getFsUuid();
+ try {
+ final File userDir = Environment.getDataUserDirectory(volumeUuid,
+ userId);
+ prepareUserDirectory(userDir);
+ enforceSerialNumber(userDir, userInfo.serialNumber);
+ } catch (IOException e) {
+ Log.wtf(LOG_TAG, "Failed to create user directory on " + volumeUuid, e);
+ }
+ }
+ mPm.createNewUserLILPw(userId);
userInfo.partial = false;
scheduleWriteUserLocked(userInfo);
updateUserIdsLocked();
@@ -1856,6 +1872,87 @@ public class UserManagerService extends IUserManager.Stub {
return RESTRICTIONS_FILE_PREFIX + packageName + XML_SUFFIX;
}
+ /**
+ * Create new {@code /data/user/[id]} directory and sets default
+ * permissions.
+ */
+ public static void prepareUserDirectory(File file) throws IOException {
+ if (!file.exists()) {
+ if (!file.mkdir()) {
+ throw new IOException("Failed to create " + file);
+ }
+ }
+ if (FileUtils.setPermissions(file.getAbsolutePath(), 0771, Process.SYSTEM_UID,
+ Process.SYSTEM_UID) != 0) {
+ throw new IOException("Failed to prepare " + file);
+ }
+ }
+
+ /**
+ * Enforce that serial number stored in user directory inode matches the
+ * given expected value. Gracefully sets the serial number if currently
+ * undefined.
+ *
+ * @throws IOException when problem extracting serial number, or serial
+ * number is mismatched.
+ */
+ public static void enforceSerialNumber(File file, int serialNumber) throws IOException {
+ final int foundSerial = getSerialNumber(file);
+ Slog.v(LOG_TAG, "Found " + file + " with serial number " + foundSerial);
+
+ if (foundSerial == -1) {
+ Slog.d(LOG_TAG, "Serial number missing on " + file + "; assuming current is valid");
+ try {
+ setSerialNumber(file, serialNumber);
+ } catch (IOException e) {
+ Slog.w(LOG_TAG, "Failed to set serial number on " + file, e);
+ }
+
+ } else if (foundSerial != serialNumber) {
+ throw new IOException("Found serial number " + foundSerial
+ + " doesn't match expected " + serialNumber);
+ }
+ }
+
+ /**
+ * Set serial number stored in user directory inode.
+ *
+ * @throws IOException if serial number was already set
+ */
+ private static void setSerialNumber(File file, int serialNumber)
+ throws IOException {
+ try {
+ final byte[] buf = Integer.toString(serialNumber).getBytes(StandardCharsets.UTF_8);
+ Os.setxattr(file.getAbsolutePath(), XATTR_SERIAL, buf, OsConstants.XATTR_CREATE);
+ } catch (ErrnoException e) {
+ throw e.rethrowAsIOException();
+ }
+ }
+
+ /**
+ * Return serial number stored in user directory inode.
+ *
+ * @return parsed serial number, or -1 if not set
+ */
+ private static int getSerialNumber(File file) throws IOException {
+ try {
+ final byte[] buf = new byte[256];
+ final int len = Os.getxattr(file.getAbsolutePath(), XATTR_SERIAL, buf);
+ final String serial = new String(buf, 0, len);
+ try {
+ return Integer.parseInt(serial);
+ } catch (NumberFormatException e) {
+ throw new IOException("Bad serial number: " + serial);
+ }
+ } catch (ErrnoException e) {
+ if (e.errno == OsConstants.ENODATA) {
+ return -1;
+ } else {
+ throw e.rethrowAsIOException();
+ }
+ }
+ }
+
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)