blob: 56e1d080a988ce6941c13e5e57e4fcfbe6bb5a50 [file] [log] [blame]
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.rollback;
import android.content.rollback.PackageRollbackInfo;
import android.content.rollback.PackageRollbackInfo.RestoreInfo;
import android.os.storage.StorageManager;
import android.util.IntArray;
import android.util.Log;
import android.util.SparseLongArray;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.pm.Installer;
import com.android.server.pm.Installer.InstallerException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Encapsulates the logic for initiating userdata snapshots and rollbacks via installd.
*/
@VisibleForTesting
// TODO(narayan): Reason about the failure scenarios that involve one or more IPCs to installd
// failing. We need to decide what course of action to take if calls to snapshotAppData or
// restoreAppDataSnapshot fail.
public class AppDataRollbackHelper {
private static final String TAG = "RollbackManager";
private final Installer mInstaller;
public AppDataRollbackHelper(Installer installer) {
mInstaller = installer;
}
/**
* Creates an app data snapshot for a specified {@code packageRollbackInfo}. Updates said {@code
* packageRollbackInfo} with the inodes of the CE user data snapshot folders.
*/
public void snapshotAppData(int snapshotId, PackageRollbackInfo packageRollbackInfo) {
final int[] installedUsers = packageRollbackInfo.getInstalledUsers().toArray();
for (int user : installedUsers) {
final int storageFlags;
if (isUserCredentialLocked(user)) {
// We've encountered a user that hasn't unlocked on a FBE device, so we can't copy
// across app user data until the user unlocks their device.
Log.v(TAG, "User: " + user + " isn't unlocked, skipping CE userdata backup.");
storageFlags = Installer.FLAG_STORAGE_DE;
packageRollbackInfo.addPendingBackup(user);
} else {
storageFlags = Installer.FLAG_STORAGE_CE | Installer.FLAG_STORAGE_DE;
}
try {
long ceSnapshotInode = mInstaller.snapshotAppData(
packageRollbackInfo.getPackageName(), user, snapshotId, storageFlags);
if ((storageFlags & Installer.FLAG_STORAGE_CE) != 0) {
packageRollbackInfo.putCeSnapshotInode(user, ceSnapshotInode);
}
} catch (InstallerException ie) {
Log.e(TAG, "Unable to create app data snapshot for: "
+ packageRollbackInfo.getPackageName() + ", userId: " + user, ie);
}
}
}
/**
* Restores an app data snapshot for a specified {@code packageRollbackInfo}, for a specified
* {@code userId}.
*
* @return {@code true} iff. a change to the {@code packageRollbackInfo} has been made. Changes
* to {@code packageRollbackInfo} are restricted to the removal or addition of {@code
* userId} to the list of pending backups or restores.
*/
public boolean restoreAppData(int rollbackId, PackageRollbackInfo packageRollbackInfo,
int userId, int appId, String seInfo) {
int storageFlags = Installer.FLAG_STORAGE_DE;
final IntArray pendingBackups = packageRollbackInfo.getPendingBackups();
final List<RestoreInfo> pendingRestores = packageRollbackInfo.getPendingRestores();
boolean changedRollbackData = false;
// If we still have a userdata backup pending for this user, it implies that the user
// hasn't unlocked their device between the point of backup and the point of restore,
// so the data cannot have changed. We simply skip restoring CE data in this case.
if (pendingBackups != null && pendingBackups.indexOf(userId) != -1) {
pendingBackups.remove(pendingBackups.indexOf(userId));
changedRollbackData = true;
} else {
// There's no pending CE backup for this user, which means that we successfully
// managed to backup data for the user, which means we seek to restore it
if (isUserCredentialLocked(userId)) {
// We've encountered a user that hasn't unlocked on a FBE device, so we can't
// copy across app user data until the user unlocks their device.
pendingRestores.add(new RestoreInfo(userId, appId, seInfo));
changedRollbackData = true;
} else {
// This user has unlocked, we can proceed to restore both CE and DE data.
storageFlags = storageFlags | Installer.FLAG_STORAGE_CE;
}
}
try {
mInstaller.restoreAppDataSnapshot(packageRollbackInfo.getPackageName(), appId, seInfo,
userId, rollbackId, storageFlags);
} catch (InstallerException ie) {
Log.e(TAG, "Unable to restore app data snapshot: "
+ packageRollbackInfo.getPackageName(), ie);
}
return changedRollbackData;
}
/**
* Deletes an app data snapshot with a given {@code rollbackId} for a specified package
* {@code packageName} for a given {@code user}.
*/
public void destroyAppDataSnapshot(int rollbackId, PackageRollbackInfo packageRollbackInfo,
int user) {
int storageFlags = Installer.FLAG_STORAGE_DE;
final SparseLongArray ceSnapshotInodes = packageRollbackInfo.getCeSnapshotInodes();
long ceSnapshotInode = ceSnapshotInodes.get(user);
if (ceSnapshotInode > 0) {
storageFlags |= Installer.FLAG_STORAGE_CE;
}
try {
mInstaller.destroyAppDataSnapshot(packageRollbackInfo.getPackageName(), user,
ceSnapshotInode, rollbackId, storageFlags);
if ((storageFlags & Installer.FLAG_STORAGE_CE) != 0) {
ceSnapshotInodes.delete(user);
}
} catch (InstallerException ie) {
Log.e(TAG, "Unable to delete app data snapshot for "
+ packageRollbackInfo.getPackageName(), ie);
}
}
/**
* Computes the list of pending backups for {@code userId} given lists of rollbacks.
* Packages pending backup for the given user are added to {@code pendingBackupPackages} along
* with their corresponding {@code PackageRollbackInfo}.
*
* @return the list of {@code RollbackData} that has pending backups. Note that some of the
* backups won't be performed, because they might be counteracted by pending restores.
*/
private static List<RollbackData> computePendingBackups(int userId,
Map<String, PackageRollbackInfo> pendingBackupPackages,
List<RollbackData> rollbacks) {
List<RollbackData> rd = new ArrayList<>();
for (RollbackData data : rollbacks) {
for (PackageRollbackInfo info : data.info.getPackages()) {
final IntArray pendingBackupUsers = info.getPendingBackups();
if (pendingBackupUsers != null) {
final int idx = pendingBackupUsers.indexOf(userId);
if (idx != -1) {
pendingBackupPackages.put(info.getPackageName(), info);
if (rd.indexOf(data) == -1) {
rd.add(data);
}
}
}
}
}
return rd;
}
/**
* Computes the list of pending restores for {@code userId} given lists of rollbacks.
* Packages pending restore are added to {@code pendingRestores} along with their corresponding
* {@code PackageRollbackInfo}.
*
* @return the list of {@code RollbackData} that has pending restores. Note that some of the
* restores won't be performed, because they might be counteracted by pending backups.
*/
private static List<RollbackData> computePendingRestores(int userId,
Map<String, PackageRollbackInfo> pendingRestorePackages,
List<RollbackData> rollbacks) {
List<RollbackData> rd = new ArrayList<>();
for (RollbackData data : rollbacks) {
for (PackageRollbackInfo info : data.info.getPackages()) {
final RestoreInfo ri = info.getRestoreInfo(userId);
if (ri != null) {
pendingRestorePackages.put(info.getPackageName(), info);
if (rd.indexOf(data) == -1) {
rd.add(data);
}
}
}
}
return rd;
}
/**
* Commits the list of pending backups and restores for a given {@code userId}. For the pending
* backups updates corresponding {@code changedRollbackData} with a mapping from {@code userId}
* to a inode of theirs CE user data snapshot.
*
* @return the set of {@code RollbackData} that have been changed and should be stored on disk.
*/
public Set<RollbackData> commitPendingBackupAndRestoreForUser(int userId,
List<RollbackData> rollbacks) {
final Map<String, PackageRollbackInfo> pendingBackupPackages = new HashMap<>();
final List<RollbackData> pendingBackups = computePendingBackups(userId,
pendingBackupPackages, rollbacks);
final Map<String, PackageRollbackInfo> pendingRestorePackages = new HashMap<>();
final List<RollbackData> pendingRestores = computePendingRestores(userId,
pendingRestorePackages, rollbacks);
// First remove unnecessary backups, i.e. when user did not unlock their phone between the
// request to backup data and the request to restore it.
Iterator<Map.Entry<String, PackageRollbackInfo>> iter =
pendingBackupPackages.entrySet().iterator();
while (iter.hasNext()) {
PackageRollbackInfo backupPackage = iter.next().getValue();
PackageRollbackInfo restorePackage =
pendingRestorePackages.get(backupPackage.getPackageName());
if (restorePackage != null) {
backupPackage.removePendingBackup(userId);
backupPackage.removePendingRestoreInfo(userId);
iter.remove();
pendingRestorePackages.remove(backupPackage.getPackageName());
}
}
if (!pendingBackupPackages.isEmpty()) {
for (RollbackData data : pendingBackups) {
for (PackageRollbackInfo info : data.info.getPackages()) {
final IntArray pendingBackupUsers = info.getPendingBackups();
final int idx = pendingBackupUsers.indexOf(userId);
if (idx != -1) {
try {
long ceSnapshotInode = mInstaller.snapshotAppData(info.getPackageName(),
userId, data.info.getRollbackId(), Installer.FLAG_STORAGE_CE);
info.putCeSnapshotInode(userId, ceSnapshotInode);
pendingBackupUsers.remove(idx);
} catch (InstallerException ie) {
Log.e(TAG,
"Unable to create app data snapshot for: "
+ info.getPackageName() + ", userId: " + userId, ie);
}
}
}
}
}
if (!pendingRestorePackages.isEmpty()) {
for (RollbackData data : pendingRestores) {
for (PackageRollbackInfo info : data.info.getPackages()) {
final RestoreInfo ri = info.getRestoreInfo(userId);
if (ri != null) {
try {
mInstaller.restoreAppDataSnapshot(info.getPackageName(), ri.appId,
ri.seInfo, userId, data.info.getRollbackId(),
Installer.FLAG_STORAGE_CE);
info.removeRestoreInfo(ri);
} catch (InstallerException ie) {
Log.e(TAG, "Unable to restore app data snapshot for: "
+ info.getPackageName(), ie);
}
}
}
}
}
final Set<RollbackData> changed = new HashSet<>(pendingBackups);
changed.addAll(pendingRestores);
return changed;
}
/**
* @return {@code true} iff. {@code userId} is locked on an FBE device.
*/
@VisibleForTesting
public boolean isUserCredentialLocked(int userId) {
return StorageManager.isFileEncryptedNativeOrEmulated()
&& !StorageManager.isUserKeyUnlocked(userId);
}
}