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

Commit 50979d14 authored by Fyodor Kupolov's avatar Fyodor Kupolov
Browse files

Tests for UserDataPreparer

Moved reconcileUsers, enforceSerialNumber to UserDataPreparer and added
unit tests

Test: manual + UserDataPreparerTest
Bug: 34736064
Change-Id: Icde93974ac68849e38357d8cfea0cc1b7a2aab49
parent 391b1b82
Loading
Loading
Loading
Loading
+4 −59
Original line number Diff line number Diff line
@@ -833,7 +833,6 @@ public class PackageManagerService extends IPackageManager.Stub {
    private List<String> mKeepUninstalledPackages;
    private UserManagerInternal mUserManagerInternal;
    private final UserDataPreparer mUserDataPreparer;
    private File mCacheDir;
@@ -1937,7 +1936,7 @@ public class PackageManagerService extends IPackageManager.Stub {
                    // Clean up any users or apps that were removed or recreated
                    // while this volume was missing
                    reconcileUsers(volumeUuid);
                    sUserManager.reconcileUsers(volumeUuid);
                    reconcileApps(volumeUuid);
                    // Clean up any install sessions that expired or were
@@ -2270,8 +2269,8 @@ public class PackageManagerService extends IPackageManager.Stub {
            mEphemeralInstallDir = new File(dataDir, "app-ephemeral");
            mAsecInternalPath = new File(dataDir, "app-asec").getPath();
            mDrmAppPrivateInstallDir = new File(dataDir, "app-private");
            mUserDataPreparer = new UserDataPreparer(mInstaller, mInstallLock, mContext, mOnlyCore);
            sUserManager = new UserManagerService(context, this, mUserDataPreparer, mPackages);
            sUserManager = new UserManagerService(context, this,
                    new UserDataPreparer(mInstaller, mInstallLock, mContext, mOnlyCore), mPackages);
            // Propagate permission configuration in to package manager.
            ArrayMap<String, SystemConfig.PermissionEntry> permConfig
@@ -19971,7 +19970,7 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
        });
        // Now that we're mostly running, clean up stale users and apps
        reconcileUsers(StorageManager.UUID_PRIVATE_INTERNAL);
        sUserManager.reconcileUsers(StorageManager.UUID_PRIVATE_INTERNAL);
        reconcileApps(StorageManager.UUID_PRIVATE_INTERNAL);
    }
@@ -21309,60 +21308,6 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
        }
    }
    /**
     * 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 List<File> files = new ArrayList<>();
        Collections.addAll(files, FileUtils
                .listFilesOrEmpty(Environment.getDataUserDeDirectory(volumeUuid)));
        Collections.addAll(files, FileUtils
                .listFilesOrEmpty(Environment.getDataUserCeDirectory(volumeUuid)));
        Collections.addAll(files, FileUtils
                .listFilesOrEmpty(Environment.getDataSystemDeDirectory()));
        Collections.addAll(files, FileUtils
                .listFilesOrEmpty(Environment.getDataSystemCeDirectory()));
        Collections.addAll(files, FileUtils
                .listFilesOrEmpty(Environment.getDataMiscCeDirectory()));
        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 if (!mOnlyCore) {
                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) {
                    mUserDataPreparer.destroyUserDataLI(volumeUuid, userId,
                            StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE);
                }
            }
        }
    }
    private void assertPackageKnown(String volumeUuid, String packageName)
            throws PackageManagerException {
        synchronized (mPackages) {
+206 −13
Original line number Diff line number Diff line
@@ -17,13 +17,28 @@
package com.android.server.pm;

import android.content.Context;
import android.content.pm.UserInfo;
import android.os.Environment;
import android.os.FileUtils;
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.Log;
import android.util.Slog;
import android.util.SparseArray;

import com.android.internal.annotations.VisibleForTesting;

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;

import static com.android.server.pm.PackageManagerService.logCriticalInfo;

@@ -31,6 +46,9 @@ import static com.android.server.pm.PackageManagerService.logCriticalInfo;
 * Helper class for preparing and destroying user storage
 */
class UserDataPreparer {
    private static final String TAG = "UserDataPreparer";
    private static final String XATTR_SERIAL = "user.serial";

    private final Object mInstallLock;
    private final Context mContext;
    private final boolean mOnlyCore;
@@ -65,19 +83,15 @@ class UserDataPreparer {
            storage.prepareUserStorage(volumeUuid, userId, userSerial, flags);

            if ((flags & StorageManager.FLAG_STORAGE_DE) != 0 && !mOnlyCore) {
                UserManagerService.enforceSerialNumber(
                        Environment.getDataUserDeDirectory(volumeUuid, userId), userSerial);
                enforceSerialNumber(getDataUserDeDirectory(volumeUuid, userId), userSerial);
                if (Objects.equals(volumeUuid, StorageManager.UUID_PRIVATE_INTERNAL)) {
                    UserManagerService.enforceSerialNumber(
                            Environment.getDataSystemDeDirectory(userId), userSerial);
                    enforceSerialNumber(getDataSystemDeDirectory(userId), userSerial);
                }
            }
            if ((flags & StorageManager.FLAG_STORAGE_CE) != 0 && !mOnlyCore) {
                UserManagerService.enforceSerialNumber(
                        Environment.getDataUserCeDirectory(volumeUuid, userId), userSerial);
                enforceSerialNumber(getDataUserCeDirectory(volumeUuid, userId), userSerial);
                if (Objects.equals(volumeUuid, StorageManager.UUID_PRIVATE_INTERNAL)) {
                    UserManagerService.enforceSerialNumber(
                            Environment.getDataSystemCeDirectory(userId), userSerial);
                    enforceSerialNumber(getDataSystemCeDirectory(userId), userSerial);
                }
            }

@@ -117,13 +131,13 @@ class UserDataPreparer {
            // Clean up system data
            if (Objects.equals(volumeUuid, StorageManager.UUID_PRIVATE_INTERNAL)) {
                if ((flags & StorageManager.FLAG_STORAGE_DE) != 0) {
                    FileUtils.deleteContentsAndDir(Environment.getUserSystemDirectory(userId));
                    FileUtils.deleteContentsAndDir(Environment.getDataSystemDeDirectory(userId));
                    FileUtils.deleteContentsAndDir(Environment.getDataMiscDeDirectory(userId));
                    FileUtils.deleteContentsAndDir(getUserSystemDirectory(userId));
                    FileUtils.deleteContentsAndDir(getDataSystemDeDirectory(userId));
                    FileUtils.deleteContentsAndDir(getDataMiscDeDirectory(userId));
                }
                if ((flags & StorageManager.FLAG_STORAGE_CE) != 0) {
                    FileUtils.deleteContentsAndDir(Environment.getDataSystemCeDirectory(userId));
                    FileUtils.deleteContentsAndDir(Environment.getDataMiscCeDirectory(userId));
                    FileUtils.deleteContentsAndDir(getDataSystemCeDirectory(userId));
                    FileUtils.deleteContentsAndDir(getDataMiscCeDirectory(userId));
                }
            }

@@ -136,4 +150,183 @@ class UserDataPreparer {
        }
    }

    /**
     * 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.
     */
    void reconcileUsers(String volumeUuid, List<UserInfo> validUsersList) {
        final List<File> files = new ArrayList<>();
        Collections.addAll(files, FileUtils
                .listFilesOrEmpty(Environment.getDataUserDeDirectory(volumeUuid)));
        Collections.addAll(files, FileUtils
                .listFilesOrEmpty(Environment.getDataUserCeDirectory(volumeUuid)));
        Collections.addAll(files, FileUtils
                .listFilesOrEmpty(Environment.getDataSystemDeDirectory()));
        Collections.addAll(files, FileUtils
                .listFilesOrEmpty(Environment.getDataSystemCeDirectory()));
        Collections.addAll(files, FileUtils
                .listFilesOrEmpty(Environment.getDataMiscCeDirectory()));
        reconcileUsers(volumeUuid, validUsersList, files);
    }

    @VisibleForTesting
    void reconcileUsers(String volumeUuid, List<UserInfo> validUsersList, List<File> files) {
        final int userCount = validUsersList.size();
        SparseArray<UserInfo> users = new SparseArray<>(userCount);
        for (int i = 0; i < userCount; i++) {
            UserInfo user = validUsersList.get(i);
            users.put(user.id, user);
        }
        for (File file : files) {
            if (!file.isDirectory()) {
                continue;
            }

            final int userId;
            final UserInfo info;
            try {
                userId = Integer.parseInt(file.getName());
                info = users.get(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 if (!mOnlyCore) {
                try {
                    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) {
                    destroyUserDataLI(volumeUuid, userId,
                            StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE);
                }
            }
        }
    }

    @VisibleForTesting
    protected File getDataMiscCeDirectory(int userId) {
        return Environment.getDataMiscCeDirectory(userId);
    }

    @VisibleForTesting
    protected File getDataSystemCeDirectory(int userId) {
        return Environment.getDataSystemCeDirectory(userId);
    }

    @VisibleForTesting
    protected File getDataMiscDeDirectory(int userId) {
        return Environment.getDataMiscDeDirectory(userId);
    }

    @VisibleForTesting
    protected File getUserSystemDirectory(int userId) {
        return Environment.getUserSystemDirectory(userId);
    }

    @VisibleForTesting
    protected File getDataUserCeDirectory(String volumeUuid, int userId) {
        return Environment.getDataUserCeDirectory(volumeUuid, userId);
    }

    @VisibleForTesting
    protected File getDataSystemDeDirectory(int userId) {
        return Environment.getDataSystemDeDirectory(userId);
    }

    @VisibleForTesting
    protected File getDataUserDeDirectory(String volumeUuid, int userId) {
        return Environment.getDataUserDeDirectory(volumeUuid, userId);
    }

    @VisibleForTesting
    protected boolean isFileEncryptedEmulatedOnly() {
        return StorageManager.isFileEncryptedEmulatedOnly();
    }

    /**
     * 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.
     */
    void enforceSerialNumber(File file, int serialNumber) throws IOException {
        if (isFileEncryptedEmulatedOnly()) {
            // When we're emulating FBE, the directory may have been chmod
            // 000'ed, meaning we can't read the serial number to enforce it;
            // instead of destroying the user, just log a warning.
            Slog.w(TAG, "Device is emulating FBE; assuming current serial number is valid");
            return;
        }

        final int foundSerial = getSerialNumber(file);
        Slog.v(TAG, "Found " + file + " with serial number " + foundSerial);

        if (foundSerial == -1) {
            Slog.d(TAG, "Serial number missing on " + file + "; assuming current is valid");
            try {
                setSerialNumber(file, serialNumber);
            } catch (IOException e) {
                Slog.w(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
     */
    @VisibleForTesting
    static int getSerialNumber(File file) throws IOException {
        try {
            final byte[] buf = Os.getxattr(file.getAbsolutePath(), XATTR_SERIAL);
            final String serial = new String(buf);
            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();
            }
        }
    }

}
+9 −74
Original line number Diff line number Diff line
@@ -218,8 +218,6 @@ 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";

    // Tron counters
    private static final String TRON_GUEST_CREATED = "users_guest_created";
    private static final String TRON_USER_CREATED = "users_user_created";
@@ -3158,6 +3156,15 @@ public class UserManagerService extends IUserManager.Stub {
        mPm.reconcileAppsData(userId, StorageManager.FLAG_STORAGE_CE, migrateAppsData);
    }

    /**
     * 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.
     */
    void reconcileUsers(String volumeUuid) {
        mUserDataPreparer.reconcileUsers(volumeUuid, getUsers(true /* excludeDying */));
    }

    /**
     * Make a note of the last started time of a user and do some cleanup.
     * This is called with ActivityManagerService lock held.
@@ -3219,78 +3226,6 @@ public class UserManagerService extends IUserManager.Stub {
        return RESTRICTIONS_FILE_PREFIX + packageName + XML_SUFFIX;
    }

    /**
     * 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 {
        if (StorageManager.isFileEncryptedEmulatedOnly()) {
            // When we're emulating FBE, the directory may have been chmod
            // 000'ed, meaning we can't read the serial number to enforce it;
            // instead of destroying the user, just log a warning.
            Slog.w(LOG_TAG, "Device is emulating FBE; assuming current serial number is valid");
            return;
        }

        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 = Os.getxattr(file.getAbsolutePath(), XATTR_SERIAL);
            final String serial = new String(buf);
            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
    public void setSeedAccountData(int userId, String accountName, String accountType,
            PersistableBundle accountOptions, boolean persist) {
+273 −0

File added.

Preview size limit exceeded, changes collapsed.