blob: 86e679f16f662caabfa794761b782a069bb790b3 [file] [log] [blame]
/*
* Copyright (C) 2017 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.fullbackup;
import static com.android.server.backup.BackupManagerService.DEBUG;
import static com.android.server.backup.BackupManagerService.DEBUG_SCHEDULING;
import static com.android.server.backup.BackupManagerService.MORE_DEBUG;
import static com.android.server.backup.UserBackupManagerService.OP_PENDING;
import static com.android.server.backup.UserBackupManagerService.OP_TYPE_BACKUP;
import static com.android.server.backup.UserBackupManagerService.OP_TYPE_BACKUP_WAIT;
import android.annotation.Nullable;
import android.app.IBackupAgent;
import android.app.backup.BackupManager;
import android.app.backup.BackupManagerMonitor;
import android.app.backup.BackupProgress;
import android.app.backup.BackupTransport;
import android.app.backup.IBackupManagerMonitor;
import android.app.backup.IBackupObserver;
import android.app.backup.IFullBackupRestoreObserver;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.util.EventLog;
import android.util.Log;
import android.util.Slog;
import com.android.internal.backup.IBackupTransport;
import com.android.internal.util.Preconditions;
import com.android.server.EventLogTags;
import com.android.server.backup.BackupAgentTimeoutParameters;
import com.android.server.backup.BackupRestoreTask;
import com.android.server.backup.FullBackupJob;
import com.android.server.backup.TransportManager;
import com.android.server.backup.UserBackupManagerService;
import com.android.server.backup.internal.OnTaskFinishedListener;
import com.android.server.backup.internal.Operation;
import com.android.server.backup.remote.RemoteCall;
import com.android.server.backup.transport.TransportClient;
import com.android.server.backup.transport.TransportNotAvailableException;
import com.android.server.backup.utils.AppBackupUtils;
import com.android.server.backup.utils.BackupManagerMonitorUtils;
import com.android.server.backup.utils.BackupObserverUtils;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
/**
* Full backup task extension used for transport-oriented operation.
*
* Flow:
* For each requested package:
* - Spin off a new SinglePackageBackupRunner (mBackupRunner) for the current package.
* - Wait until preflight is complete. (mBackupRunner.getPreflightResultBlocking())
* - If preflight data size is within limit, start reading data from agent pipe and writing
* to transport pipe. While there is data to send, call transport.sendBackupData(int) to
* tell the transport how many bytes to expect on its pipe.
* - After sending all data, call transport.finishBackup() if things went well. And
* transport.cancelFullBackup() otherwise.
*
* Interactions with mCurrentOperations:
* - An entry for this object is added to mCurrentOperations for the entire lifetime of this
* object. Used to cancel the operation.
* - SinglePackageBackupRunner and SinglePackageBackupPreflight will put ephemeral entries
* to get timeouts or operation complete callbacks.
*
* Handling cancels:
* - The contract we provide is that the task won't interact with the transport after
* handleCancel() is done executing.
* - This task blocks at 3 points: 1. Preflight result check 2. Reading on agent side pipe
* and 3. Get backup result from mBackupRunner.
* - Bubbling up handleCancel to mBackupRunner handles all 3: 1. Calls handleCancel on the
* preflight operation which counts down on the preflight latch. 2. Tears down the agent,
* so read() returns -1. 3. Notifies mCurrentOpLock which unblocks
* mBackupRunner.getBackupResultBlocking().
*/
public class PerformFullTransportBackupTask extends FullBackupTask implements BackupRestoreTask {
public static PerformFullTransportBackupTask newWithCurrentTransport(
UserBackupManagerService backupManagerService,
IFullBackupRestoreObserver observer,
String[] whichPackages,
boolean updateSchedule,
FullBackupJob runningJob,
CountDownLatch latch,
IBackupObserver backupObserver,
IBackupManagerMonitor monitor,
boolean userInitiated,
String caller) {
TransportManager transportManager = backupManagerService.getTransportManager();
TransportClient transportClient = transportManager.getCurrentTransportClient(caller);
OnTaskFinishedListener listener =
listenerCaller ->
transportManager.disposeOfTransportClient(transportClient, listenerCaller);
return new PerformFullTransportBackupTask(
backupManagerService,
transportClient,
observer,
whichPackages,
updateSchedule,
runningJob,
latch,
backupObserver,
monitor,
listener,
userInitiated);
}
private static final String TAG = "PFTBT";
private UserBackupManagerService backupManagerService;
private final Object mCancelLock = new Object();
ArrayList<PackageInfo> mPackages;
PackageInfo mCurrentPackage;
boolean mUpdateSchedule;
CountDownLatch mLatch;
FullBackupJob mJob; // if a scheduled job needs to be finished afterwards
IBackupObserver mBackupObserver;
@Nullable private IBackupManagerMonitor mMonitor;
boolean mUserInitiated;
SinglePackageBackupRunner mBackupRunner;
private final int mBackupRunnerOpToken;
private final OnTaskFinishedListener mListener;
private final TransportClient mTransportClient;
private final int mUserId;
// This is true when a backup operation for some package is in progress.
private volatile boolean mIsDoingBackup;
private volatile boolean mCancelAll;
private final int mCurrentOpToken;
private final BackupAgentTimeoutParameters mAgentTimeoutParameters;
public PerformFullTransportBackupTask(UserBackupManagerService backupManagerService,
TransportClient transportClient,
IFullBackupRestoreObserver observer,
String[] whichPackages, boolean updateSchedule,
FullBackupJob runningJob, CountDownLatch latch, IBackupObserver backupObserver,
@Nullable IBackupManagerMonitor monitor, @Nullable OnTaskFinishedListener listener,
boolean userInitiated) {
super(observer);
this.backupManagerService = backupManagerService;
mTransportClient = transportClient;
mUpdateSchedule = updateSchedule;
mLatch = latch;
mJob = runningJob;
mPackages = new ArrayList<>(whichPackages.length);
mBackupObserver = backupObserver;
mMonitor = monitor;
mListener = (listener != null) ? listener : OnTaskFinishedListener.NOP;
mUserInitiated = userInitiated;
mCurrentOpToken = backupManagerService.generateRandomIntegerToken();
mBackupRunnerOpToken = backupManagerService.generateRandomIntegerToken();
mAgentTimeoutParameters = Preconditions.checkNotNull(
backupManagerService.getAgentTimeoutParameters(),
"Timeout parameters cannot be null");
mUserId = backupManagerService.getUserId();
if (backupManagerService.isBackupOperationInProgress()) {
if (DEBUG) {
Slog.d(TAG, "Skipping full backup. A backup is already in progress.");
}
mCancelAll = true;
return;
}
registerTask();
for (String pkg : whichPackages) {
try {
PackageManager pm = backupManagerService.getPackageManager();
PackageInfo info = pm.getPackageInfoAsUser(pkg,
PackageManager.GET_SIGNING_CERTIFICATES, mUserId);
mCurrentPackage = info;
if (!AppBackupUtils.appIsEligibleForBackup(info.applicationInfo, mUserId)) {
// Cull any packages that have indicated that backups are not permitted,
// that run as system-domain uids but do not define their own backup agents,
// as well as any explicit mention of the 'special' shared-storage agent
// package (we handle that one at the end).
if (MORE_DEBUG) {
Slog.d(TAG, "Ignoring ineligible package " + pkg);
}
mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_INELIGIBLE,
mCurrentPackage,
BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
null);
BackupObserverUtils.sendBackupOnPackageResult(mBackupObserver, pkg,
BackupManager.ERROR_BACKUP_NOT_ALLOWED);
continue;
} else if (!AppBackupUtils.appGetsFullBackup(info)) {
// Cull any packages that are found in the queue but now aren't supposed
// to get full-data backup operations.
if (MORE_DEBUG) {
Slog.d(TAG, "Ignoring full-data backup of key/value participant "
+ pkg);
}
mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_KEY_VALUE_PARTICIPANT,
mCurrentPackage,
BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
null);
BackupObserverUtils.sendBackupOnPackageResult(mBackupObserver, pkg,
BackupManager.ERROR_BACKUP_NOT_ALLOWED);
continue;
} else if (AppBackupUtils.appIsStopped(info.applicationInfo)) {
// Cull any packages in the 'stopped' state: they've either just been
// installed or have explicitly been force-stopped by the user. In both
// cases we do not want to launch them for backup.
if (MORE_DEBUG) {
Slog.d(TAG, "Ignoring stopped package " + pkg);
}
mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_STOPPED,
mCurrentPackage,
BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
null);
BackupObserverUtils.sendBackupOnPackageResult(mBackupObserver, pkg,
BackupManager.ERROR_BACKUP_NOT_ALLOWED);
continue;
}
mPackages.add(info);
} catch (NameNotFoundException e) {
Slog.i(TAG, "Requested package " + pkg + " not found; ignoring");
mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_NOT_FOUND,
mCurrentPackage,
BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
null);
}
}
}
private void registerTask() {
synchronized (backupManagerService.getCurrentOpLock()) {
Slog.d(TAG, "backupmanager pftbt token=" + Integer.toHexString(mCurrentOpToken));
backupManagerService.getCurrentOperations().put(
mCurrentOpToken,
new Operation(OP_PENDING, this, OP_TYPE_BACKUP));
}
}
public void unregisterTask() {
backupManagerService.removeOperation(mCurrentOpToken);
}
@Override
public void execute() {
// Nothing to do.
}
@Override
public void handleCancel(boolean cancelAll) {
synchronized (mCancelLock) {
// We only support 'cancelAll = true' case for this task. Cancelling of a single package
// due to timeout is handled by SinglePackageBackupRunner and
// SinglePackageBackupPreflight.
if (!cancelAll) {
Slog.wtf(TAG, "Expected cancelAll to be true.");
}
if (mCancelAll) {
Slog.d(TAG, "Ignoring duplicate cancel call.");
return;
}
mCancelAll = true;
if (mIsDoingBackup) {
backupManagerService.handleCancel(mBackupRunnerOpToken, cancelAll);
try {
// If we're running a backup we should be connected to a transport
IBackupTransport transport =
mTransportClient.getConnectedTransport("PFTBT.handleCancel()");
transport.cancelFullBackup();
} catch (RemoteException | TransportNotAvailableException e) {
Slog.w(TAG, "Error calling cancelFullBackup() on transport: " + e);
// Can't do much.
}
}
}
}
@Override
public void operationComplete(long result) {
// Nothing to do.
}
@Override
public void run() {
// data from the app, passed to us for bridging to the transport
ParcelFileDescriptor[] enginePipes = null;
// Pipe through which we write data to the transport
ParcelFileDescriptor[] transportPipes = null;
long backoff = 0;
int backupRunStatus = BackupManager.SUCCESS;
try {
if (!backupManagerService.isEnabled() || !backupManagerService.isSetupComplete()) {
// Backups are globally disabled, so don't proceed.
if (DEBUG) {
Slog.i(TAG, "full backup requested but enabled=" + backupManagerService
.isEnabled()
+ " setupComplete=" + backupManagerService.isSetupComplete()
+ "; ignoring");
}
int monitoringEvent;
if (backupManagerService.isSetupComplete()) {
monitoringEvent = BackupManagerMonitor.LOG_EVENT_ID_BACKUP_DISABLED;
} else {
monitoringEvent = BackupManagerMonitor.LOG_EVENT_ID_DEVICE_NOT_PROVISIONED;
}
mMonitor = BackupManagerMonitorUtils
.monitorEvent(mMonitor, monitoringEvent, null,
BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
null);
mUpdateSchedule = false;
backupRunStatus = BackupManager.ERROR_BACKUP_NOT_ALLOWED;
return;
}
IBackupTransport transport = mTransportClient.connect("PFTBT.run()");
if (transport == null) {
Slog.w(TAG, "Transport not present; full data backup not performed");
backupRunStatus = BackupManager.ERROR_TRANSPORT_ABORTED;
mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_TRANSPORT_NOT_PRESENT,
mCurrentPackage, BackupManagerMonitor.LOG_EVENT_CATEGORY_TRANSPORT,
null);
return;
}
// Set up to send data to the transport
final int N = mPackages.size();
final byte[] buffer = new byte[8192];
for (int i = 0; i < N; i++) {
mBackupRunner = null;
PackageInfo currentPackage = mPackages.get(i);
String packageName = currentPackage.packageName;
if (DEBUG) {
Slog.i(TAG, "Initiating full-data transport backup of " + packageName
+ " token: " + mCurrentOpToken);
}
EventLog.writeEvent(EventLogTags.FULL_BACKUP_PACKAGE, packageName);
transportPipes = ParcelFileDescriptor.createPipe();
// Tell the transport the data's coming
int flags = mUserInitiated ? BackupTransport.FLAG_USER_INITIATED : 0;
int backupPackageStatus;
long quota = Long.MAX_VALUE;
synchronized (mCancelLock) {
if (mCancelAll) {
break;
}
backupPackageStatus = transport.performFullBackup(currentPackage,
transportPipes[0], flags);
if (backupPackageStatus == BackupTransport.TRANSPORT_OK) {
quota = transport.getBackupQuota(currentPackage.packageName,
true /* isFullBackup */);
// Now set up the backup engine / data source end of things
enginePipes = ParcelFileDescriptor.createPipe();
mBackupRunner =
new SinglePackageBackupRunner(enginePipes[1], currentPackage,
mTransportClient, quota, mBackupRunnerOpToken,
transport.getTransportFlags());
// The runner dup'd the pipe half, so we close it here
enginePipes[1].close();
enginePipes[1] = null;
mIsDoingBackup = true;
}
}
if (backupPackageStatus == BackupTransport.TRANSPORT_OK) {
// The transport has its own copy of the read end of the pipe,
// so close ours now
transportPipes[0].close();
transportPipes[0] = null;
// Spin off the runner to fetch the app's data and pipe it
// into the engine pipes
(new Thread(mBackupRunner, "package-backup-bridge")).start();
// Read data off the engine pipe and pass it to the transport
// pipe until we hit EOD on the input stream. We do not take
// close() responsibility for these FDs into these stream wrappers.
FileInputStream in = new FileInputStream(
enginePipes[0].getFileDescriptor());
FileOutputStream out = new FileOutputStream(
transportPipes[1].getFileDescriptor());
long totalRead = 0;
final long preflightResult = mBackupRunner.getPreflightResultBlocking();
// Preflight result is negative if some error happened on preflight.
if (preflightResult < 0) {
if (MORE_DEBUG) {
Slog.d(TAG, "Backup error after preflight of package "
+ packageName + ": " + preflightResult
+ ", not running backup.");
}
mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
BackupManagerMonitor.LOG_EVENT_ID_ERROR_PREFLIGHT,
mCurrentPackage,
BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
BackupManagerMonitorUtils.putMonitoringExtra(null,
BackupManagerMonitor.EXTRA_LOG_PREFLIGHT_ERROR,
preflightResult));
backupPackageStatus = (int) preflightResult;
} else {
int nRead = 0;
do {
nRead = in.read(buffer);
if (MORE_DEBUG) {
Slog.v(TAG, "in.read(buffer) from app: " + nRead);
}
if (nRead > 0) {
out.write(buffer, 0, nRead);
synchronized (mCancelLock) {
if (!mCancelAll) {
backupPackageStatus = transport.sendBackupData(nRead);
}
}
totalRead += nRead;
if (mBackupObserver != null && preflightResult > 0) {
BackupObserverUtils
.sendBackupOnUpdate(mBackupObserver, packageName,
new BackupProgress(preflightResult, totalRead));
}
}
} while (nRead > 0
&& backupPackageStatus == BackupTransport.TRANSPORT_OK);
// Despite preflight succeeded, package still can hit quota on flight.
if (backupPackageStatus == BackupTransport.TRANSPORT_QUOTA_EXCEEDED) {
Slog.w(TAG, "Package hit quota limit in-flight " + packageName
+ ": " + totalRead + " of " + quota);
mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
BackupManagerMonitor.LOG_EVENT_ID_QUOTA_HIT_PREFLIGHT,
mCurrentPackage,
BackupManagerMonitor.LOG_EVENT_CATEGORY_TRANSPORT,
null);
mBackupRunner.sendQuotaExceeded(totalRead, quota);
}
}
final int backupRunnerResult = mBackupRunner.getBackupResultBlocking();
synchronized (mCancelLock) {
mIsDoingBackup = false;
// If mCancelCurrent is true, we have already called cancelFullBackup().
if (!mCancelAll) {
if (backupRunnerResult == BackupTransport.TRANSPORT_OK) {
// If we were otherwise in a good state, now interpret the final
// result based on what finishBackup() returns. If we're in a
// failure case already, preserve that result and ignore whatever
// finishBackup() reports.
final int finishResult = transport.finishBackup();
if (backupPackageStatus == BackupTransport.TRANSPORT_OK) {
backupPackageStatus = finishResult;
}
} else {
transport.cancelFullBackup();
}
}
}
// A transport-originated error here means that we've hit an error that the
// runner doesn't know about, so it's still moving data but we're pulling the
// rug out from under it. Don't ask for its result: we already know better
// and we'll hang if we block waiting for it, since it relies on us to
// read back the data it's writing into the engine. Just proceed with
// a graceful failure. The runner/engine mechanism will tear itself
// down cleanly when we close the pipes from this end. Transport-level
// errors take precedence over agent/app-specific errors for purposes of
// determining our course of action.
if (backupPackageStatus == BackupTransport.TRANSPORT_OK) {
// We still could fail in backup runner thread.
if (backupRunnerResult != BackupTransport.TRANSPORT_OK) {
// If there was an error in runner thread and
// not TRANSPORT_ERROR here, overwrite it.
backupPackageStatus = backupRunnerResult;
}
} else {
if (MORE_DEBUG) {
Slog.i(TAG, "Transport-level failure; cancelling agent work");
}
}
if (MORE_DEBUG) {
Slog.i(TAG, "Done delivering backup data: result="
+ backupPackageStatus);
}
if (backupPackageStatus != BackupTransport.TRANSPORT_OK) {
Slog.e(TAG, "Error " + backupPackageStatus + " backing up "
+ packageName);
}
// Also ask the transport how long it wants us to wait before
// moving on to the next package, if any.
backoff = transport.requestFullBackupTime();
if (DEBUG_SCHEDULING) {
Slog.i(TAG, "Transport suggested backoff=" + backoff);
}
}
// Roll this package to the end of the backup queue if we're
// in a queue-driven mode (regardless of success/failure)
if (mUpdateSchedule) {
backupManagerService.enqueueFullBackup(packageName, System.currentTimeMillis());
}
if (backupPackageStatus == BackupTransport.TRANSPORT_PACKAGE_REJECTED) {
BackupObserverUtils
.sendBackupOnPackageResult(mBackupObserver, packageName,
BackupManager.ERROR_TRANSPORT_PACKAGE_REJECTED);
if (DEBUG) {
Slog.i(TAG, "Transport rejected backup of " + packageName
+ ", skipping");
}
EventLog.writeEvent(EventLogTags.FULL_BACKUP_AGENT_FAILURE, packageName,
"transport rejected");
// This failure state can come either a-priori from the transport, or
// from the preflight pass. If we got as far as preflight, we now need
// to tear down the target process.
if (mBackupRunner != null) {
backupManagerService.tearDownAgentAndKill(currentPackage.applicationInfo);
}
// ... and continue looping.
} else if (backupPackageStatus == BackupTransport.TRANSPORT_QUOTA_EXCEEDED) {
BackupObserverUtils
.sendBackupOnPackageResult(mBackupObserver, packageName,
BackupManager.ERROR_TRANSPORT_QUOTA_EXCEEDED);
if (DEBUG) {
Slog.i(TAG, "Transport quota exceeded for package: " + packageName);
EventLog.writeEvent(EventLogTags.FULL_BACKUP_QUOTA_EXCEEDED,
packageName);
}
backupManagerService.tearDownAgentAndKill(currentPackage.applicationInfo);
// Do nothing, clean up, and continue looping.
} else if (backupPackageStatus == BackupTransport.AGENT_ERROR) {
BackupObserverUtils
.sendBackupOnPackageResult(mBackupObserver, packageName,
BackupManager.ERROR_AGENT_FAILURE);
Slog.w(TAG, "Application failure for package: " + packageName);
EventLog.writeEvent(EventLogTags.BACKUP_AGENT_FAILURE, packageName);
backupManagerService.tearDownAgentAndKill(currentPackage.applicationInfo);
// Do nothing, clean up, and continue looping.
} else if (backupPackageStatus == BackupManager.ERROR_BACKUP_CANCELLED) {
BackupObserverUtils
.sendBackupOnPackageResult(mBackupObserver, packageName,
BackupManager.ERROR_BACKUP_CANCELLED);
Slog.w(TAG, "Backup cancelled. package=" + packageName +
", cancelAll=" + mCancelAll);
EventLog.writeEvent(EventLogTags.FULL_BACKUP_CANCELLED, packageName);
backupManagerService.tearDownAgentAndKill(currentPackage.applicationInfo);
// Do nothing, clean up, and continue looping.
} else if (backupPackageStatus != BackupTransport.TRANSPORT_OK) {
BackupObserverUtils
.sendBackupOnPackageResult(mBackupObserver, packageName,
BackupManager.ERROR_TRANSPORT_ABORTED);
Slog.w(TAG, "Transport failed; aborting backup: " + backupPackageStatus);
EventLog.writeEvent(EventLogTags.FULL_BACKUP_TRANSPORT_FAILURE);
// Abort entire backup pass.
backupRunStatus = BackupManager.ERROR_TRANSPORT_ABORTED;
backupManagerService.tearDownAgentAndKill(currentPackage.applicationInfo);
return;
} else {
// Success!
BackupObserverUtils
.sendBackupOnPackageResult(mBackupObserver, packageName,
BackupManager.SUCCESS);
EventLog.writeEvent(EventLogTags.FULL_BACKUP_SUCCESS, packageName);
backupManagerService.logBackupComplete(packageName);
}
cleanUpPipes(transportPipes);
cleanUpPipes(enginePipes);
if (currentPackage.applicationInfo != null) {
Slog.i(TAG, "Unbinding agent in " + packageName);
try {
backupManagerService.getActivityManager().unbindBackupAgent(
currentPackage.applicationInfo);
} catch (RemoteException e) { /* can't happen; activity manager is local */ }
}
}
} catch (Exception e) {
backupRunStatus = BackupManager.ERROR_TRANSPORT_ABORTED;
Slog.w(TAG, "Exception trying full transport backup", e);
mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
BackupManagerMonitor.LOG_EVENT_ID_EXCEPTION_FULL_BACKUP,
mCurrentPackage,
BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
BackupManagerMonitorUtils.putMonitoringExtra(null,
BackupManagerMonitor.EXTRA_LOG_EXCEPTION_FULL_BACKUP,
Log.getStackTraceString(e)));
} finally {
if (mCancelAll) {
backupRunStatus = BackupManager.ERROR_BACKUP_CANCELLED;
}
if (DEBUG) {
Slog.i(TAG, "Full backup completed with status: " + backupRunStatus);
}
BackupObserverUtils.sendBackupFinished(mBackupObserver, backupRunStatus);
cleanUpPipes(transportPipes);
cleanUpPipes(enginePipes);
unregisterTask();
if (mJob != null) {
mJob.finishBackupPass(mUserId);
}
synchronized (backupManagerService.getQueueLock()) {
backupManagerService.setRunningFullBackupTask(null);
}
mListener.onFinished("PFTBT.run()");
mLatch.countDown();
// Now that we're actually done with schedule-driven work, reschedule
// the next pass based on the new queue state.
if (mUpdateSchedule) {
backupManagerService.scheduleNextFullBackupJob(backoff);
}
Slog.i(TAG, "Full data backup pass finished.");
backupManagerService.getWakelock().release();
}
}
void cleanUpPipes(ParcelFileDescriptor[] pipes) {
if (pipes != null) {
if (pipes[0] != null) {
ParcelFileDescriptor fd = pipes[0];
pipes[0] = null;
try {
fd.close();
} catch (IOException e) {
Slog.w(TAG, "Unable to close pipe!");
}
}
if (pipes[1] != null) {
ParcelFileDescriptor fd = pipes[1];
pipes[1] = null;
try {
fd.close();
} catch (IOException e) {
Slog.w(TAG, "Unable to close pipe!");
}
}
}
}
// Run the backup and pipe it back to the given socket -- expects to run on
// a standalone thread. The runner owns this half of the pipe, and closes
// it to indicate EOD to the other end.
class SinglePackageBackupPreflight implements BackupRestoreTask, FullBackupPreflight {
final AtomicLong mResult = new AtomicLong(BackupTransport.AGENT_ERROR);
final CountDownLatch mLatch = new CountDownLatch(1);
final TransportClient mTransportClient;
final long mQuota;
private final int mCurrentOpToken;
private final int mTransportFlags;
SinglePackageBackupPreflight(
TransportClient transportClient,
long quota,
int currentOpToken,
int transportFlags) {
mTransportClient = transportClient;
mQuota = quota;
mCurrentOpToken = currentOpToken;
mTransportFlags = transportFlags;
}
@Override
public int preflightFullBackup(PackageInfo pkg, IBackupAgent agent) {
int result;
long fullBackupAgentTimeoutMillis =
mAgentTimeoutParameters.getFullBackupAgentTimeoutMillis();
try {
backupManagerService.prepareOperationTimeout(
mCurrentOpToken, fullBackupAgentTimeoutMillis, this, OP_TYPE_BACKUP_WAIT);
if (MORE_DEBUG) {
Slog.d(TAG, "Preflighting full payload of " + pkg.packageName);
}
agent.doMeasureFullBackup(mQuota, mCurrentOpToken,
backupManagerService.getBackupManagerBinder(), mTransportFlags);
// Now wait to get our result back. If this backstop timeout is reached without
// the latch being thrown, flow will continue as though a result or "normal"
// timeout had been produced. In case of a real backstop timeout, mResult
// will still contain the value it was constructed with, AGENT_ERROR, which
// intentionaly falls into the "just report failure" code.
mLatch.await(fullBackupAgentTimeoutMillis, TimeUnit.MILLISECONDS);
long totalSize = mResult.get();
// If preflight timed out, mResult will contain error code as int.
if (totalSize < 0) {
return (int) totalSize;
}
if (MORE_DEBUG) {
Slog.v(TAG, "Got preflight response; size=" + totalSize);
}
IBackupTransport transport =
mTransportClient.connectOrThrow("PFTBT$SPBP.preflightFullBackup()");
result = transport.checkFullBackupSize(totalSize);
if (result == BackupTransport.TRANSPORT_QUOTA_EXCEEDED) {
if (MORE_DEBUG) {
Slog.d(TAG, "Package hit quota limit on preflight " +
pkg.packageName + ": " + totalSize + " of " + mQuota);
}
RemoteCall.execute(
callback -> agent.doQuotaExceeded(totalSize, mQuota, callback),
mAgentTimeoutParameters.getQuotaExceededTimeoutMillis());
}
} catch (Exception e) {
Slog.w(TAG, "Exception preflighting " + pkg.packageName + ": " + e.getMessage());
result = BackupTransport.AGENT_ERROR;
}
return result;
}
@Override
public void execute() {
// Unused.
}
@Override
public void operationComplete(long result) {
// got the callback, and our preflightFullBackup() method is waiting for the result
if (MORE_DEBUG) {
Slog.i(TAG, "Preflight op complete, result=" + result);
}
mResult.set(result);
mLatch.countDown();
backupManagerService.removeOperation(mCurrentOpToken);
}
@Override
public void handleCancel(boolean cancelAll) {
if (MORE_DEBUG) {
Slog.i(TAG, "Preflight cancelled; failing");
}
mResult.set(BackupTransport.AGENT_ERROR);
mLatch.countDown();
backupManagerService.removeOperation(mCurrentOpToken);
}
@Override
public long getExpectedSizeOrErrorCode() {
long fullBackupAgentTimeoutMillis =
mAgentTimeoutParameters.getFullBackupAgentTimeoutMillis();
try {
mLatch.await(fullBackupAgentTimeoutMillis, TimeUnit.MILLISECONDS);
return mResult.get();
} catch (InterruptedException e) {
return BackupTransport.NO_MORE_DATA;
}
}
}
class SinglePackageBackupRunner implements Runnable, BackupRestoreTask {
final ParcelFileDescriptor mOutput;
final PackageInfo mTarget;
final SinglePackageBackupPreflight mPreflight;
final CountDownLatch mPreflightLatch;
final CountDownLatch mBackupLatch;
private final int mCurrentOpToken;
private final int mEphemeralToken;
private FullBackupEngine mEngine;
private volatile int mPreflightResult;
private volatile int mBackupResult;
private final long mQuota;
private volatile boolean mIsCancelled;
private final int mTransportFlags;
SinglePackageBackupRunner(ParcelFileDescriptor output, PackageInfo target,
TransportClient transportClient, long quota, int currentOpToken, int transportFlags)
throws IOException {
mOutput = ParcelFileDescriptor.dup(output.getFileDescriptor());
mTarget = target;
mCurrentOpToken = currentOpToken;
mEphemeralToken = backupManagerService.generateRandomIntegerToken();
mPreflight = new SinglePackageBackupPreflight(
transportClient, quota, mEphemeralToken, transportFlags);
mPreflightLatch = new CountDownLatch(1);
mBackupLatch = new CountDownLatch(1);
mPreflightResult = BackupTransport.AGENT_ERROR;
mBackupResult = BackupTransport.AGENT_ERROR;
mQuota = quota;
mTransportFlags = transportFlags;
registerTask();
}
void registerTask() {
synchronized (backupManagerService.getCurrentOpLock()) {
backupManagerService.getCurrentOperations().put(
mCurrentOpToken, new Operation(OP_PENDING, this, OP_TYPE_BACKUP_WAIT));
}
}
void unregisterTask() {
synchronized (backupManagerService.getCurrentOpLock()) {
backupManagerService.getCurrentOperations().remove(mCurrentOpToken);
}
}
@Override
public void run() {
FileOutputStream out = new FileOutputStream(mOutput.getFileDescriptor());
mEngine = new FullBackupEngine(backupManagerService, out, mPreflight, mTarget, false,
this, mQuota, mCurrentOpToken, mTransportFlags);
try {
try {
if (!mIsCancelled) {
mPreflightResult = mEngine.preflightCheck();
}
} finally {
mPreflightLatch.countDown();
}
// If there is no error on preflight, continue backup.
if (mPreflightResult == BackupTransport.TRANSPORT_OK) {
if (!mIsCancelled) {
mBackupResult = mEngine.backupOnePackage();
}
}
} catch (Exception e) {
Slog.e(TAG, "Exception during full package backup of " + mTarget.packageName);
} finally {
unregisterTask();
mBackupLatch.countDown();
try {
mOutput.close();
} catch (IOException e) {
Slog.w(TAG, "Error closing transport pipe in runner");
}
}
}
public void sendQuotaExceeded(final long backupDataBytes, final long quotaBytes) {
mEngine.sendQuotaExceeded(backupDataBytes, quotaBytes);
}
// If preflight succeeded, returns positive number - preflight size,
// otherwise return negative error code.
long getPreflightResultBlocking() {
long fullBackupAgentTimeoutMillis =
mAgentTimeoutParameters.getFullBackupAgentTimeoutMillis();
try {
mPreflightLatch.await(fullBackupAgentTimeoutMillis, TimeUnit.MILLISECONDS);
if (mIsCancelled) {
return BackupManager.ERROR_BACKUP_CANCELLED;
}
if (mPreflightResult == BackupTransport.TRANSPORT_OK) {
return mPreflight.getExpectedSizeOrErrorCode();
} else {
return mPreflightResult;
}
} catch (InterruptedException e) {
return BackupTransport.AGENT_ERROR;
}
}
int getBackupResultBlocking() {
long fullBackupAgentTimeoutMillis =
mAgentTimeoutParameters.getFullBackupAgentTimeoutMillis();
try {
mBackupLatch.await(fullBackupAgentTimeoutMillis, TimeUnit.MILLISECONDS);
if (mIsCancelled) {
return BackupManager.ERROR_BACKUP_CANCELLED;
}
return mBackupResult;
} catch (InterruptedException e) {
return BackupTransport.AGENT_ERROR;
}
}
// BackupRestoreTask interface: specifically, timeout detection
@Override
public void execute() { /* intentionally empty */ }
@Override
public void operationComplete(long result) { /* intentionally empty */ }
@Override
public void handleCancel(boolean cancelAll) {
if (DEBUG) {
Slog.w(TAG, "Full backup cancel of " + mTarget.packageName);
}
mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
BackupManagerMonitor.LOG_EVENT_ID_FULL_BACKUP_CANCEL,
mCurrentPackage, BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT, null);
mIsCancelled = true;
// Cancel tasks spun off by this task.
backupManagerService.handleCancel(mEphemeralToken, cancelAll);
backupManagerService.tearDownAgentAndKill(mTarget.applicationInfo);
// Free up everyone waiting on this task and its children.
mPreflightLatch.countDown();
mBackupLatch.countDown();
// We are done with this operation.
backupManagerService.removeOperation(mCurrentOpToken);
}
}
}