Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 537ff90b authored by Arnab Sen's avatar Arnab Sen
Browse files

Adopted Storage: Prepare user storage before move

Moving "primary storage" between internal and adoptable storage involves
accessing the CE and DE storage of all users.  Similarly, moving an app
involves accessing the CE and DE storage of all users who have the app
installed.  CE and DE storage must be unlocked and prepared on both
volumes for all these users.  Currently this as handled as follows:

- Before starting either operation, Settings requires LSKF verification
  for each user whose CE storage is currently locked.

- For "move app", PMS prepares CE and DE storage on the target volume
  for users who don't have a CE directory on the target volume.

This isn't enough, however.  First, because unlocking of adoptable
storage is done by SMS.prepareUserStorage and not by SMS.unlockUserKey,
LSKF verification doesn't do it unless the user is already running.
Therefore, CE adoptable storage isn't unlocked for stopped users.
Second, since a stopped user might never have had adoptable storage
prepared at all, and "move primary storage" skips the prepare, the
target directory /mnt/expand/${volume_uuid}/media/${user_id} can end up
with the wrong SELinux label, encryption policy, and other metadata.

Fix both issues by making PMS and SMS call prepareUserStorage on the
source and target volumes for all relevant users for both types of move.

Test: manual
Test A: Check move storage works as expected:
    1. Create multiple users via Settings
    2. Switch to one user and set a lockscreen credentail
    3. Switch back to system user
    4. Execute adb shell am stop-user <user_id>
    5. Format SD card as adopted storage.
    6. Opt in for Move storage
    7. Check that the storage is prepared before moving the storage
       by checking the logs:
       adb logcat | grep fscrypt_prepare_user_storage
    8. Also check move storage is successful

Test B: Check move app works as expected:
    1. Create multiple users apart from system user
    2. Make ExternalLocTestApp and install for all the
       users.
    3. Go to Settings > All Apps >  ExternalLocTestApp > Storage & Cache
    4. Tap on Change under Storage User and Select Adopted storage.
    5. Check that the process is not failing

Change-Id: I8aef053968af957d52261bfe86d8fea64dcae6e7
parent aba0a551
Loading
Loading
Loading
Loading
+16 −0
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package android.os.storage;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.pm.UserInfo;
import android.os.IVold;

import java.util.List;
@@ -169,4 +170,19 @@ public abstract class StorageManagerInternal {
     */
    public abstract void registerCloudProviderChangeListener(
            @NonNull CloudProviderChangeListener listener);

    /**
     * Prepares user data directories before moving storage or apps. This is required as adoptable
     * storage unlock is tied to the prepare user data and storage needs to be unlocked before
     * performing any operations on it. This will also create user data directories before
     * initiating the move operations, which essential for ensuring the directories to have correct
     * SELinux labels and permissions.
     *
     * @param fromVolumeUuid the source volume UUID from which content needs to be transferred
     * @param toVolumeUuid the destination volume UUID to which contents are to be transferred
     * @param users a list of users for whom to prepare storage
     */
    public abstract void prepareUserStorageForMove(String fromVolumeUuid, String toVolumeUuid,
            List<UserInfo> users);

}
+36 −1
Original line number Diff line number Diff line
@@ -1377,6 +1377,16 @@ class StorageManagerService extends IStorageManager.Stub
        return mVold.supportsBlockCheckpoint();
    }

    private void prepareUserStorageForMoveInternal(String fromVolumeUuid, String toVolumeUuid,
            List<UserInfo> users) throws Exception {

        final int flags = StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE;
        for (UserInfo user : users) {
            prepareUserStorageInternal(fromVolumeUuid, user.id, user.serialNumber, flags);
            prepareUserStorageInternal(toVolumeUuid, user.id, user.serialNumber, flags);
        }
    }

    @Override
    public void onAwakeStateChanged(boolean isAwake) {
        // Ignored
@@ -2986,6 +2996,7 @@ class StorageManagerService extends IStorageManager.Stub

        final VolumeInfo from;
        final VolumeInfo to;
        final List<UserInfo> users;

        synchronized (mLock) {
            if (Objects.equals(mPrimaryStorageUuid, volumeUuid)) {
@@ -2999,7 +3010,7 @@ class StorageManagerService extends IStorageManager.Stub
            mMoveTargetUuid = volumeUuid;

            // We need all the users unlocked to move their primary storage
            final List<UserInfo> users = mContext.getSystemService(UserManager.class).getUsers();
            users = mContext.getSystemService(UserManager.class).getUsers();
            for (UserInfo user : users) {
                if (StorageManager.isFileEncrypted() && !isUserKeyUnlocked(user.id)) {
                    Slog.w(TAG, "Failing move due to locked user " + user.id);
@@ -3035,6 +3046,19 @@ class StorageManagerService extends IStorageManager.Stub
            }
        }

        // Prepare the storage before move, this is required to unlock adoptable storage (as the
        // keys are tied to prepare user data step) & also is required for the destination files to
        // end up with the correct SELinux labels and encryption policies for directories
        try {
            prepareUserStorageForMoveInternal(mPrimaryStorageUuid, volumeUuid, users);
        } catch (Exception e) {
            Slog.w(TAG, "Failing move due to failure on prepare user data", e);
            synchronized (mLock) {
                onMoveStatusLocked(PackageManager.MOVE_FAILED_INTERNAL_ERROR);
            }
            return;
        }

        try {
            mVold.moveStorage(from.id, to.id, new IVoldTaskListener.Stub() {
                @Override
@@ -5024,5 +5048,16 @@ class StorageManagerService extends IStorageManager.Stub
            mCloudProviderChangeListeners.add(listener);
            mHandler.obtainMessage(H_CLOUD_MEDIA_PROVIDER_CHANGED, listener);
        }

        @Override
        public void prepareUserStorageForMove(String fromVolumeUuid, String toVolumeUuid,
                List<UserInfo> users) {
            try {
                prepareUserStorageForMoveInternal(fromVolumeUuid, toVolumeUuid, users);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

    }
}
+16 −24
Original line number Diff line number Diff line
@@ -48,8 +48,8 @@ import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.storage.StorageManager;
import android.os.storage.StorageManagerInternal;
import android.os.storage.VolumeInfo;
import android.text.TextUtils;
import android.util.MathUtils;
import android.util.Slog;
import android.util.SparseIntArray;
@@ -63,6 +63,7 @@ import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.pm.pkg.PackageStateUtils;

import java.io.File;
import java.util.ArrayList;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -223,9 +224,7 @@ public final class MovePackageHelper {
        }

        try {
            for (int index = 0; index < installedUserIds.length; index++) {
                prepareUserDataForVolumeIfRequired(volumeUuid, installedUserIds[index], storage);
            }
            prepareUserStorageForMove(currentVolumeUuid, volumeUuid, installedUserIds);
        } catch (RuntimeException e) {
            freezer.close();
            throw new PackageManagerException(MOVE_FAILED_INTERNAL_ERROR,
@@ -380,27 +379,20 @@ public final class MovePackageHelper {
        return true;
    }

    private void prepareUserDataForVolumeIfRequired(String volumeUuid, int userId,
            StorageManager storageManager) {
        if (TextUtils.isEmpty(volumeUuid)
                || doesDataDirectoryExistForUser(volumeUuid, userId)) {
            return;
        }
    private void prepareUserStorageForMove(String fromVolumeUuid, String toVolumeUuid,
            int[] userIds) {
        if (DEBUG_INSTALL) {
            Slog.d(TAG, "Preparing user directories for user u" + userId + " for UUID "
                    + volumeUuid);
            Slog.d(TAG, "Preparing user directories before moving app, from UUID " + fromVolumeUuid
                    + " to UUID " + toVolumeUuid);
        }
        final StorageManagerInternal smInternal =
                mPm.mInjector.getLocalService(StorageManagerInternal.class);
        final ArrayList<UserInfo> users = new ArrayList<>();
        for (int userId : userIds) {
            final UserInfo user = mPm.mUserManager.getUserInfo(userId);
        if (user == null) return;
        // This call is same as StorageEventHelper#loadPrivatePackagesInner which prepares
        // the storage before reconciling apps
        storageManager.prepareUserStorage(volumeUuid, user.id, user.serialNumber,
                StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE);
            users.add(user);
        }

    private boolean doesDataDirectoryExistForUser(String uuid, int userId) {
        final File userDirectoryFile = Environment.getDataUserCeDirectory(uuid, userId);
        return userDirectoryFile != null && userDirectoryFile.exists();
        smInternal.prepareUserStorageForMove(fromVolumeUuid, toVolumeUuid, users);
    }

    public static class MoveCallbacks extends Handler {