Merge "[KV] Exceptions for error-handling"

This commit is contained in:
Bernardo Rufino
2018-09-26 09:11:20 +00:00
committed by Android (Google) Code Review
9 changed files with 1094 additions and 352 deletions

View File

@ -0,0 +1,63 @@
/*
* Copyright (C) 2018 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.backup.keyvalue;
/**
* This represents something wrong with a specific package. For example:
* <ul>
* <li>Package unknown.
* <li>Package is not eligible for backup anymore.
* <li>Backup agent timed out.
* <li>Backup agent wrote protected keys.
* <li>...
* </ul>
*
* @see KeyValueBackupTask
* @see TaskException
*/
class AgentException extends BackupException {
static AgentException transitory() {
return new AgentException(/* transitory */ true);
}
static AgentException transitory(Exception cause) {
return new AgentException(/* transitory */ true, cause);
}
static AgentException permanent() {
return new AgentException(/* transitory */ false);
}
static AgentException permanent(Exception cause) {
return new AgentException(/* transitory */ false, cause);
}
private final boolean mTransitory;
private AgentException(boolean transitory) {
mTransitory = transitory;
}
private AgentException(boolean transitory, Exception cause) {
super(cause);
mTransitory = transitory;
}
boolean isTransitory() {
return mTransitory;
}
}

View File

@ -0,0 +1,33 @@
/*
* Copyright (C) 2018 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.backup.keyvalue;
import android.util.AndroidException;
/**
* Key-value backup task exception.
*
* @see AgentException
* @see TaskException
*/
class BackupException extends AndroidException {
BackupException() {}
BackupException(Exception cause) {
super(cause);
}
}

View File

@ -153,16 +153,18 @@ public class KeyValueBackupReporter {
mObserver, packageName, BackupManager.ERROR_BACKUP_NOT_ALLOWED);
}
void onBindAgentError(SecurityException e) {
Slog.d(TAG, "Error in bind/backup", e);
}
void onAgentUnknown(String packageName) {
Slog.d(TAG, "Package does not exist, skipping");
BackupObserverUtils.sendBackupOnPackageResult(
mObserver, packageName, BackupManager.ERROR_PACKAGE_NOT_FOUND);
}
void onBindAgentError(String packageName, SecurityException e) {
Slog.d(TAG, "Error in bind/backup", e);
BackupObserverUtils.sendBackupOnPackageResult(
mObserver, packageName, BackupManager.ERROR_AGENT_FAILURE);
}
void onAgentError(String packageName) {
if (MORE_DEBUG) {
Slog.i(TAG, "Agent failure for " + packageName + ", re-staging");
@ -190,6 +192,8 @@ public class KeyValueBackupReporter {
void onCallAgentDoBackupError(String packageName, boolean callingAgent, Exception e) {
if (callingAgent) {
Slog.e(TAG, "Error invoking agent on " + packageName + ": " + e);
BackupObserverUtils.sendBackupOnPackageResult(
mObserver, packageName, BackupManager.ERROR_AGENT_FAILURE);
} else {
Slog.e(TAG, "Error before invoking agent on " + packageName + ": " + e);
}
@ -220,12 +224,8 @@ public class KeyValueBackupReporter {
}
}
void onReadAgentDataError(String packageName, IOException e) {
Slog.w(TAG, "Unable read backup data for " + packageName + ": " + e);
}
void onWriteWidgetDataError(String packageName, IOException e) {
Slog.w(TAG, "Unable to save widget data for " + packageName + ": " + e);
void onAgentDataError(String packageName, IOException e) {
Slog.w(TAG, "Unable to read/write agent data for " + packageName + ": " + e);
}
void onDigestError(NoSuchAlgorithmException e) {
@ -243,16 +243,12 @@ public class KeyValueBackupReporter {
}
}
void onSendDataToTransport(String packageName) {
void onTransportPerformBackup(String packageName) {
if (MORE_DEBUG) {
Slog.v(TAG, "Sending non-empty data to transport for " + packageName);
}
}
void onNonIncrementalAndNonIncrementalRequired() {
Slog.e(TAG, "Transport requested non-incremental but already the case");
}
void onEmptyData(PackageInfo packageInfo) {
if (MORE_DEBUG) {
Slog.i(TAG, "No backup data written, not calling transport");
@ -302,13 +298,20 @@ public class KeyValueBackupReporter {
/* extras */ null);
}
void onPackageBackupNonIncrementalAndNonIncrementalRequired(String packageName) {
Slog.e(TAG, "Transport requested non-incremental but already the case");
BackupObserverUtils.sendBackupOnPackageResult(
mObserver, packageName, BackupManager.ERROR_TRANSPORT_ABORTED);
EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_FAILURE, packageName);
}
void onPackageBackupTransportFailure(String packageName) {
BackupObserverUtils.sendBackupOnPackageResult(
mObserver, packageName, BackupManager.ERROR_TRANSPORT_ABORTED);
EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_FAILURE, packageName);
}
void onPackageBackupError(String packageName, Exception e) {
void onPackageBackupTransportError(String packageName, Exception e) {
Slog.e(TAG, "Transport error backing up " + packageName, e);
BackupObserverUtils.sendBackupOnPackageResult(
mObserver, packageName, BackupManager.ERROR_TRANSPORT_ABORTED);

View File

@ -16,6 +16,7 @@
package com.android.server.backup.keyvalue;
import static android.app.ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL;
import static android.os.ParcelFileDescriptor.MODE_CREATE;
import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
@ -25,8 +26,8 @@ import static com.android.server.backup.BackupManagerService.KEY_WIDGET_STATE;
import static com.android.server.backup.BackupManagerService.OP_PENDING;
import static com.android.server.backup.BackupManagerService.OP_TYPE_BACKUP;
import android.annotation.IntDef;
import android.annotation.Nullable;
import android.app.ApplicationThreadConstants;
import android.app.IBackupAgent;
import android.app.backup.BackupAgent;
import android.app.backup.BackupDataInput;
@ -47,7 +48,6 @@ import android.os.RemoteException;
import android.os.SELinux;
import android.os.UserHandle;
import android.os.WorkSource;
import android.util.Pair;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@ -77,6 +77,8 @@ import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
@ -173,10 +175,8 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable {
private static final AtomicInteger THREAD_COUNT = new AtomicInteger();
private static final String BLANK_STATE_FILE_NAME = "blank_state";
private static final String PM_PACKAGE = BackupManagerService.PACKAGE_MANAGER_SENTINEL;
@VisibleForTesting
public static final String STAGING_FILE_SUFFIX = ".data";
@VisibleForTesting
public static final String NEW_STATE_FILE_SUFFIX = ".new";
@VisibleForTesting public static final String STAGING_FILE_SUFFIX = ".data";
@VisibleForTesting public static final String NEW_STATE_FILE_SUFFIX = ".new";
/**
* Creates a new {@link KeyValueBackupTask} for key-value backup operation, spins up a new
@ -244,13 +244,13 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable {
private final int mCurrentOpToken;
private final File mStateDirectory;
private final File mDataDirectory;
private final File mBlankStateFile;
private final List<String> mOriginalQueue;
private final List<String> mQueue;
private final List<String> mPendingFullBackups;
private final Object mQueueLock;
@Nullable private final DataChangedJournal mJournal;
private int mStatus;
@Nullable private PerformFullTransportBackupTask mFullBackupTask;
@Nullable private IBackupAgent mAgent;
@Nullable private PackageInfo mCurrentPackage;
@ -316,6 +316,7 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable {
mDataDirectory = mBackupManagerService.getDataDir();
mCurrentOpToken = backupManagerService.generateRandomIntegerToken();
mQueueLock = mBackupManagerService.getQueueLock();
mBlankStateFile = new File(mStateDirectory, BLANK_STATE_FILE_NAME);
}
private void registerTask() {
@ -331,45 +332,43 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable {
public void run() {
Process.setThreadPriority(THREAD_PRIORITY);
boolean processQueue = startTask();
while (processQueue && !mQueue.isEmpty() && !mCancelled) {
String packageName = mQueue.remove(0);
if (PM_PACKAGE.equals(packageName)) {
processQueue = backupPm();
} else {
processQueue = backupPackage(packageName);
int status = BackupTransport.TRANSPORT_OK;
try {
startTask();
while (!mQueue.isEmpty() && !mCancelled) {
String packageName = mQueue.remove(0);
try {
if (PM_PACKAGE.equals(packageName)) {
backupPm();
} else {
backupPackage(packageName);
}
} catch (AgentException e) {
if (e.isTransitory()) {
// We try again this package in the next backup pass.
mBackupManagerService.dataChangedImpl(packageName);
}
}
}
} catch (TaskException e) {
if (e.isStateCompromised()) {
mBackupManagerService.resetBackupState(mStateDirectory);
}
revertTask();
status = e.getStatus();
}
finishTask();
finishTask(status);
}
/** Returns whether to consume next queue package. */
private boolean handleAgentResult(@Nullable PackageInfo packageInfo, RemoteResult result) {
if (result == RemoteResult.FAILED_THREAD_INTERRUPTED) {
// Not an explicit cancel, we need to flag it.
mCancelled = true;
mReporter.onAgentCancelled(packageInfo);
cleanUpAgentForAgentError();
return false;
/** Returns transport status. */
private int sendDataToTransport(@Nullable PackageInfo packageInfo)
throws AgentException, TaskException {
try {
return sendDataToTransport();
} catch (IOException e) {
mReporter.onAgentDataError(packageInfo.packageName, e);
throw TaskException.causedBy(e);
}
if (result == RemoteResult.FAILED_CANCELLED) {
mReporter.onAgentCancelled(packageInfo);
cleanUpAgentForAgentError();
return false;
}
if (result == RemoteResult.FAILED_TIMED_OUT) {
mReporter.onAgentTimedOut(packageInfo);
cleanUpAgentForAgentError();
return true;
}
Preconditions.checkState(result.isPresent());
long agentResult = result.get();
if (agentResult == BackupAgent.RESULT_ERROR) {
mReporter.onAgentResultError(packageInfo);
cleanUpAgentForAgentError();
return true;
}
return sendDataToTransport();
}
@Override
@ -378,11 +377,10 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable {
@Override
public void operationComplete(long unusedResult) {}
/** Returns whether to consume next queue package. */
private boolean startTask() {
private void startTask() throws TaskException {
if (mBackupManagerService.isBackupOperationInProgress()) {
mReporter.onSkipBackup();
return false;
throw TaskException.create();
}
// Unfortunately full backup task constructor registers the task with BMS, so we have to
@ -390,11 +388,9 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable {
mFullBackupTask = createFullBackupTask(mPendingFullBackups);
registerTask();
mStatus = BackupTransport.TRANSPORT_OK;
if (mQueue.isEmpty() && mPendingFullBackups.isEmpty()) {
mReporter.onEmptyQueueAtStart();
return false;
return;
}
// We only backup PM if it was explicitly in the queue or if it's incremental.
boolean backupPm = mQueue.remove(PM_PACKAGE) || !mNonIncremental;
@ -415,20 +411,18 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable {
if (pmState.length() <= 0) {
mReporter.onInitializeTransport(transportName);
mBackupManagerService.resetBackupState(mStateDirectory);
mStatus = transport.initializeDevice();
mReporter.onTransportInitialized(mStatus);
int status = transport.initializeDevice();
mReporter.onTransportInitialized(status);
if (status != BackupTransport.TRANSPORT_OK) {
throw TaskException.stateCompromised();
}
}
} catch (TaskException e) {
throw e;
} catch (Exception e) {
mReporter.onInitializeTransportError(e);
mStatus = BackupTransport.TRANSPORT_ERROR;
throw TaskException.stateCompromised();
}
if (mStatus != BackupTransport.TRANSPORT_OK) {
mBackupManagerService.resetBackupState(mStateDirectory);
return false;
}
return true;
}
private PerformFullTransportBackupTask createFullBackupTask(List<String> packages) {
@ -446,120 +440,82 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable {
mUserInitiated);
}
/** Returns whether to consume next queue package. */
private boolean backupPm() {
RemoteResult agentResult = null;
private void backupPm() throws TaskException {
mReporter.onStartPackageBackup(PM_PACKAGE);
mCurrentPackage = new PackageInfo();
mCurrentPackage.packageName = PM_PACKAGE;
try {
mCurrentPackage = new PackageInfo();
mCurrentPackage.packageName = PM_PACKAGE;
// Since PM is running in the system process we can set up its agent directly.
BackupAgent pmAgent = mBackupManagerService.makeMetadataAgent();
mAgent = IBackupAgent.Stub.asInterface(pmAgent.onBind());
Pair<Integer, RemoteResult> statusAndResult = extractAgentData(PM_PACKAGE, mAgent);
mStatus = statusAndResult.first;
agentResult = statusAndResult.second;
} catch (Exception e) {
extractPmAgentData(mCurrentPackage);
int status = sendDataToTransport(mCurrentPackage);
cleanUpAgentForTransportStatus(status);
} catch (AgentException | TaskException e) {
mReporter.onExtractPmAgentDataError(e);
mStatus = BackupTransport.TRANSPORT_ERROR;
cleanUpAgentForError(e);
// PM agent failure is task failure.
throw TaskException.stateCompromised(e);
}
if (mStatus != BackupTransport.TRANSPORT_OK) {
// In this case either extractAgentData() already made the agent clean-up or we haven't
// prepared the state for calling the agent, in either case we don't need to clean-up.
mBackupManagerService.resetBackupState(mStateDirectory);
return false;
}
Preconditions.checkNotNull(agentResult);
return handleAgentResult(mCurrentPackage, agentResult);
}
/** Returns whether to consume next queue package. */
private boolean backupPackage(String packageName) {
private void backupPackage(String packageName) throws AgentException, TaskException {
mReporter.onStartPackageBackup(packageName);
mStatus = BackupTransport.TRANSPORT_OK;
mCurrentPackage = getPackageForBackup(packageName);
// Verify that the requested app is eligible for key-value backup.
RemoteResult agentResult = null;
try {
mCurrentPackage = mPackageManager.getPackageInfo(
packageName, PackageManager.GET_SIGNING_CERTIFICATES);
ApplicationInfo applicationInfo = mCurrentPackage.applicationInfo;
if (!AppBackupUtils.appIsEligibleForBackup(applicationInfo, mPackageManager)) {
// The manifest has changed. This won't happen again because the app won't be
// requesting further backups.
mReporter.onPackageNotEligibleForBackup(packageName);
return true;
}
if (AppBackupUtils.appGetsFullBackup(mCurrentPackage)) {
// Initially enqueued for key-value backup, but only supports full-backup now.
mReporter.onPackageEligibleForFullBackup(packageName);
return true;
}
if (AppBackupUtils.appIsStopped(applicationInfo)) {
// Just as it won't receive broadcasts, we won't run it for backup.
mReporter.onPackageStopped(packageName);
return true;
}
try {
mBackupManagerService.setWorkSource(new WorkSource(applicationInfo.uid));
IBackupAgent agent =
mBackupManagerService.bindToAgentSynchronous(
applicationInfo,
ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL);
if (agent != null) {
mAgent = agent;
Pair<Integer, RemoteResult> statusAndResult =
extractAgentData(packageName, agent);
mStatus = statusAndResult.first;
agentResult = statusAndResult.second;
} else {
// Timeout waiting for the agent to bind.
mStatus = BackupTransport.AGENT_ERROR;
}
} catch (SecurityException e) {
mReporter.onBindAgentError(e);
mStatus = BackupTransport.AGENT_ERROR;
}
} catch (PackageManager.NameNotFoundException e) {
mStatus = BackupTransport.AGENT_UNKNOWN;
} finally {
mBackupManagerService.setWorkSource(null);
extractAgentData(mCurrentPackage);
int status = sendDataToTransport(mCurrentPackage);
cleanUpAgentForTransportStatus(status);
} catch (AgentException | TaskException e) {
cleanUpAgentForError(e);
throw e;
}
if (mStatus != BackupTransport.TRANSPORT_OK) {
// In this case either extractAgentData() already made the agent clean-up or we haven't
// prepared the state for calling the agent, in either case we don't need to clean-up.
Preconditions.checkState(mAgent == null);
if (mStatus == BackupTransport.AGENT_ERROR) {
mReporter.onAgentError(packageName);
mBackupManagerService.dataChangedImpl(packageName);
mStatus = BackupTransport.TRANSPORT_OK;
return true;
}
if (mStatus == BackupTransport.AGENT_UNKNOWN) {
mReporter.onAgentUnknown(packageName);
mStatus = BackupTransport.TRANSPORT_OK;
return true;
}
// Transport-level failure, re-enqueue everything.
revertTask();
return false;
}
Preconditions.checkNotNull(agentResult);
return handleAgentResult(mCurrentPackage, agentResult);
}
private void finishTask() {
private PackageInfo getPackageForBackup(String packageName) throws AgentException {
final PackageInfo packageInfo;
try {
packageInfo =
mPackageManager.getPackageInfo(
packageName, PackageManager.GET_SIGNING_CERTIFICATES);
} catch (PackageManager.NameNotFoundException e) {
mReporter.onAgentUnknown(packageName);
throw AgentException.permanent(e);
}
ApplicationInfo applicationInfo = packageInfo.applicationInfo;
if (!AppBackupUtils.appIsEligibleForBackup(applicationInfo, mPackageManager)) {
mReporter.onPackageNotEligibleForBackup(packageName);
throw AgentException.permanent();
}
if (AppBackupUtils.appGetsFullBackup(packageInfo)) {
mReporter.onPackageEligibleForFullBackup(packageName);
throw AgentException.permanent();
}
if (AppBackupUtils.appIsStopped(applicationInfo)) {
mReporter.onPackageStopped(packageName);
throw AgentException.permanent();
}
return packageInfo;
}
private IBackupAgent bindAgent(PackageInfo packageInfo) throws AgentException {
String packageName = packageInfo.packageName;
final IBackupAgent agent;
try {
agent =
mBackupManagerService.bindToAgentSynchronous(
packageInfo.applicationInfo, BACKUP_MODE_INCREMENTAL);
if (agent == null) {
mReporter.onAgentError(packageName);
throw AgentException.transitory();
}
} catch (SecurityException e) {
mReporter.onBindAgentError(packageName, e);
throw AgentException.transitory(e);
}
return agent;
}
private void finishTask(int status) {
// Mark packages that we couldn't backup as pending backup.
for (String packageName : mQueue) {
mBackupManagerService.dataChangedImpl(packageName);
@ -576,7 +532,7 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable {
// If we succeeded and this is the first time we've done a backup, we can record the current
// backup dataset token.
long currentToken = mBackupManagerService.getCurrentToken();
if ((mStatus == BackupTransport.TRANSPORT_OK) && (currentToken == 0)) {
if ((status == BackupTransport.TRANSPORT_OK) && (currentToken == 0)) {
try {
IBackupTransport transport = mTransportClient.connectOrThrow(callerLogString);
mBackupManagerService.setCurrentToken(transport.getCurrentRestoreSet());
@ -589,9 +545,14 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable {
synchronized (mQueueLock) {
mBackupManagerService.setBackupRunning(false);
if (mStatus == BackupTransport.TRANSPORT_NOT_INITIALIZED) {
if (status == BackupTransport.TRANSPORT_NOT_INITIALIZED) {
mReporter.onTransportNotInitialized();
triggerTransportInitializationLocked();
try {
triggerTransportInitializationLocked();
} catch (Exception e) {
mReporter.onPendingInitializeTransportError(e);
status = BackupTransport.TRANSPORT_ERROR;
}
}
}
@ -605,7 +566,7 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable {
}
if (!mCancelled
&& mStatus == BackupTransport.TRANSPORT_OK
&& status == BackupTransport.TRANSPORT_OK
&& mFullBackupTask != null
&& !mPendingFullBackups.isEmpty()) {
mReporter.onStartFullBackup(mPendingFullBackups);
@ -621,7 +582,7 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable {
mFullBackupTask.unregisterTask();
}
mTaskFinishedListener.onFinished(callerLogString);
mReporter.onBackupFinished(getBackupFinishedStatus(mCancelled, mStatus));
mReporter.onBackupFinished(getBackupFinishedStatus(mCancelled, status));
mBackupManagerService.getWakelock().release();
}
@ -642,17 +603,12 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable {
}
@GuardedBy("mQueueLock")
private void triggerTransportInitializationLocked() {
try {
IBackupTransport transport =
mTransportClient.connectOrThrow("KVBT.triggerTransportInitializationLocked");
mBackupManagerService.getPendingInits().add(transport.name());
deletePmStateFile();
mBackupManagerService.backupNow();
} catch (Exception e) {
mReporter.onPendingInitializeTransportError(e);
mStatus = BackupTransport.TRANSPORT_ERROR;
}
private void triggerTransportInitializationLocked() throws Exception {
IBackupTransport transport =
mTransportClient.connectOrThrow("KVBT.triggerTransportInitializationLocked");
mBackupManagerService.getPendingInits().add(transport.name());
deletePmStateFile();
mBackupManagerService.backupNow();
}
/** Removes PM state, triggering initialization in the next key-value task. */
@ -660,35 +616,69 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable {
new File(mStateDirectory, PM_PACKAGE).delete();
}
/** Same as {@link #extractAgentData(PackageInfo)}, but only for PM package. */
private void extractPmAgentData(PackageInfo packageInfo) throws AgentException, TaskException {
Preconditions.checkArgument(packageInfo.packageName.equals(PM_PACKAGE));
BackupAgent pmAgent = mBackupManagerService.makeMetadataAgent();
mAgent = IBackupAgent.Stub.asInterface(pmAgent.onBind());
extractAgentData(packageInfo, mAgent);
}
/**
* Returns a {@link Pair}. The first of the pair contains the status. In case the status is
* {@link BackupTransport#TRANSPORT_OK}, the second of the pair contains the agent result,
* otherwise {@code null}.
* Binds to the agent and extracts its backup data. If this method returns, the data in {@code
* mBackupData} is ready to be sent to the transport, otherwise it will throw.
*
* <p>This method leaves agent resources (agent binder, files and file-descriptors) opened that
* need to be cleaned up after terminating, either successfully or exceptionally. This clean-up
* can be done with methods {@link #cleanUpAgentForTransportStatus(int)} and {@link
* #cleanUpAgentForError(BackupException)}, depending on whether data was successfully sent to
* the transport or not. It's the caller responsibility to do the clean-up or delegate it.
*/
private Pair<Integer, RemoteResult> extractAgentData(String packageName, IBackupAgent agent) {
private void extractAgentData(PackageInfo packageInfo) throws AgentException, TaskException {
mBackupManagerService.setWorkSource(new WorkSource(packageInfo.applicationInfo.uid));
try {
mAgent = bindAgent(packageInfo);
extractAgentData(packageInfo, mAgent);
} finally {
mBackupManagerService.setWorkSource(null);
}
}
/**
* Calls agent {@link IBackupAgent#doBackup(ParcelFileDescriptor, ParcelFileDescriptor,
* ParcelFileDescriptor, long, IBackupCallback, int)} and waits for the result. If this method
* returns, the data in {@code mBackupData} is ready to be sent to the transport, otherwise it
* will throw.
*
* <p>This method creates files and file-descriptors for the agent that need to be deleted and
* closed after terminating, either successfully or exceptionally. This clean-up can be done
* with methods {@link #cleanUpAgentForTransportStatus(int)} and {@link
* #cleanUpAgentForError(BackupException)}, depending on whether data was successfully sent to
* the transport or not. It's the caller responsibility to do the clean-up or delegate it.
*/
private void extractAgentData(PackageInfo packageInfo, IBackupAgent agent)
throws AgentException, TaskException {
String packageName = packageInfo.packageName;
mReporter.onExtractAgentData(packageName);
File blankStateFile = new File(mStateDirectory, BLANK_STATE_FILE_NAME);
mSavedStateFile = new File(mStateDirectory, packageName);
mBackupDataFile = new File(mDataDirectory, packageName + STAGING_FILE_SUFFIX);
mNewStateFile = new File(mStateDirectory, packageName + NEW_STATE_FILE_SUFFIX);
mReporter.onAgentFilesReady(mBackupDataFile);
mSavedState = null;
mBackupData = null;
mNewState = null;
boolean callingAgent = false;
final RemoteResult agentResult;
try {
File savedStateFileForAgent = (mNonIncremental) ? blankStateFile : mSavedStateFile;
File savedStateFileForAgent = (mNonIncremental) ? mBlankStateFile : mSavedStateFile;
// MODE_CREATE to make an empty file if necessary
mSavedState = ParcelFileDescriptor.open(
savedStateFileForAgent, MODE_READ_ONLY | MODE_CREATE);
mBackupData = ParcelFileDescriptor.open(
mBackupDataFile, MODE_READ_WRITE | MODE_CREATE | MODE_TRUNCATE);
mNewState = ParcelFileDescriptor.open(
mNewStateFile, MODE_READ_WRITE | MODE_CREATE | MODE_TRUNCATE);
mSavedState =
ParcelFileDescriptor.open(savedStateFileForAgent, MODE_READ_ONLY | MODE_CREATE);
mBackupData =
ParcelFileDescriptor.open(
mBackupDataFile, MODE_READ_WRITE | MODE_CREATE | MODE_TRUNCATE);
mNewState =
ParcelFileDescriptor.open(
mNewStateFile, MODE_READ_WRITE | MODE_CREATE | MODE_TRUNCATE);
if (!SELinux.restorecon(mBackupDataFile)) {
mReporter.onRestoreconFailed(mBackupDataFile);
@ -713,15 +703,40 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable {
"doBackup()");
} catch (Exception e) {
mReporter.onCallAgentDoBackupError(packageName, callingAgent, e);
cleanUpAgentForAgentError();
// TODO: Remove the check on callingAgent when RemoteCall supports local agent calls.
int status =
callingAgent ? BackupTransport.AGENT_ERROR : BackupTransport.TRANSPORT_ERROR;
return Pair.create(status, null);
if (callingAgent) {
throw AgentException.transitory(e);
} else {
throw TaskException.create();
}
} finally {
mBlankStateFile.delete();
}
blankStateFile.delete();
checkAgentResult(packageInfo, agentResult);
}
return Pair.create(BackupTransport.TRANSPORT_OK, agentResult);
private void checkAgentResult(PackageInfo packageInfo, RemoteResult result)
throws AgentException, TaskException {
if (result == RemoteResult.FAILED_THREAD_INTERRUPTED) {
// Not an explicit cancel, we need to flag it.
mCancelled = true;
mReporter.onAgentCancelled(packageInfo);
throw TaskException.create();
}
if (result == RemoteResult.FAILED_CANCELLED) {
mReporter.onAgentCancelled(packageInfo);
throw TaskException.create();
}
if (result == RemoteResult.FAILED_TIMED_OUT) {
mReporter.onAgentTimedOut(packageInfo);
throw AgentException.transitory();
}
Preconditions.checkState(result.isPresent());
long resultCode = result.get();
if (resultCode == BackupAgent.RESULT_ERROR) {
mReporter.onAgentResultError(packageInfo);
throw AgentException.transitory();
}
Preconditions.checkState(resultCode == BackupAgent.RESULT_SUCCESS);
}
private void agentFail(IBackupAgent agent, String message) {
@ -801,94 +816,79 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable {
}
}
/** Returns whether to consume next queue package. */
private boolean sendDataToTransport() {
/** Returns transport status. */
private int sendDataToTransport() throws AgentException, TaskException, IOException {
Preconditions.checkState(mBackupData != null);
checkBackupData(mCurrentPackage.applicationInfo, mBackupDataFile);
String packageName = mCurrentPackage.packageName;
ApplicationInfo applicationInfo = mCurrentPackage.applicationInfo;
boolean writingWidgetData = false;
try {
if (!validateBackupData(applicationInfo, mBackupDataFile)) {
cleanUpAgentForAgentError();
return true;
}
writingWidgetData = true;
writeWidgetPayloadIfAppropriate(mBackupData.getFileDescriptor(), packageName);
} catch (IOException e) {
if (writingWidgetData) {
mReporter.onWriteWidgetDataError(packageName, e);
} else {
mReporter.onReadAgentDataError(packageName, e);
}
cleanUpAgentForAgentError();
revertTask();
return false;
}
writeWidgetPayloadIfAppropriate(mBackupData.getFileDescriptor(), packageName);
boolean nonIncremental = mSavedStateFile.length() == 0;
long size = mBackupDataFile.length();
if (size > 0) {
try (ParcelFileDescriptor backupData =
ParcelFileDescriptor.open(mBackupDataFile, MODE_READ_ONLY)) {
IBackupTransport transport =
mTransportClient.connectOrThrow("KVBT.sendDataToTransport()");
mReporter.onSendDataToTransport(packageName);
int flags = getPerformBackupFlags(mUserInitiated, nonIncremental);
mStatus = transport.performBackup(mCurrentPackage, backupData, flags);
if (mStatus == BackupTransport.TRANSPORT_OK) {
mStatus = transport.finishBackup();
}
} catch (Exception e) {
mReporter.onPackageBackupError(packageName, e);
mStatus = BackupTransport.TRANSPORT_ERROR;
}
} else {
mReporter.onEmptyData(mCurrentPackage);
mStatus = BackupTransport.TRANSPORT_OK;
}
if (nonIncremental
&& mStatus == BackupTransport.TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED) {
mReporter.onNonIncrementalAndNonIncrementalRequired();
mStatus = BackupTransport.TRANSPORT_ERROR;
}
boolean processQueue = handleTransportStatus(mStatus, packageName, size);
// We might report quota exceeded to the agent in handleTransportStatus() above, so we
// only clean-up after it.
cleanUpAgentForTransportStatus(mStatus);
return processQueue;
int status = transportPerformBackup(mCurrentPackage, mBackupDataFile, nonIncremental);
handleTransportStatus(status, packageName, mBackupDataFile.length());
return status;
}
/** Returns whether to consume next queue package. */
private boolean handleTransportStatus(int status, String packageName, long size) {
private int transportPerformBackup(
PackageInfo packageInfo, File backupDataFile, boolean nonIncremental)
throws TaskException {
String packageName = packageInfo.packageName;
long size = backupDataFile.length();
if (size <= 0) {
mReporter.onEmptyData(packageInfo);
return BackupTransport.TRANSPORT_OK;
}
int status;
try (ParcelFileDescriptor backupData =
ParcelFileDescriptor.open(backupDataFile, MODE_READ_ONLY)) {
IBackupTransport transport =
mTransportClient.connectOrThrow("KVBT.transportPerformBackup()");
mReporter.onTransportPerformBackup(packageName);
int flags = getPerformBackupFlags(mUserInitiated, nonIncremental);
status = transport.performBackup(packageInfo, backupData, flags);
if (status == BackupTransport.TRANSPORT_OK) {
status = transport.finishBackup();
}
} catch (Exception e) {
mReporter.onPackageBackupTransportError(packageName, e);
throw TaskException.causedBy(e);
}
if (nonIncremental && status == BackupTransport.TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED) {
mReporter.onPackageBackupNonIncrementalAndNonIncrementalRequired(packageName);
throw TaskException.create();
}
return status;
}
private void handleTransportStatus(int status, String packageName, long size)
throws TaskException, AgentException {
if (status == BackupTransport.TRANSPORT_OK) {
mReporter.onPackageBackupComplete(packageName, size);
return true;
}
if (status == BackupTransport.TRANSPORT_PACKAGE_REJECTED) {
mReporter.onPackageBackupRejected(packageName);
return true;
return;
}
if (status == BackupTransport.TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED) {
mReporter.onPackageBackupNonIncrementalRequired(mCurrentPackage);
// Immediately retry the current package.
mQueue.add(0, packageName);
return true;
return;
}
if (status == BackupTransport.TRANSPORT_PACKAGE_REJECTED) {
mReporter.onPackageBackupRejected(packageName);
throw AgentException.permanent();
}
if (status == BackupTransport.TRANSPORT_QUOTA_EXCEEDED) {
mReporter.onPackageBackupQuotaExceeded(packageName);
agentDoQuotaExceeded(mAgent, packageName, size);
return true;
throw AgentException.permanent();
}
// Any other error here indicates a transport-level failure.
mReporter.onPackageBackupTransportFailure(packageName);
revertTask();
return false;
throw TaskException.forStatus(status);
}
private void agentDoQuotaExceeded(@Nullable IBackupAgent agent, String packageName, long size) {
@ -908,19 +908,17 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable {
}
/**
* For system apps and pseudo-apps always return {@code true}. For regular apps returns whether
* {@code backupDataFile} doesn't have any protected keys.
*
* <p>If the app has attempted to write any protected keys we also crash them.
* For system apps and pseudo-apps never throws. For regular apps throws {@link AgentException}
* if {@code backupDataFile} has any protected keys, also crashing the app.
*/
private boolean validateBackupData(
@Nullable ApplicationInfo applicationInfo, File backupDataFile) throws IOException {
private void checkBackupData(@Nullable ApplicationInfo applicationInfo, File backupDataFile)
throws IOException, AgentException {
if (applicationInfo == null || (applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
// System apps and pseudo-apps can write what they want.
return true;
return;
}
try (ParcelFileDescriptor backupData =
ParcelFileDescriptor.open(backupDataFile, MODE_READ_ONLY)) {
ParcelFileDescriptor.open(backupDataFile, MODE_READ_ONLY)) {
BackupDataInput backupDataInput = new BackupDataInput(backupData.getFileDescriptor());
while (backupDataInput.readNextHeader()) {
String key = backupDataInput.getKey();
@ -928,12 +926,11 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable {
mReporter.onAgentIllegalKey(mCurrentPackage, key);
// Crash them if they wrote any protected keys.
agentFail(mAgent, "Illegal backup key: " + key);
return false;
throw AgentException.permanent();
}
backupDataInput.skipEntityData();
}
}
return true;
}
private int getPerformBackupFlags(boolean userInitiated, boolean nonIncremental) {
@ -1009,44 +1006,39 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable {
}
}
/** Cleans-up after having called the agent. */
/**
* Cleans up agent resources opened by {@link #extractAgentData(PackageInfo)} for exceptional
* case.
*
* <p>Note: Declaring exception parameter so that the caller only calls this when an exception
* is thrown.
*/
private void cleanUpAgentForError(BackupException exception) {
cleanUpAgent(StateTransaction.DISCARD_NEW);
}
/**
* Cleans up agent resources opened by {@link #extractAgentData(PackageInfo)} according to
* transport status returned in {@link #sendDataToTransport(PackageInfo)}.
*/
private void cleanUpAgentForTransportStatus(int status) {
updateFiles(status);
cleanUpAgent();
}
/** Cleans-up if we failed to call the agent. */
private void cleanUpAgentForAgentError() {
mBackupDataFile.delete();
mNewStateFile.delete();
cleanUpAgent();
}
private void updateFiles(int status) {
switch (status) {
case BackupTransport.TRANSPORT_OK:
mBackupDataFile.delete();
mNewStateFile.renameTo(mSavedStateFile);
cleanUpAgent(StateTransaction.COMMIT_NEW);
break;
case BackupTransport.TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED:
mSavedStateFile.delete();
mBackupDataFile.delete();
mNewStateFile.delete();
cleanUpAgent(StateTransaction.DISCARD_ALL);
break;
default:
// Includes:
// * BackupTransport.TRANSPORT_PACKAGE_REJECTED
// * BackupTransport.TRANSPORT_QUOTA_EXCEEDED
// * BackupTransport.TRANSPORT_ERROR
mBackupDataFile.delete();
mNewStateFile.delete();
break;
// All other transport statuses are properly converted to agent or task exceptions.
throw new AssertionError();
}
}
/** Cleans-up file-descriptors and unbinds agent. */
private void cleanUpAgent() {
mAgent = null;
private void cleanUpAgent(@StateTransaction int stateTransaction) {
applyStateTransaction(stateTransaction);
mBackupDataFile.delete();
mBlankStateFile.delete();
tryCloseFileDescriptor(mSavedState, "old state");
tryCloseFileDescriptor(mBackupData, "backup data");
tryCloseFileDescriptor(mNewState, "new state");
@ -1058,6 +1050,24 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable {
if (mCurrentPackage.applicationInfo != null) {
mBackupManagerService.unbindAgent(mCurrentPackage.applicationInfo);
}
mAgent = null;
}
private void applyStateTransaction(@StateTransaction int stateTransaction) {
switch (stateTransaction) {
case StateTransaction.COMMIT_NEW:
mNewStateFile.renameTo(mSavedStateFile);
break;
case StateTransaction.DISCARD_NEW:
mNewStateFile.delete();
break;
case StateTransaction.DISCARD_ALL:
mSavedStateFile.delete();
mNewStateFile.delete();
break;
default:
throw new IllegalArgumentException("Unknown state transaction " + stateTransaction);
}
}
private void tryCloseFileDescriptor(@Nullable Closeable closeable, String logName) {
@ -1079,4 +1089,16 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable {
mPendingCall = null;
return result;
}
@IntDef({
StateTransaction.COMMIT_NEW,
StateTransaction.DISCARD_NEW,
StateTransaction.DISCARD_ALL,
})
@Retention(RetentionPolicy.SOURCE)
private @interface StateTransaction {
int COMMIT_NEW = 0;
int DISCARD_NEW = 1;
int DISCARD_ALL = 2;
}
}

View File

@ -0,0 +1,83 @@
/*
* Copyright (C) 2018 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.backup.keyvalue;
import android.app.backup.BackupTransport;
import com.android.internal.util.Preconditions;
/**
* The key-value backup task has failed, no more packages will be processed and we shouldn't attempt
* any more backups now. These can be caused by transport failures (as opposed to agent failures).
*
* @see KeyValueBackupTask
* @see AgentException
*/
class TaskException extends BackupException {
private static final int DEFAULT_STATUS = BackupTransport.TRANSPORT_ERROR;
static TaskException stateCompromised() {
return new TaskException(/* stateCompromised */ true, DEFAULT_STATUS);
}
static TaskException stateCompromised(Exception cause) {
if (cause instanceof TaskException) {
TaskException exception = (TaskException) cause;
return new TaskException(cause, /* stateCompromised */ true, exception.getStatus());
}
return new TaskException(cause, /* stateCompromised */ true, DEFAULT_STATUS);
}
static TaskException forStatus(int status) {
Preconditions.checkArgument(
status != BackupTransport.TRANSPORT_OK, "Exception based on TRANSPORT_OK");
return new TaskException(/* stateCompromised */ false, status);
}
static TaskException causedBy(Exception cause) {
if (cause instanceof TaskException) {
return (TaskException) cause;
}
return new TaskException(cause, /* stateCompromised */ false, DEFAULT_STATUS);
}
static TaskException create() {
return new TaskException(/* stateCompromised */ false, DEFAULT_STATUS);
}
private final boolean mStateCompromised;
private final int mStatus;
private TaskException(Exception cause, boolean stateCompromised, int status) {
super(cause);
mStateCompromised = stateCompromised;
mStatus = status;
}
private TaskException(boolean stateCompromised, int status) {
mStateCompromised = stateCompromised;
mStatus = status;
}
boolean isStateCompromised() {
return mStateCompromised;
}
int getStatus() {
return mStatus;
}
}

View File

@ -0,0 +1,70 @@
/*
* Copyright (C) 2018 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.backup.keyvalue;
import static com.google.common.truth.Truth.assertThat;
import android.platform.test.annotations.Presubmit;
import com.android.server.testing.FrameworkRobolectricTestRunner;
import com.android.server.testing.SystemLoaderPackages;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.annotation.Config;
import java.io.IOException;
@RunWith(FrameworkRobolectricTestRunner.class)
@Config(manifest = Config.NONE, sdk = 26)
@SystemLoaderPackages({"com.android.server.backup"})
@Presubmit
public class AgentExceptionTest {
@Test
public void testTransitory_isTransitory() throws Exception {
AgentException exception = AgentException.transitory();
assertThat(exception.isTransitory()).isTrue();
}
@Test
public void testTransitory_withCause() throws Exception {
Exception cause = new IOException();
AgentException exception = AgentException.transitory(cause);
assertThat(exception.isTransitory()).isTrue();
assertThat(exception.getCause()).isEqualTo(cause);
}
@Test
public void testPermanent_isNotTransitory() throws Exception {
AgentException exception = AgentException.permanent();
assertThat(exception.isTransitory()).isFalse();
}
@Test
public void testPermanent_withCause() throws Exception {
Exception cause = new IOException();
AgentException exception = AgentException.permanent(cause);
assertThat(exception.isTransitory()).isFalse();
assertThat(exception.getCause()).isEqualTo(cause);
}
}

View File

@ -0,0 +1,45 @@
/*
* Copyright (C) 2018 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.backup.keyvalue;
import static com.google.common.truth.Truth.assertThat;
import android.platform.test.annotations.Presubmit;
import com.android.server.testing.FrameworkRobolectricTestRunner;
import com.android.server.testing.SystemLoaderPackages;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.annotation.Config;
import java.io.IOException;
@RunWith(FrameworkRobolectricTestRunner.class)
@Config(manifest = Config.NONE, sdk = 26)
@SystemLoaderPackages({"com.android.server.backup"})
@Presubmit
public class BackupExceptionTest {
@Test
public void testConstructor_passesCause() {
Exception cause = new IOException();
Exception exception = new BackupException(cause);
assertThat(exception.getCause()).isEqualTo(cause);
}
}

View File

@ -155,9 +155,7 @@ import java.util.List;
import java.util.concurrent.TimeoutException;
import java.util.stream.Stream;
// TODO: When returning to RUNNING_QUEUE vs FINAL, RUNNING_QUEUE sets status = OK. Why? Verify?
// TODO: Check queue in general, behavior w/ multiple packages
// TODO: Test PM invocation
// TODO: Test agents timing out
@RunWith(FrameworkRobolectricTestRunner.class)
@Config(
manifest = Config.NONE,
@ -369,6 +367,47 @@ public class KeyValueBackupTaskTest {
assertThat(mWakeLock.isHeld()).isFalse();
}
@Test
public void testRunTask_whenOnePackage_cleansUpPmFiles() throws Exception {
TransportMock transportMock = setUpInitializedTransport(mTransport);
setUpAgent(PACKAGE_1);
KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);
runTask(task);
assertCleansUpFiles(mTransport, PM_PACKAGE);
}
@Test
public void testRunTask_whenTransportReturnsTransportErrorForPm_cleansUpPmFiles()
throws Exception {
TransportMock transportMock = setUpInitializedTransport(mTransport);
when(transportMock.transport.performBackup(
argThat(packageInfo(PM_PACKAGE)), any(), anyInt()))
.thenReturn(BackupTransport.TRANSPORT_PACKAGE_REJECTED);
setUpAgent(PACKAGE_1);
KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);
runTask(task);
assertCleansUpFiles(mTransport, PM_PACKAGE);
}
@Test
public void testRunTask_whenTransportReturnsTransportErrorForPm_resetsBackupState()
throws Exception {
TransportMock transportMock = setUpInitializedTransport(mTransport);
when(transportMock.transport.performBackup(
argThat(packageInfo(PM_PACKAGE)), any(), anyInt()))
.thenReturn(BackupTransport.TRANSPORT_PACKAGE_REJECTED);
setUpAgent(PACKAGE_1);
KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);
runTask(task);
verify(mBackupManagerService).resetBackupState(getStateDirectory(mTransport).toFile());
}
@Test
public void testRunTask_whenOnePackage_updatesBookkeeping() throws Exception {
// Transport has to be initialized to not reset current token
@ -418,7 +457,7 @@ public class KeyValueBackupTaskTest {
public void testRunTask_whenNonPmPackageAndNonIncremental_doesNotBackUpPm() throws Exception {
TransportMock transportMock = setUpInitializedTransport(mTransport);
setUpAgentWithData(PACKAGE_1);
PackageManagerBackupAgent pmAgent = spy(createPmAgent());
BackupAgent pmAgent = spy(createPmAgent());
when(mBackupManagerService.makeMetadataAgent()).thenReturn(forward(pmAgent));
KeyValueBackupTask task = createKeyValueBackupTask(transportMock, true, PACKAGE_1);
@ -431,7 +470,7 @@ public class KeyValueBackupTaskTest {
public void testRunTask_whenNonPmPackageAndPmAndNonIncremental_backsUpPm() throws Exception {
TransportMock transportMock = setUpInitializedTransport(mTransport);
setUpAgentWithData(PACKAGE_1);
PackageManagerBackupAgent pmAgent = spy(createPmAgent());
BackupAgent pmAgent = spy(createPmAgent());
when(mBackupManagerService.makeMetadataAgent()).thenReturn(forward(pmAgent));
KeyValueBackupTask task =
createKeyValueBackupTask(transportMock, true, PACKAGE_1, PM_PACKAGE);
@ -445,7 +484,7 @@ public class KeyValueBackupTaskTest {
public void testRunTask_whenNonPmPackageAndIncremental_backsUpPm() throws Exception {
TransportMock transportMock = setUpInitializedTransport(mTransport);
setUpAgentWithData(PACKAGE_1);
PackageManagerBackupAgent pmAgent = spy(createPmAgent());
BackupAgent pmAgent = spy(createPmAgent());
when(mBackupManagerService.makeMetadataAgent()).thenReturn(forward(pmAgent));
KeyValueBackupTask task = createKeyValueBackupTask(transportMock, false, PACKAGE_1);
@ -528,6 +567,35 @@ public class KeyValueBackupTaskTest {
assertBackupPendingFor(PACKAGE_1);
}
@Test
public void testRunTask_whenPackageUnknown() throws Exception {
TransportMock transportMock = setUpInitializedTransport(mTransport);
// Not calling setUpAgent() for PACKAGE_1
KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);
runTask(task);
verify(transportMock.transport, never())
.performBackup(argThat(packageInfo(PACKAGE_1)), any(), anyInt());
verify(mObserver).onResult(PACKAGE_1.packageName, ERROR_PACKAGE_NOT_FOUND);
verify(mObserver).backupFinished(SUCCESS);
assertBackupNotPendingFor(PACKAGE_1);
}
@Test
public void testRunTask_whenFirstPackageUnknown_callsTransportForSecondPackage()
throws Exception {
TransportMock transportMock = setUpInitializedTransport(mTransport);
// Not calling setUpAgent() for PACKAGE_1
setUpAgentWithData(PACKAGE_2);
KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1, PACKAGE_2);
runTask(task);
verify(transportMock.transport)
.performBackup(argThat(packageInfo(PACKAGE_2)), any(), anyInt());
}
@Test
public void testRunTask_whenPackageNotEligibleForBackup() throws Exception {
TransportMock transportMock = setUpInitializedTransport(mTransport);
@ -544,6 +612,19 @@ public class KeyValueBackupTaskTest {
assertBackupNotPendingFor(PACKAGE_1);
}
@Test
public void testRunTask_whenFirstPackageNotEligibleForBackup_callsTransportForSecondPackage()
throws Exception {
TransportMock transportMock = setUpInitializedTransport(mTransport);
setUpAgentsWithData(PACKAGE_1.backupNotAllowed(), PACKAGE_2);
KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1, PACKAGE_2);
runTask(task);
verify(transportMock.transport)
.performBackup(argThat(packageInfo(PACKAGE_2)), any(), anyInt());
}
@Test
public void testRunTask_whenPackageDoesFullBackup() throws Exception {
TransportMock transportMock = setUpInitializedTransport(mTransport);
@ -560,6 +641,20 @@ public class KeyValueBackupTaskTest {
assertBackupNotPendingFor(PACKAGE_1);
}
@Test
public void testRunTask_whenFirstPackageDoesFullBackup_callsTransportForSecondPackage()
throws Exception {
TransportMock transportMock = setUpInitializedTransport(mTransport);
PackageData packageData = fullBackupPackage(1);
setUpAgentsWithData(packageData, PACKAGE_2);
KeyValueBackupTask task = createKeyValueBackupTask(transportMock, packageData, PACKAGE_2);
runTask(task);
verify(transportMock.transport)
.performBackup(argThat(packageInfo(PACKAGE_2)), any(), anyInt());
}
@Test
public void testRunTask_whenPackageIsStopped() throws Exception {
TransportMock transportMock = setUpInitializedTransport(mTransport);
@ -575,18 +670,16 @@ public class KeyValueBackupTaskTest {
}
@Test
public void testRunTask_whenPackageUnknown() throws Exception {
public void testRunTask_whenFirstPackageIsStopped_callsTransportForSecondPackage()
throws Exception {
TransportMock transportMock = setUpInitializedTransport(mTransport);
// Not calling setUpAgent()
KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);
setUpAgentsWithData(PACKAGE_1.stopped(), PACKAGE_2);
KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1, PACKAGE_2);
runTask(task);
verify(transportMock.transport, never())
.performBackup(argThat(packageInfo(PACKAGE_1)), any(), anyInt());
verify(mObserver).onResult(PACKAGE_1.packageName, ERROR_PACKAGE_NOT_FOUND);
verify(mObserver).backupFinished(SUCCESS);
assertBackupNotPendingFor(PACKAGE_1);
verify(transportMock.transport)
.performBackup(argThat(packageInfo(PACKAGE_2)), any(), anyInt());
}
@Test
@ -629,6 +722,7 @@ public class KeyValueBackupTaskTest {
verify(mBackupManagerService).setWorkSource(null);
verify(mObserver).onResult(PACKAGE_1.packageName, ERROR_AGENT_FAILURE);
verify(mObserver).backupFinished(BackupManager.SUCCESS);
assertBackupPendingFor(PACKAGE_1);
}
@Test
@ -645,6 +739,7 @@ public class KeyValueBackupTaskTest {
verify(mBackupManagerService).setWorkSource(null);
verify(mObserver).onResult(PACKAGE_1.packageName, ERROR_AGENT_FAILURE);
verify(mObserver).backupFinished(BackupManager.SUCCESS);
assertBackupPendingFor(PACKAGE_1);
}
@Test
@ -798,7 +893,7 @@ public class KeyValueBackupTaskTest {
runTask(task);
assertBackupNotPendingFor(PACKAGE_1);
assertBackupPendingFor(PACKAGE_1);
}
@Test
@ -1139,6 +1234,38 @@ public class KeyValueBackupTaskTest {
}
}
@Test
public void testRunTask_whenPmAgentWritesData_callsTransportPerformBackupWithAgentData()
throws Exception {
TransportMock transportMock = setUpInitializedTransport(mTransport);
setUpAgent(PACKAGE_1);
Path backupDataPath = createTemporaryFile();
when(transportMock.transport.performBackup(
argThat(packageInfo(PM_PACKAGE)), any(), anyInt()))
.then(copyBackupDataTo(backupDataPath));
BackupAgent pmAgent = spy(createPmAgent());
when(mBackupManagerService.makeMetadataAgent()).thenReturn(forward(pmAgent));
agentOnBackupDo(
pmAgent,
(oldState, dataOutput, newState) -> {
writeData(dataOutput, "key1", "data1".getBytes());
writeData(dataOutput, "key2", "data2".getBytes());
writeState(newState, "newState".getBytes());
});
KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);
runTask(task);
verify(transportMock.transport)
.performBackup(argThat(packageInfo(PM_PACKAGE)), any(), anyInt());
try (FileInputStream inputStream = new FileInputStream(backupDataPath.toFile())) {
BackupDataInput backupData = new BackupDataInput(inputStream.getFD());
assertDataHasKeyValue(backupData, "key1", "data1".getBytes());
assertDataHasKeyValue(backupData, "key2", "data2".getBytes());
assertThat(backupData.readNextHeader()).isFalse();
}
}
@Test
public void testRunTask_whenPerformBackupSucceeds_callsTransportFinishBackup()
throws Exception {
@ -1175,6 +1302,50 @@ public class KeyValueBackupTaskTest {
assertCleansUpFilesAndAgent(mTransport, PACKAGE_1);
}
@Test
public void testRunTask_whenFinishBackupSucceedsForPm_cleansUp() throws Exception {
TransportMock transportMock = setUpInitializedTransport(mTransport);
setUpAgent(PACKAGE_1);
when(transportMock.transport.finishBackup()).thenReturn(BackupTransport.TRANSPORT_OK);
BackupAgent pmAgent = spy(createPmAgent());
when(mBackupManagerService.makeMetadataAgent()).thenReturn(forward(pmAgent));
agentOnBackupDo(
pmAgent,
(oldState, dataOutput, newState) -> {
writeData(dataOutput, "key", "data".getBytes());
writeState(newState, "newState".getBytes());
});
KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);
runTask(task);
assertThat(Files.readAllBytes(getStateFile(mTransport, PM_PACKAGE)))
.isEqualTo("newState".getBytes());
assertCleansUpFiles(mTransport, PM_PACKAGE);
// We don't unbind PM
verify(mBackupManagerService, never()).unbindAgent(argThat(applicationInfo(PM_PACKAGE)));
}
@Test
public void testRunTask_whenFinishBackupSucceedsForPm_doesNotUnbindPm() throws Exception {
TransportMock transportMock = setUpInitializedTransport(mTransport);
setUpAgent(PACKAGE_1);
when(transportMock.transport.finishBackup()).thenReturn(BackupTransport.TRANSPORT_OK);
BackupAgent pmAgent = spy(createPmAgent());
when(mBackupManagerService.makeMetadataAgent()).thenReturn(forward(pmAgent));
agentOnBackupDo(
pmAgent,
(oldState, dataOutput, newState) -> {
writeData(dataOutput, "key", "data".getBytes());
writeState(newState, "newState".getBytes());
});
KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);
runTask(task);
verify(mBackupManagerService, never()).unbindAgent(argThat(applicationInfo(PM_PACKAGE)));
}
@Test
public void testRunTask_whenFinishBackupSucceeds_logsBackupPackageEvent() throws Exception {
TransportMock transportMock = setUpInitializedTransport(mTransport);
@ -1354,6 +1525,7 @@ public class KeyValueBackupTaskTest {
public void testRunTask_whenTransportReturnsQuotaExceeded_updatesBookkeeping()
throws Exception {
TransportMock transportMock = setUpInitializedTransport(mTransport);
setUpAgentWithData(PACKAGE_1);
when(transportMock.transport.performBackup(
argThat(packageInfo(PACKAGE_1)), any(), anyInt()))
.thenReturn(BackupTransport.TRANSPORT_QUOTA_EXCEEDED);
@ -1701,9 +1873,9 @@ public class KeyValueBackupTaskTest {
}
@Test
public void testRunTask_whenPmAgentFails() throws Exception {
public void testRunTask_whenPmAgentFails_reportsCorrectly() throws Exception {
TransportMock transportMock = setUpInitializedTransport(mTransport);
PackageManagerBackupAgent pmAgent = createThrowingPmAgent(new RuntimeException());
BackupAgent pmAgent = createThrowingPmAgent(new RuntimeException());
when(mBackupManagerService.makeMetadataAgent()).thenReturn(pmAgent);
KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);
@ -1717,6 +1889,75 @@ public class KeyValueBackupTaskTest {
new RuntimeException().toString());
}
@Test
public void testRunTask_whenPmAgentFails_revertsTask() throws Exception {
TransportMock transportMock = setUpInitializedTransport(mTransport);
setUpAgent(PACKAGE_1);
BackupAgent pmAgent = createThrowingPmAgent(new RuntimeException());
when(mBackupManagerService.makeMetadataAgent()).thenReturn(pmAgent);
KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);
runTask(task);
assertTaskReverted(transportMock, PACKAGE_1);
}
@Test
public void testRunTask_whenPmAgentFails_cleansUpFiles() throws Exception {
TransportMock transportMock = setUpInitializedTransport(mTransport);
setUpAgent(PACKAGE_1);
BackupAgent pmAgent = createThrowingPmAgent(new RuntimeException());
when(mBackupManagerService.makeMetadataAgent()).thenReturn(pmAgent);
KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);
runTask(task);
assertCleansUpFiles(mTransport, PM_PACKAGE);
}
@Test
public void testRunTask_whenPmAgentFails_resetsBackupState() throws Exception {
TransportMock transportMock = setUpInitializedTransport(mTransport);
setUpAgent(PACKAGE_1);
BackupAgent pmAgent = createThrowingPmAgent(new RuntimeException());
when(mBackupManagerService.makeMetadataAgent()).thenReturn(pmAgent);
KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);
runTask(task);
verify(mBackupManagerService).resetBackupState(getStateDirectory(mTransport).toFile());
}
@Test
public void testRunTask_whenMarkCancelDuringPmOnBackup_resetsBackupState() throws Exception {
TransportMock transportMock = setUpInitializedTransport(mTransport);
setUpAgent(PACKAGE_1);
BackupAgent pmAgent = spy(createPmAgent());
when(mBackupManagerService.makeMetadataAgent()).thenReturn(forward(pmAgent));
KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);
agentOnBackupDo(
pmAgent, (oldState, dataOutput, newState) -> runInWorkerThread(task::markCancel));
runTask(task);
verify(mBackupManagerService).resetBackupState(getStateDirectory(mTransport).toFile());
}
@Test
public void testRunTask_whenMarkCancelDuringPmOnBackup_cleansUpFiles() throws Exception {
TransportMock transportMock = setUpInitializedTransport(mTransport);
setUpAgent(PACKAGE_1);
BackupAgent pmAgent = spy(createPmAgent());
when(mBackupManagerService.makeMetadataAgent()).thenReturn(forward(pmAgent));
KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);
agentOnBackupDo(
pmAgent, (oldState, dataOutput, newState) -> runInWorkerThread(task::markCancel));
runTask(task);
assertCleansUpFiles(mTransport, PM_PACKAGE);
}
@Test
public void testRunTask_whenBackupRunning_doesNotThrow() throws Exception {
TransportMock transportMock = setUpInitializedTransport(mTransport);
@ -1736,7 +1977,7 @@ public class KeyValueBackupTaskTest {
runTask(task);
verify(mReporter).onReadAgentDataError(eq(PACKAGE_1.packageName), any());
verify(mReporter).onAgentDataError(eq(PACKAGE_1.packageName), any());
}
@Test
@ -1778,6 +2019,24 @@ public class KeyValueBackupTaskTest {
assertTaskReverted(transportMock, PACKAGE_1, PACKAGE_2);
}
@Test
public void testRunTask_whenMarkCancelDuringAgentOnBackup_cleansUpFiles() throws Exception {
TransportMock transportMock = setUpInitializedTransport(mTransport);
AgentMock agentMock = setUpAgent(PACKAGE_1);
KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);
agentOnBackupDo(
agentMock,
(oldState, dataOutput, newState) -> {
writeData(dataOutput, "key", "data".getBytes());
writeState(newState, "newState".getBytes());
runInWorkerThread(task::markCancel);
});
runTask(task);
assertCleansUpFiles(mTransport, PACKAGE_1);
}
@Test
public void
testRunTask_whenMarkCancelDuringFirstAgentOnBackup_doesNotCallTransportAfterWaitCancel()
@ -2293,20 +2552,28 @@ public class KeyValueBackupTaskTest {
*/
private static void agentOnBackupDo(AgentMock agentMock, BackupAgentOnBackup function)
throws Exception {
doAnswer(
(BackupAgentOnBackup)
(oldState, dataOutput, newState) -> {
ByteArrayOutputStream outputStream =
new ByteArrayOutputStream();
transferStreamedData(
new FileInputStream(oldState.getFileDescriptor()),
outputStream);
agentMock.oldState = outputStream.toByteArray();
agentMock.oldStateHistory.add(agentMock.oldState);
function.onBackup(oldState, dataOutput, newState);
})
.when(agentMock.agent)
.onBackup(any(), any(), any());
agentOnBackupDo(
agentMock.agent,
(oldState, dataOutput, newState) -> {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
transferStreamedData(
new FileInputStream(oldState.getFileDescriptor()), outputStream);
agentMock.oldState = outputStream.toByteArray();
agentMock.oldStateHistory.add(agentMock.oldState);
function.onBackup(oldState, dataOutput, newState);
});
}
/**
* Implements {@code function} for {@link BackupAgent#onBackup(ParcelFileDescriptor,
* BackupDataOutput, ParcelFileDescriptor)} of {@code agentMock}.
*
* @see #agentOnBackupDo(AgentMock, BackupAgentOnBackup)
* @see #remoteAgentOnBackupThrows(AgentMock, BackupAgentOnBackup)
*/
private static void agentOnBackupDo(BackupAgent backupAgent, BackupAgentOnBackup function)
throws IOException {
doAnswer(function).when(backupAgent).onBackup(any(), any(), any());
}
/**
@ -2400,6 +2667,10 @@ public class KeyValueBackupTaskTest {
// constructor
assertJournalDoesNotContain(mBackupManagerService.getJournal(), packageName);
assertThat(mBackupManagerService.getPendingBackups()).doesNotContainKey(packageName);
// Also verifying BMS is never called since for some cases the package wouldn't be
// pending for other reasons (for example it's not eligible for backup). Regardless of
// these reasons, we shouldn't mark them as pending backup (call dataChangedImpl()).
verify(mBackupManagerService, never()).dataChangedImpl(packageName);
}
}

View File

@ -0,0 +1,152 @@
/*
* Copyright (C) 2018 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.backup.keyvalue;
import static com.google.common.truth.Truth.assertThat;
import static org.testng.Assert.expectThrows;
import android.app.backup.BackupTransport;
import android.platform.test.annotations.Presubmit;
import com.android.server.testing.FrameworkRobolectricTestRunner;
import com.android.server.testing.SystemLoaderPackages;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.annotation.Config;
import java.io.IOException;
@RunWith(FrameworkRobolectricTestRunner.class)
@Config(manifest = Config.NONE, sdk = 26)
@SystemLoaderPackages({"com.android.server.backup"})
@Presubmit
public class TaskExceptionTest {
@Test
public void testStateCompromised() {
TaskException exception = TaskException.stateCompromised();
assertThat(exception.isStateCompromised()).isTrue();
assertThat(exception.getStatus()).isEqualTo(BackupTransport.TRANSPORT_ERROR);
}
@Test
public void testStateCompromised_whenCauseInstanceOfTaskException() {
Exception cause = TaskException.forStatus(BackupTransport.TRANSPORT_NOT_INITIALIZED);
TaskException exception = TaskException.stateCompromised(cause);
assertThat(exception.isStateCompromised()).isTrue();
assertThat(exception.getStatus()).isEqualTo(BackupTransport.TRANSPORT_NOT_INITIALIZED);
assertThat(exception.getCause()).isEqualTo(cause);
}
@Test
public void testStateCompromised_whenCauseNotInstanceOfTaskException() {
Exception cause = new IOException();
TaskException exception = TaskException.stateCompromised(cause);
assertThat(exception.isStateCompromised()).isTrue();
assertThat(exception.getStatus()).isEqualTo(BackupTransport.TRANSPORT_ERROR);
assertThat(exception.getCause()).isEqualTo(cause);
}
@Test
public void testForStatus_whenTransportOk_throws() {
expectThrows(
IllegalArgumentException.class,
() -> TaskException.forStatus(BackupTransport.TRANSPORT_OK));
}
@Test
public void testForStatus_whenTransportNotInitialized() {
TaskException exception =
TaskException.forStatus(BackupTransport.TRANSPORT_NOT_INITIALIZED);
assertThat(exception.isStateCompromised()).isFalse();
assertThat(exception.getStatus()).isEqualTo(BackupTransport.TRANSPORT_NOT_INITIALIZED);
}
@Test
public void testCausedBy_whenCauseInstanceOfTaskException_returnsCause() {
Exception cause = TaskException.forStatus(BackupTransport.TRANSPORT_NOT_INITIALIZED);
TaskException exception = TaskException.causedBy(cause);
assertThat(exception).isEqualTo(cause);
}
@Test
public void testCausedBy_whenCauseNotInstanceOfTaskException() {
Exception cause = new IOException();
TaskException exception = TaskException.causedBy(cause);
assertThat(exception).isNotEqualTo(cause);
assertThat(exception.isStateCompromised()).isFalse();
assertThat(exception.getStatus()).isEqualTo(BackupTransport.TRANSPORT_ERROR);
assertThat(exception.getCause()).isEqualTo(cause);
}
@Test
public void testCreate() {
TaskException exception = TaskException.create();
assertThat(exception.isStateCompromised()).isFalse();
assertThat(exception.getStatus()).isEqualTo(BackupTransport.TRANSPORT_ERROR);
}
@Test
public void testIsStateCompromised_whenStateCompromised_returnsTrue() {
TaskException taskException = TaskException.stateCompromised();
boolean stateCompromised = taskException.isStateCompromised();
assertThat(stateCompromised).isTrue();
}
@Test
public void testIsStateCompromised_whenCreatedWithCreate_returnsFalse() {
TaskException taskException = TaskException.create();
boolean stateCompromised = taskException.isStateCompromised();
assertThat(stateCompromised).isFalse();
}
@Test
public void testGetStatus_whenStatusIsTransportPackageRejected() {
TaskException taskException =
TaskException.forStatus(BackupTransport.TRANSPORT_PACKAGE_REJECTED);
int status = taskException.getStatus();
assertThat(status).isEqualTo(BackupTransport.TRANSPORT_PACKAGE_REJECTED);
}
@Test
public void testGetStatus_whenStatusIsTransportNotInitialized() {
TaskException taskException =
TaskException.forStatus(BackupTransport.TRANSPORT_NOT_INITIALIZED);
int status = taskException.getStatus();
assertThat(status).isEqualTo(BackupTransport.TRANSPORT_NOT_INITIALIZED);
}
}