| 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.RefactoredBackupManagerService.OP_TYPE_BACKUP_WAIT; | |
| import static com.android.server.backup.RefactoredBackupManagerService.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 com.android.server.backup.utils.FullBackupUtils; | |
| 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. | |
| */ | |
| public 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 BackupManagerServiceInterface 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; | |
| public KeyValueAdbBackupEngine(OutputStream output, PackageInfo packageInfo, | |
| BackupManagerServiceInterface 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, RefactoredBackupManagerService.BACKUP_MANIFEST_FILENAME); | |
| } | |
| public 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.generateRandomIntegerToken(); | |
| 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.getBackupManagerBinder()); | |
| 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); | |
| } | |
| FullBackupUtils.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.getBackupManagerBinder().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.generateRandomIntegerToken(); | |
| 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 | |
| FullBackupUtils.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(); | |
| } | |
| } |