diff options
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) |
