blob: 524ae54972e574daba416b207366f067ebb76a21 [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.pm.PackageManager;
import android.content.rollback.PackageRollbackInfo;
import android.content.rollback.PackageRollbackInfo.RestoreInfo;
import android.os.storage.StorageManager;
import android.util.IntArray;
import android.util.Slog;
import android.util.SparseLongArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.pm.ApexManager;
import com.android.server.pm.Installer;
import com.android.server.pm.Installer.InstallerException;
import java.util.List;
/**
* 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;
private final ApexManager mApexManager;
public AppDataRollbackHelper(Installer installer) {
mInstaller = installer;
mApexManager = ApexManager.getInstance();
}
@VisibleForTesting
AppDataRollbackHelper(Installer installer, ApexManager apexManager) {
mInstaller = installer;
mApexManager = apexManager;
}
/**
* Creates an app data snapshot for a specified {@code packageRollbackInfo} and the specified
* {@code userIds}. Updates said {@code packageRollbackInfo} with the inodes of the CE user data
* snapshot folders.
*/
@GuardedBy("rollback.mLock")
// TODO(b/136241838): Move into Rollback and synchronize there.
public void snapshotAppData(
int rollbackId, PackageRollbackInfo packageRollbackInfo, int[] userIds) {
for (int user : userIds) {
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.
Slog.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;
}
doSnapshot(packageRollbackInfo, user, rollbackId, storageFlags);
}
}
/**
* 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.
*/
@GuardedBy("rollback.mLock")
// TODO(b/136241838): Move into Rollback and synchronize there.
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 changedRollback = 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));
changedRollback = 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));
changedRollback = true;
} else {
// This user has unlocked, we can proceed to restore both CE and DE data.
storageFlags = storageFlags | Installer.FLAG_STORAGE_CE;
}
}
doRestoreOrWipe(packageRollbackInfo, userId, rollbackId, appId, seInfo, storageFlags);
return changedRollback;
}
private boolean doSnapshot(
PackageRollbackInfo packageRollbackInfo, int userId, int rollbackId, int flags) {
if (packageRollbackInfo.isApex()) {
// For APEX, only snapshot CE here
if ((flags & Installer.FLAG_STORAGE_CE) != 0) {
long ceSnapshotInode = mApexManager.snapshotCeData(
userId, rollbackId, packageRollbackInfo.getPackageName());
if (ceSnapshotInode > 0) {
packageRollbackInfo.putCeSnapshotInode(userId, ceSnapshotInode);
} else {
return false;
}
}
} else {
// APK
try {
long ceSnapshotInode = mInstaller.snapshotAppData(
packageRollbackInfo.getPackageName(), userId, rollbackId, flags);
if ((flags & Installer.FLAG_STORAGE_CE) != 0) {
packageRollbackInfo.putCeSnapshotInode(userId, ceSnapshotInode);
}
} catch (InstallerException ie) {
Slog.e(TAG, "Unable to create app data snapshot for: "
+ packageRollbackInfo.getPackageName() + ", userId: " + userId, ie);
return false;
}
}
return true;
}
private boolean doRestoreOrWipe(PackageRollbackInfo packageRollbackInfo, int userId,
int rollbackId, int appId, String seInfo, int flags) {
if (packageRollbackInfo.isApex()) {
switch (packageRollbackInfo.getRollbackDataPolicy()) {
case PackageManager.RollbackDataPolicy.WIPE:
// TODO: Implement WIPE for apex CE data
break;
case PackageManager.RollbackDataPolicy.RESTORE:
// For APEX, only restore of CE may be done here.
if ((flags & Installer.FLAG_STORAGE_CE) != 0) {
mApexManager.restoreCeData(
userId, rollbackId, packageRollbackInfo.getPackageName());
}
break;
default:
break;
}
} else {
// APK
try {
switch (packageRollbackInfo.getRollbackDataPolicy()) {
case PackageManager.RollbackDataPolicy.WIPE:
mInstaller.clearAppData(null, packageRollbackInfo.getPackageName(),
userId, flags, 0);
break;
case PackageManager.RollbackDataPolicy.RESTORE:
mInstaller.restoreAppDataSnapshot(packageRollbackInfo.getPackageName(),
appId, seInfo, userId, rollbackId, flags);
break;
default:
break;
}
} catch (InstallerException ie) {
Slog.e(TAG, "Unable to restore/wipe app data: "
+ packageRollbackInfo.getPackageName() + " policy="
+ packageRollbackInfo.getRollbackDataPolicy(), ie);
return false;
}
}
return true;
}
/**
* Deletes an app data snapshot with a given {@code rollbackId} for a specified package
* {@code packageName} for a given {@code user}.
*/
@GuardedBy("rollback.mLock")
// TODO(b/136241838): Move into Rollback and synchronize there.
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) {
Slog.e(TAG, "Unable to delete app data snapshot for "
+ packageRollbackInfo.getPackageName(), ie);
}
}
/**
* Deletes all device-encrypted apex data snapshots for the given rollback id.
*/
public void destroyApexDeSnapshots(int rollbackId) {
mApexManager.destroyDeSnapshots(rollbackId);
}
/**
* Commits the pending backups and restores for a given {@code userId} and {@code rollback}. If
* the rollback has a pending backup, it is updated with a mapping from {@code userId} to inode
* of the CE user data snapshot.
*
* @return true if any backups or restores were found for the userId
*/
@GuardedBy("rollback.mLock")
boolean commitPendingBackupAndRestoreForUser(int userId, Rollback rollback) {
boolean foundBackupOrRestore = false;
for (PackageRollbackInfo info : rollback.info.getPackages()) {
boolean hasPendingBackup = false;
boolean hasPendingRestore = false;
final IntArray pendingBackupUsers = info.getPendingBackups();
if (pendingBackupUsers != null) {
if (pendingBackupUsers.indexOf(userId) != -1) {
hasPendingBackup = true;
foundBackupOrRestore = true;
}
}
RestoreInfo ri = info.getRestoreInfo(userId);
if (ri != null) {
hasPendingRestore = true;
foundBackupOrRestore = true;
}
if (hasPendingBackup && hasPendingRestore) {
// Remove unnecessary backup, i.e. when user did not unlock their phone between the
// request to backup data and the request to restore it.
info.removePendingBackup(userId);
info.removePendingRestoreInfo(userId);
continue;
}
if (hasPendingBackup) {
int idx = pendingBackupUsers.indexOf(userId);
if (doSnapshot(
info, userId, rollback.info.getRollbackId(), Installer.FLAG_STORAGE_CE)) {
pendingBackupUsers.remove(idx);
}
}
if (hasPendingRestore && doRestoreOrWipe(info, userId, rollback.info.getRollbackId(),
ri.appId, ri.seInfo, Installer.FLAG_STORAGE_CE)) {
info.removeRestoreInfo(ri);
}
}
return foundBackupOrRestore;
}
/**
* @return {@code true} iff. {@code userId} is locked on an FBE device.
*/
@VisibleForTesting
public boolean isUserCredentialLocked(int userId) {
return StorageManager.isFileEncryptedNativeOrEmulated()
&& !StorageManager.isUserKeyUnlocked(userId);
}
}