| 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. |
| */ |
| 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, BackupManagerService.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); |
| } |
| 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.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 |
| 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(); |
| } |
| } |