blob: 8dd076028b43c208b7d01e6ad8d4e59da2edb57a [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.content.rollback.RollbackInfo;
import android.os.storage.StorageManager;
import android.util.IntArray;
import android.util.Log;
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.List;
import java.util.Map;
/**
* 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 packageName} for {@code installedUsers},
* a specified set of users for whom the package is installed.
*
* @return a list of users for which the snapshot is pending, usually because data for one or
* more users is still credential locked.
*/
public IntArray snapshotAppData(String packageName, int[] installedUsers) {
final IntArray pendingBackups = new IntArray();
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;
pendingBackups.add(user);
} else {
storageFlags = Installer.FLAG_STORAGE_CE | Installer.FLAG_STORAGE_DE;
}
try {
mInstaller.snapshotAppData(packageName, user, storageFlags);
} catch (InstallerException ie) {
Log.e(TAG, "Unable to create app data snapshot for: " + packageName
+ ", userId: " + user, ie);
}
}
return pendingBackups;
}
/**
* Restores an app data snapshot for a specified package ({@code packageName},
* {@code rollbackData}) for a specified {@code userId}.
*
* @return {@code true} iff. a change to the {@code rollbackData} has been made. Changes to
* {@code rollbackData} are restricted to the removal or addition of {@code userId} to
* the list of pending backups or restores.
*/
public boolean restoreAppData(String packageName, RollbackData rollbackData,
int userId, int appId, long ceDataInode, String seInfo) {
if (rollbackData == null) {
return false;
}
if (!rollbackData.inProgress) {
Log.e(TAG, "Request to restore userData for: " + packageName
+ ", but no rollback in progress.");
return false;
}
PackageRollbackInfo packageInfo = RollbackManagerServiceImpl.getPackageRollbackInfo(
rollbackData, packageName);
int storageFlags = Installer.FLAG_STORAGE_DE;
final IntArray pendingBackups = packageInfo.getPendingBackups();
final List<RestoreInfo> pendingRestores = packageInfo.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(packageName, appId, ceDataInode,
seInfo, userId, storageFlags);
} catch (InstallerException ie) {
Log.e(TAG, "Unable to restore app data snapshot: " + packageName, ie);
}
return changedRollbackData;
}
/**
* Computes the list of pending backups and restores for {@code userId} given lists of
* available and recent rollbacks. Packages pending backup for the given user are added
* to {@code pendingBackups} and packages pending restore are added to {@code pendingRestores}
* along with their corresponding {@code RestoreInfo}.
*
* @return the list of {@code RollbackData} that have been modified during this computation.
*/
public List<RollbackData> computePendingBackupsAndRestores(int userId,
ArrayList<String> pendingBackupPackages, Map<String, RestoreInfo> pendingRestores,
List<RollbackData> availableRollbacks, List<RollbackInfo> recentRollbacks) {
List<RollbackData> rd = new ArrayList<>();
// First check with the list of available rollbacks to see whether there are any
// pending backup operations that we've not managed to execute.
for (RollbackData data : availableRollbacks) {
for (PackageRollbackInfo info : data.packages) {
final IntArray pendingBackupUsers = info.getPendingBackups();
if (pendingBackupUsers != null) {
final int idx = pendingBackupUsers.indexOf(userId);
if (idx != -1) {
pendingBackupPackages.add(info.getPackageName());
pendingBackupUsers.remove(idx);
if (rd.indexOf(data) == -1) {
rd.add(data);
}
}
}
}
}
// Then check with the list of recently executed rollbacks to see whether there are
// any rollback operations
for (RollbackInfo data : recentRollbacks) {
for (PackageRollbackInfo info : data.getPackages()) {
final RestoreInfo ri = info.getRestoreInfo(userId);
if (ri != null) {
if (pendingBackupPackages.contains(info.getPackageName())) {
// This implies that the user hasn't unlocked their device between
// the request to backup data for this user and the request to restore
// it, so we do nothing here.
pendingBackupPackages.remove(info.getPackageName());
} else {
pendingRestores.put(info.getPackageName(), ri);
}
info.removeRestoreInfo(ri);
}
}
}
return rd;
}
/**
* Commits the list of pending backups and restores for a given {@code userId}.
*/
public void commitPendingBackupAndRestoreForUser(int userId,
ArrayList<String> pendingBackups, Map<String, RestoreInfo> pendingRestores) {
if (!pendingBackups.isEmpty()) {
for (String packageName : pendingBackups) {
try {
mInstaller.snapshotAppData(packageName, userId, Installer.FLAG_STORAGE_CE);
} catch (InstallerException ie) {
Log.e(TAG, "Unable to create app data snapshot for: " + packageName, ie);
}
}
}
// TODO(narayan): Should we perform the restore before the backup for packages that have
// both backups and restores pending ? We could get into this case if we have a pending
// restore from a rollback + a snapshot request from a new restore.
if (!pendingRestores.isEmpty()) {
for (String packageName : pendingRestores.keySet()) {
try {
final RestoreInfo ri = pendingRestores.get(packageName);
// TODO(narayan): Verify that the user of "0" for ceDataInode is accurate
// here. We know that the user has unlocked (and that their CE data is
// available) so we shouldn't need to resort to the fallback path.
mInstaller.restoreAppDataSnapshot(packageName, ri.appId,
0 /* ceDataInode */, ri.seInfo, userId, Installer.FLAG_STORAGE_CE);
} catch (InstallerException ie) {
Log.e(TAG, "Unable to restore app data snapshot for: " + packageName, ie);
}
}
}
}
/**
* @return {@code true} iff. {@code userId} is locked on an FBE device.
*/
@VisibleForTesting
public boolean isUserCredentialLocked(int userId) {
return StorageManager.isFileEncryptedNativeOrEmulated()
&& !StorageManager.isUserKeyUnlocked(userId);
}
}