blob: cd137603b350700afc9592f3c351a3ef7fe285ef [file] [log] [blame]
package com.android.server.backup;
import static android.os.ParcelFileDescriptor.MODE_CREATE;
import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
import static android.os.ParcelFileDescriptor.MODE_TRUNCATE;
import static com.android.server.backup.BackupManagerService.OP_TYPE_BACKUP_WAIT;
import static com.android.server.backup.BackupManagerService.TIMEOUT_BACKUP_INTERVAL;
import android.app.ApplicationThreadConstants;
import android.app.IBackupAgent;
import android.app.backup.FullBackup;
import android.app.backup.FullBackupDataOutput;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.SELinux;
import android.util.Slog;
import libcore.io.IoUtils;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
/**
* Used by BackupManagerService to perform adb backup for key-value packages. At the moment this
* class resembles what is done in the standard key-value code paths in BackupManagerService, and
* should be unified later.
*
* TODO: We should create unified backup/restore engines that can be used for both transport and
* adb backup/restore, and for fullbackup and key-value backup.
*/
class KeyValueAdbBackupEngine {
private static final String TAG = "KeyValueAdbBackupEngine";
private static final boolean DEBUG = false;
private static final String BACKUP_KEY_VALUE_DIRECTORY_NAME = "key_value_dir";
private static final String BACKUP_KEY_VALUE_BLANK_STATE_FILENAME = "blank_state";
private static final String BACKUP_KEY_VALUE_BACKUP_DATA_FILENAME_SUFFIX = ".data";
private static final String BACKUP_KEY_VALUE_NEW_STATE_FILENAME_SUFFIX = ".new";
private BackupManagerService mBackupManagerService;
private final PackageManager mPackageManager;
private final OutputStream mOutput;
private final PackageInfo mCurrentPackage;
private final File mDataDir;
private final File mStateDir;
private final File mBlankStateName;
private final File mBackupDataName;
private final File mNewStateName;
private final File mManifestFile;
private ParcelFileDescriptor mSavedState;
private ParcelFileDescriptor mBackupData;
private ParcelFileDescriptor mNewState;
KeyValueAdbBackupEngine(OutputStream output, PackageInfo packageInfo,
BackupManagerService backupManagerService, PackageManager packageManager,
File baseStateDir, File dataDir) {
mOutput = output;
mCurrentPackage = packageInfo;
mBackupManagerService = backupManagerService;
mPackageManager = packageManager;
mDataDir = dataDir;
mStateDir = new File(baseStateDir, BACKUP_KEY_VALUE_DIRECTORY_NAME);
mStateDir.mkdirs();
String pkg = mCurrentPackage.packageName;
mBlankStateName = new File(mStateDir, BACKUP_KEY_VALUE_BLANK_STATE_FILENAME);
mBackupDataName = new File(mDataDir,
pkg + BACKUP_KEY_VALUE_BACKUP_DATA_FILENAME_SUFFIX);
mNewStateName = new File(mStateDir,
pkg + BACKUP_KEY_VALUE_NEW_STATE_FILENAME_SUFFIX);
mManifestFile = new File(mDataDir, BackupManagerService.BACKUP_MANIFEST_FILENAME);
}
void backupOnePackage() throws IOException {
ApplicationInfo targetApp = mCurrentPackage.applicationInfo;
try {
prepareBackupFiles(mCurrentPackage.packageName);
IBackupAgent agent = bindToAgent(targetApp);
if (agent == null) {
// We failed binding to the agent, so ignore this package
Slog.e(TAG, "Failed binding to BackupAgent for package "
+ mCurrentPackage.packageName);
return;
}
// We are bound to agent, initiate backup.
if (!invokeAgentForAdbBackup(mCurrentPackage.packageName, agent)) {
// Backup failed, skip package.
Slog.e(TAG, "Backup Failed for package " + mCurrentPackage.packageName);
return;
}
// Backup finished successfully. Copy the backup data to the output stream.
writeBackupData();
} catch (FileNotFoundException e) {
Slog.e(TAG, "Failed creating files for package " + mCurrentPackage.packageName
+ " will ignore package. " + e);
} finally {
// We are either done, failed or have timed out, so do cleanup and kill the agent.
cleanup();
}
}
private void prepareBackupFiles(String packageName) throws FileNotFoundException {
// We pass a blank state to make sure we are getting the complete backup, not just an
// increment
mSavedState = ParcelFileDescriptor.open(mBlankStateName,
MODE_READ_ONLY | MODE_CREATE); // Make an empty file if necessary
mBackupData = ParcelFileDescriptor.open(mBackupDataName,
MODE_READ_WRITE | MODE_CREATE | MODE_TRUNCATE);
if (!SELinux.restorecon(mBackupDataName)) {
Slog.e(TAG, "SELinux restorecon failed on " + mBackupDataName);
}
mNewState = ParcelFileDescriptor.open(mNewStateName,
MODE_READ_WRITE | MODE_CREATE | MODE_TRUNCATE);
}
private IBackupAgent bindToAgent(ApplicationInfo targetApp) {
try {
return mBackupManagerService.bindToAgentSynchronous(targetApp,
ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL);
} catch (SecurityException e) {
Slog.e(TAG, "error in binding to agent for package " + targetApp.packageName
+ ". " + e);
return null;
}
}
// Return true on backup success, false otherwise
private boolean invokeAgentForAdbBackup(String packageName, IBackupAgent agent) {
int token = mBackupManagerService.generateToken();
try {
mBackupManagerService.prepareOperationTimeout(token, TIMEOUT_BACKUP_INTERVAL, null,
OP_TYPE_BACKUP_WAIT);
// Start backup and wait for BackupManagerService to get callback for success or timeout
agent.doBackup(mSavedState, mBackupData, mNewState, Long.MAX_VALUE, token,
mBackupManagerService.mBackupManagerBinder);
if (!mBackupManagerService.waitUntilOperationComplete(token)) {
Slog.e(TAG, "Key-value backup failed on package " + packageName);
return false;
}
if (DEBUG) {
Slog.i(TAG, "Key-value backup success for package " + packageName);
}
return true;
} catch (RemoteException e) {
Slog.e(TAG, "Error invoking agent for backup on " + packageName + ". " + e);
return false;
}
}
class KeyValueAdbBackupDataCopier implements Runnable {
private final PackageInfo mPackage;
private final ParcelFileDescriptor mPipe;
private final int mToken;
KeyValueAdbBackupDataCopier(PackageInfo pack, ParcelFileDescriptor pipe,
int token)
throws IOException {
mPackage = pack;
mPipe = ParcelFileDescriptor.dup(pipe.getFileDescriptor());
mToken = token;
}
@Override
public void run() {
try {
FullBackupDataOutput output = new FullBackupDataOutput(mPipe);
if (DEBUG) {
Slog.d(TAG, "Writing manifest for " + mPackage.packageName);
}
BackupManagerService.writeAppManifest(
mPackage, mPackageManager, mManifestFile, false, false);
FullBackup.backupToTar(mPackage.packageName, FullBackup.KEY_VALUE_DATA_TOKEN, null,
mDataDir.getAbsolutePath(),
mManifestFile.getAbsolutePath(),
output);
mManifestFile.delete();
if (DEBUG) {
Slog.d(TAG, "Writing key-value package payload" + mPackage.packageName);
}
FullBackup.backupToTar(mPackage.packageName, FullBackup.KEY_VALUE_DATA_TOKEN, null,
mDataDir.getAbsolutePath(),
mBackupDataName.getAbsolutePath(),
output);
// Write EOD marker
try {
FileOutputStream out = new FileOutputStream(mPipe.getFileDescriptor());
byte[] buf = new byte[4];
out.write(buf);
} catch (IOException e) {
Slog.e(TAG, "Unable to finalize backup stream!");
}
try {
mBackupManagerService.mBackupManagerBinder.opComplete(mToken, 0);
} catch (RemoteException e) {
// we'll time out anyway, so we're safe
}
} catch (IOException e) {
Slog.e(TAG, "Error running full backup for " + mPackage.packageName + ". " + e);
} finally {
IoUtils.closeQuietly(mPipe);
}
}
}
private void writeBackupData() throws IOException {
int token = mBackupManagerService.generateToken();
ParcelFileDescriptor[] pipes = null;
try {
pipes = ParcelFileDescriptor.createPipe();
mBackupManagerService.prepareOperationTimeout(token, TIMEOUT_BACKUP_INTERVAL, null,
OP_TYPE_BACKUP_WAIT);
// We will have to create a runnable that will read the manifest and backup data we
// created, such that we can pipe the data into mOutput. The reason we do this is that
// internally FullBackup.backupToTar is used, which will create the necessary file
// header, but will also chunk the data. The method routeSocketDataToOutput in
// BackupManagerService will dechunk the data, and append it to the TAR outputstream.
KeyValueAdbBackupDataCopier runner = new KeyValueAdbBackupDataCopier(mCurrentPackage, pipes[1],
token);
pipes[1].close(); // the runner has dup'd it
pipes[1] = null;
Thread t = new Thread(runner, "key-value-app-data-runner");
t.start();
// Now pull data from the app and stuff it into the output
BackupManagerService.routeSocketDataToOutput(pipes[0], mOutput);
if (!mBackupManagerService.waitUntilOperationComplete(token)) {
Slog.e(TAG, "Full backup failed on package " + mCurrentPackage.packageName);
} else {
if (DEBUG) {
Slog.d(TAG, "Full package backup success: " + mCurrentPackage.packageName);
}
}
} catch (IOException e) {
Slog.e(TAG, "Error backing up " + mCurrentPackage.packageName + ": " + e);
} finally {
// flush after every package
mOutput.flush();
if (pipes != null) {
IoUtils.closeQuietly(pipes[0]);
IoUtils.closeQuietly(pipes[1]);
}
}
}
private void cleanup() {
mBackupManagerService.tearDownAgentAndKill(mCurrentPackage.applicationInfo);
mBlankStateName.delete();
mNewStateName.delete();
mBackupDataName.delete();
}
}