Merge commit '1db64c19' into merge3
Change-Id: I0aea6817876a5820a7d67a4de5bef0f86ce702a2
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 538007a..49386f9 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -4448,22 +4448,6 @@
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.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 @@
// 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 @@
// 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 @@
}
/** {@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 4e91093..7f40421 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -586,6 +586,21 @@
}
/** {@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 f1750bc..b318cf9 100644
--- a/services/core/java/com/android/server/MountService.java
+++ b/services/core/java/com/android/server/MountService.java
@@ -2888,7 +2888,8 @@
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 96300f2..c50efdc 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -20395,8 +20395,9 @@
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 @@
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 @@
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 @@
throw new IllegalStateException("Failed to allocate session ID");
}
- private File buildInternalStagingDir() {
- return new File(Environment.getDataDirectory(), "app");
+ private File buildStagingDir(String volumeUuid) {
+ return Environment.getDataAppDirectory(volumeUuid);
}
- 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 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 a4b3d09..9ec9c43 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.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;
@@ -428,6 +429,7 @@
// 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 */
@@ -455,6 +457,7 @@
// 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>();
@@ -1605,9 +1608,19 @@
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);
}
@@ -1624,7 +1637,17 @@
@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();
+ }
}
};
@@ -2770,8 +2793,9 @@
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;
}
@@ -6615,8 +6639,8 @@
} 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()) {
@@ -6770,6 +6794,18 @@
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.
@@ -11441,8 +11477,8 @@
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
@@ -12299,6 +12335,8 @@
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(
@@ -15237,6 +15275,127 @@
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(mContext, volumeUuid, user.id);
+ 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);
@@ -15536,14 +15695,11 @@
// 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);
@@ -15597,10 +15753,10 @@
}
/** 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 2a5c0cd..c6d4a66 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -3617,10 +3617,7 @@
}
}
- void createNewUserLILPw(PackageManagerService service, Installer installer,
- int userHandle, File path) {
- service.mContext.getSystemService(StorageManager.class)
- .createNewUserDir(userHandle, path);
+ 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 fb84f0d..05ae342 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -43,6 +43,10 @@
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;
@@ -138,6 +142,8 @@
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;
@@ -147,7 +153,6 @@
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>();
@@ -211,7 +216,6 @@
// Make zeroth user directory, for services to migrate their files to that location
File userZeroDir = new File(mUsersDir, String.valueOf(UserHandle.USER_SYSTEM));
userZeroDir.mkdirs();
- mBaseUserPath = baseUserPath;
FileUtils.setPermissions(mUsersDir.toString(),
FileUtils.S_IRWXU|FileUtils.S_IRWXG
|FileUtils.S_IROTH|FileUtils.S_IXOTH,
@@ -1253,7 +1257,6 @@
}
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;
@@ -1268,7 +1271,19 @@
}
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(mContext, volumeUuid, userId);
+ 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();
@@ -1874,6 +1889,81 @@
return RESTRICTIONS_FILE_PREFIX + packageName + XML_SUFFIX;
}
+ /**
+ * Create new {@code /data/user/[id]} directory and sets default
+ * permissions.
+ */
+ public static void prepareUserDirectory(Context context, String volumeUuid, int userId) {
+ final StorageManager storage = context.getSystemService(StorageManager.class);
+ final File userDir = Environment.getDataUserDirectory(volumeUuid, userId);
+ storage.createNewUserDir(userId, userDir);
+ }
+
+ /**
+ * 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)