diff options
author | Christopher Tate <ctate@google.com> | 2014-06-03 17:20:07 -0700 |
---|---|---|
committer | Christopher Tate <ctate@google.com> | 2014-06-15 17:35:33 -0700 |
commit | 9ff53a7100b1a40f5d2df3eb19a2f3f2fff39a46 (patch) | |
tree | 162c23a51e116f9506e7d6801236dd2e2c8f3788 | |
parent | ad60891a6ecaf2a5815677b33e96afe7f49ee113 (diff) | |
download | frameworks_base-9ff53a7100b1a40f5d2df3eb19a2f3f2fff39a46.zip frameworks_base-9ff53a7100b1a40f5d2df3eb19a2f3f2fff39a46.tar.gz frameworks_base-9ff53a7100b1a40f5d2df3eb19a2f3f2fff39a46.tar.bz2 |
Implement full data backup through transport
Currently no timed/scheduled full-data backup operations are
performed by the OS, but the plumbing is now in place and can
be tested using 'adb shell bmgr fullbackup pkg [pkg2 pkg3 ...]'.
The LocalTransport test transport implementation has been augmented
to support the new full-data backup API as well.
In addition, 'adb backup' now takes the -compress/-nocompress
command line options to control whether the resulting archive is
compressed before leaving the device. Previously the archive was
always compressed. (The default is still to compress, as it will
usually reduce the archive size considerably.)
Internally, the core implementation of gathering the full backup
data stream from the target application has been refactored into
an "engine" component that is shared by both 'adb backup' and the
transport-oriented full backup task. The archive file header
generation, encryption, and compression logic are now factored out
of the engine itself instead of being hardwired into the data
handling.
Bug 15329632
Change-Id: I4a044faa4070d684ef457bd3e11771198cdf557c
7 files changed, 883 insertions, 336 deletions
diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java index db3d8bb..d1bea2e 100644 --- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java +++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java @@ -23,6 +23,7 @@ import android.app.backup.IRestoreSession; import android.os.RemoteException; import android.os.ServiceManager; +import java.util.ArrayList; import java.util.HashSet; public final class Bmgr { @@ -102,6 +103,11 @@ public final class Bmgr { return; } + if ("fullbackup".equals(op)) { + doFullTransportBackup(); + return; + } + System.err.println("Unknown command"); showUsage(); } @@ -165,6 +171,21 @@ public final class Bmgr { } } + private void doFullTransportBackup() { + System.out.println("Performing full transport backup"); + + String pkg; + while ((pkg = nextArg()) != null) { + System.out.println(" Package " + pkg + " ..."); + try { + mBmgr.fullTransportBackup(new String[] { pkg }); + } catch (RemoteException e) { + System.err.println(e.toString()); + System.err.println(BMGR_NOT_RUNNING_ERR); + } + } + } + private void doTransport() { try { String which = nextArg(); diff --git a/cmds/bu/src/com/android/commands/bu/Backup.java b/cmds/bu/src/com/android/commands/bu/Backup.java index 4503726..ffc0f87 100644 --- a/cmds/bu/src/com/android/commands/bu/Backup.java +++ b/cmds/bu/src/com/android/commands/bu/Backup.java @@ -69,6 +69,7 @@ public final class Backup { boolean doEverything = false; boolean doWidgets = false; boolean allIncludesSystem = true; + boolean doCompress = true; String arg; while ((arg = nextArg()) != null) { @@ -95,6 +96,10 @@ public final class Backup { doWidgets = false; } else if ("-all".equals(arg)) { doEverything = true; + } else if ("-compress".equals(arg)) { + doCompress = true; + } else if ("-nocompress".equals(arg)) { + doCompress = false; } else { Log.w(TAG, "Unknown backup flag " + arg); continue; @@ -119,7 +124,7 @@ public final class Backup { fd = ParcelFileDescriptor.adoptFd(socketFd); String[] packArray = new String[packages.size()]; mBackupManager.fullBackup(fd, saveApks, saveObbs, saveShared, doWidgets, - doEverything, allIncludesSystem, packages.toArray(packArray)); + doEverything, allIncludesSystem, doCompress, packages.toArray(packArray)); } catch (RemoteException e) { Log.e(TAG, "Unable to invoke backup manager for backup"); } finally { diff --git a/core/java/android/app/backup/BackupTransport.java b/core/java/android/app/backup/BackupTransport.java index 46f082e..56a55fb 100644 --- a/core/java/android/app/backup/BackupTransport.java +++ b/core/java/android/app/backup/BackupTransport.java @@ -328,6 +328,58 @@ public class BackupTransport { return BackupTransport.TRANSPORT_ERROR; } + // ------------------------------------------------------------------------------------ + // Full restore interfaces + + /** + * Ask the transport to set up to perform a full data restore of the given packages. + * + * @param token A backup token as returned by {@link #getAvailableRestoreSets} + * or {@link #getCurrentRestoreSet}. + * @param targetPackage The names of the packages whose data is being requested. + * @return TRANSPORT_OK to indicate that the OS may proceed with requesting + * restore data; TRANSPORT_ERROR to indicate a fatal error condition that precludes + * performing any restore at this time. + */ + public int prepareFullRestore(long token, String[] targetPackages) { + return BackupTransport.TRANSPORT_OK; + } + + /** + * Ask the transport what package's full data will be restored next. When all apps' + * data has been delivered, the transport should return {@code null} here. + * @return The package name of the next application whose data will be restored, or + * {@code null} if all available package has been delivered. + */ + public String getNextFullRestorePackage() { + return null; + } + + /** + * Ask the transport to provide data for the "current" package being restored. The + * transport then writes some data to the socket supplied to this call, and returns + * the number of bytes written. The system will then read that many bytes and + * stream them to the application's agent for restore, then will call this method again + * to receive the next chunk of the archive. This sequence will be repeated until the + * transport returns zero indicating that all of the package's data has been delivered + * (or returns a negative value indicating some sort of hard error condition at the + * transport level). + * + * <p>After this method returns zero, the system will then call + * {@link #getNextFullRestorePackage()} to begin the restore process for the next + * application, and the sequence begins again. + * + * @param socket The file descriptor that the transport will use for delivering the + * streamed archive. + * @return 0 when no more data for the current package is available. A positive value + * indicates the presence of that much data to be delivered to the app. A negative + * return value is treated as equivalent to {@link BackupTransport#TRANSPORT_ERROR}, + * indicating a fatal error condition that precludes further restore operations + * on the current dataset. + */ + public int getNextFullRestoreDataChunk(ParcelFileDescriptor socket) { + return 0; + } /** * Bridge between the actual IBackupTransport implementation and the stable API. If the * binder interface needs to change, we use this layer to translate so that we can @@ -411,5 +463,20 @@ public class BackupTransport { public void finishRestore() throws RemoteException { BackupTransport.this.finishRestore(); } + + @Override + public long requestFullBackupTime() throws RemoteException { + return BackupTransport.this.requestFullBackupTime(); + } + + @Override + public int performFullBackup(PackageInfo targetPackage, ParcelFileDescriptor socket) throws RemoteException { + return BackupTransport.this.performFullBackup(targetPackage, socket); + } + + @Override + public int sendBackupData(int numBytes) throws RemoteException { + return BackupTransport.this.sendBackupData(numBytes); + } } } diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl index c629a2e..72bc4f0 100644 --- a/core/java/android/app/backup/IBackupManager.aidl +++ b/core/java/android/app/backup/IBackupManager.aidl @@ -168,7 +168,15 @@ interface IBackupManager { */ void fullBackup(in ParcelFileDescriptor fd, boolean includeApks, boolean includeObbs, boolean includeShared, boolean doWidgets, boolean allApps, boolean allIncludesSystem, - in String[] packageNames); + boolean doCompress, in String[] packageNames); + + /** + * Perform a full-dataset backup of the given applications via the currently active + * transport. + * + * @param packageNames The package names of the apps whose data are to be backed up. + */ + void fullTransportBackup(in String[] packageNames); /** * Restore device content from the data stream passed through the given socket. The diff --git a/core/java/com/android/internal/backup/IBackupTransport.aidl b/core/java/com/android/internal/backup/IBackupTransport.aidl index d10451b..960fa49 100644 --- a/core/java/com/android/internal/backup/IBackupTransport.aidl +++ b/core/java/com/android/internal/backup/IBackupTransport.aidl @@ -187,4 +187,8 @@ interface IBackupTransport { * freeing any resources and connections used during the restore process. */ void finishRestore(); + + long requestFullBackupTime(); + int performFullBackup(in PackageInfo targetPackage, in ParcelFileDescriptor socket); + int sendBackupData(int numBytes); } diff --git a/core/java/com/android/internal/backup/LocalTransport.java b/core/java/com/android/internal/backup/LocalTransport.java index 7292116..c9d621d 100644 --- a/core/java/com/android/internal/backup/LocalTransport.java +++ b/core/java/com/android/internal/backup/LocalTransport.java @@ -34,12 +34,17 @@ import android.util.Log; import com.android.org.bouncycastle.util.encoders.Base64; +import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; +import java.util.List; import static android.system.OsConstants.*; @@ -64,16 +69,29 @@ public class LocalTransport extends BackupTransport { private Context mContext; private File mDataDir = new File(Environment.getDownloadCacheDirectory(), "backup"); private File mCurrentSetDir = new File(mDataDir, Long.toString(CURRENT_SET_TOKEN)); + private File mCurrentSetIncrementalDir = new File(mCurrentSetDir, "_delta"); + private File mCurrentSetFullDir = new File(mCurrentSetDir, "_full"); private PackageInfo[] mRestorePackages = null; private int mRestorePackage = -1; // Index into mRestorePackages private File mRestoreDataDir; private long mRestoreToken; + // Additional bookkeeping for full backup + private String mFullTargetPackage; + private ParcelFileDescriptor mSocket; + private FileInputStream mSocketInputStream; + private BufferedOutputStream mFullBackupOutputStream; + private byte[] mFullBackupBuffer; + + private File mFullRestoreSetDir; + private HashSet<String> mFullRestorePackages; public LocalTransport(Context context) { mContext = context; mCurrentSetDir.mkdirs(); + mCurrentSetFullDir.mkdir(); + mCurrentSetIncrementalDir.mkdir(); if (!SELinux.restorecon(mCurrentSetDir)) { Log.e(TAG, "SELinux restorecon failed for " + mCurrentSetDir); } @@ -119,7 +137,7 @@ public class LocalTransport extends BackupTransport { } } - File packageDir = new File(mCurrentSetDir, packageInfo.packageName); + File packageDir = new File(mCurrentSetIncrementalDir, packageInfo.packageName); packageDir.mkdirs(); // Each 'record' in the restore set is kept in its own file, named by @@ -200,7 +218,7 @@ public class LocalTransport extends BackupTransport { public int clearBackupData(PackageInfo packageInfo) { if (DEBUG) Log.v(TAG, "clearBackupData() pkg=" + packageInfo.packageName); - File packageDir = new File(mCurrentSetDir, packageInfo.packageName); + File packageDir = new File(mCurrentSetIncrementalDir, packageInfo.packageName); final File[] fileset = packageDir.listFiles(); if (fileset != null) { for (File f : fileset) { @@ -208,27 +226,122 @@ public class LocalTransport extends BackupTransport { } packageDir.delete(); } + + packageDir = new File(mCurrentSetFullDir, packageInfo.packageName); + final File[] tarballs = packageDir.listFiles(); + if (tarballs != null) { + for (File f : tarballs) { + f.delete(); + } + packageDir.delete(); + } + return BackupTransport.TRANSPORT_OK; } public int finishBackup() { if (DEBUG) Log.v(TAG, "finishBackup()"); + if (mSocket != null) { + if (DEBUG) { + Log.v(TAG, "Concluding full backup of " + mFullTargetPackage); + } + try { + mFullBackupOutputStream.flush(); + mFullBackupOutputStream.close(); + mSocketInputStream = null; + mFullTargetPackage = null; + mSocket.close(); + } catch (IOException e) { + return BackupTransport.TRANSPORT_ERROR; + } finally { + mSocket = null; + } + } return BackupTransport.TRANSPORT_OK; } + // ------------------------------------------------------------------------------------ + // Full backup handling + public long requestFullBackupTime() { + return 0; + } + + public int performFullBackup(PackageInfo targetPackage, ParcelFileDescriptor socket) { + if (mSocket != null) { + Log.e(TAG, "Attempt to initiate full backup while one is in progress"); + return BackupTransport.TRANSPORT_ERROR; + } + + if (DEBUG) { + Log.i(TAG, "performFullBackup : " + targetPackage); + } + + // We know a priori that we run in the system process, so we need to make + // sure to dup() our own copy of the socket fd. Transports which run in + // their own processes must not do this. + try { + mSocket = ParcelFileDescriptor.dup(socket.getFileDescriptor()); + mSocketInputStream = new FileInputStream(mSocket.getFileDescriptor()); + } catch (IOException e) { + Log.e(TAG, "Unable to process socket for full backup"); + return BackupTransport.TRANSPORT_ERROR; + } + + mFullTargetPackage = targetPackage.packageName; + FileOutputStream tarstream; + try { + File tarball = new File(mCurrentSetFullDir, mFullTargetPackage); + tarstream = new FileOutputStream(tarball); + } catch (FileNotFoundException e) { + return BackupTransport.TRANSPORT_ERROR; + } + mFullBackupOutputStream = new BufferedOutputStream(tarstream); + mFullBackupBuffer = new byte[4096]; + + return BackupTransport.TRANSPORT_OK; + } + + public int sendBackupData(int numBytes) { + if (mFullBackupBuffer == null) { + Log.w(TAG, "Attempted sendBackupData before performFullBackup"); + return BackupTransport.TRANSPORT_ERROR; + } + + if (numBytes > mFullBackupBuffer.length) { + mFullBackupBuffer = new byte[numBytes]; + } + while (numBytes > 0) { + try { + int nRead = mSocketInputStream.read(mFullBackupBuffer, 0, numBytes); + if (nRead < 0) { + // Something went wrong if we expect data but saw EOD + Log.w(TAG, "Unexpected EOD; failing backup"); + return BackupTransport.TRANSPORT_ERROR; + } + mFullBackupOutputStream.write(mFullBackupBuffer, 0, nRead); + numBytes -= nRead; + } catch (IOException e) { + Log.e(TAG, "Error handling backup data for " + mFullTargetPackage); + return BackupTransport.TRANSPORT_ERROR; + } + } + return BackupTransport.TRANSPORT_OK; + } + + // ------------------------------------------------------------------------------------ // Restore handling static final long[] POSSIBLE_SETS = { 2, 3, 4, 5, 6, 7, 8, 9 }; public RestoreSet[] getAvailableRestoreSets() { long[] existing = new long[POSSIBLE_SETS.length + 1]; int num = 0; - // see which possible non-current sets exist, then put the current set at the end + // see which possible non-current sets exist... for (long token : POSSIBLE_SETS) { if ((new File(mDataDir, Long.toString(token))).exists()) { existing[num++] = token; } } - // and always the currently-active set last + // ...and always the currently-active set last existing[num++] = CURRENT_SET_TOKEN; RestoreSet[] available = new RestoreSet[num]; @@ -345,4 +458,60 @@ public class LocalTransport extends BackupTransport { public void finishRestore() { if (DEBUG) Log.v(TAG, "finishRestore()"); } + + // ------------------------------------------------------------------------------------ + // Full restore handling + + public int prepareFullRestore(long token, String[] targetPackages) { + mRestoreDataDir = new File(mDataDir, Long.toString(token)); + mFullRestoreSetDir = new File(mRestoreDataDir, "_full"); + mFullRestorePackages = new HashSet<String>(); + if (mFullRestoreSetDir.exists()) { + List<String> pkgs = Arrays.asList(mFullRestoreSetDir.list()); + HashSet<String> available = new HashSet<String>(pkgs); + + for (int i = 0; i < targetPackages.length; i++) { + if (available.contains(targetPackages[i])) { + mFullRestorePackages.add(targetPackages[i]); + } + } + } + return BackupTransport.TRANSPORT_OK; + } + + /** + * Ask the transport what package's full data will be restored next. When all apps' + * data has been delivered, the transport should return {@code null} here. + * @return The package name of the next application whose data will be restored, or + * {@code null} if all available package has been delivered. + */ + public String getNextFullRestorePackage() { + return null; + } + + /** + * Ask the transport to provide data for the "current" package being restored. The + * transport then writes some data to the socket supplied to this call, and returns + * the number of bytes written. The system will then read that many bytes and + * stream them to the application's agent for restore, then will call this method again + * to receive the next chunk of the archive. This sequence will be repeated until the + * transport returns zero indicating that all of the package's data has been delivered + * (or returns a negative value indicating some sort of hard error condition at the + * transport level). + * + * <p>After this method returns zero, the system will then call + * {@link #getNextFullRestorePackage()} to begin the restore process for the next + * application, and the sequence begins again. + * + * @param socket The file descriptor that the transport will use for delivering the + * streamed archive. + * @return 0 when no more data for the current package is available. A positive value + * indicates the presence of that much data to be delivered to the app. A negative + * return value is treated as equivalent to {@link BackupTransport#TRANSPORT_ERROR}, + * indicating a fatal error condition that precludes further restore operations + * on the current dataset. + */ + public int getNextFullRestoreDataChunk(ParcelFileDescriptor socket) { + return 0; + } } diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java index 14c15a7..cef6830 100644 --- a/services/backup/java/com/android/server/backup/BackupManagerService.java +++ b/services/backup/java/com/android/server/backup/BackupManagerService.java @@ -201,7 +201,7 @@ public class BackupManagerService extends IBackupManager.Stub { private static final String RUN_INITIALIZE_ACTION = "android.app.backup.intent.INIT"; private static final String RUN_CLEAR_ACTION = "android.app.backup.intent.CLEAR"; private static final int MSG_RUN_BACKUP = 1; - private static final int MSG_RUN_FULL_BACKUP = 2; + private static final int MSG_RUN_ADB_BACKUP = 2; private static final int MSG_RUN_RESTORE = 3; private static final int MSG_RUN_CLEAR = 4; private static final int MSG_RUN_INITIALIZE = 5; @@ -209,10 +209,11 @@ public class BackupManagerService extends IBackupManager.Stub { private static final int MSG_TIMEOUT = 7; private static final int MSG_RESTORE_TIMEOUT = 8; private static final int MSG_FULL_CONFIRMATION_TIMEOUT = 9; - private static final int MSG_RUN_FULL_RESTORE = 10; + private static final int MSG_RUN_ADB_RESTORE = 10; private static final int MSG_RETRY_INIT = 11; private static final int MSG_RETRY_CLEAR = 12; private static final int MSG_WIDGET_BROADCAST = 13; + private static final int MSG_RUN_FULL_TRANSPORT_BACKUP = 14; // backup task state machine tick static final int MSG_BACKUP_RESTORE_STEP = 20; @@ -448,11 +449,12 @@ public class BackupManagerService extends IBackupManager.Stub { public boolean doWidgets; public boolean allApps; public boolean includeSystem; + public boolean doCompress; public String[] packages; FullBackupParams(ParcelFileDescriptor output, boolean saveApks, boolean saveObbs, boolean saveShared, boolean alsoWidgets, boolean doAllApps, boolean doSystem, - String[] pkgList) { + boolean compress, String[] pkgList) { fd = output; includeApks = saveApks; includeObbs = saveObbs; @@ -460,6 +462,7 @@ public class BackupManagerService extends IBackupManager.Stub { doWidgets = alsoWidgets; allApps = doAllApps; includeSystem = doSystem; + doCompress = compress; packages = pkgList; } } @@ -646,17 +649,25 @@ public class BackupManagerService extends IBackupManager.Stub { break; } - case MSG_RUN_FULL_BACKUP: + case MSG_RUN_ADB_BACKUP: { // TODO: refactor full backup to be a looper-based state machine // similar to normal backup/restore. FullBackupParams params = (FullBackupParams)msg.obj; - PerformFullBackupTask task = new PerformFullBackupTask(params.fd, + PerformAdbBackupTask task = new PerformAdbBackupTask(params.fd, params.observer, params.includeApks, params.includeObbs, params.includeShared, params.doWidgets, params.curPassword, params.encryptPassword, - params.allApps, params.includeSystem, params.packages, params.latch); - (new Thread(task)).start(); + params.allApps, params.includeSystem, params.doCompress, + params.packages, params.latch); + (new Thread(task, "adb-backup")).start(); + break; + } + + case MSG_RUN_FULL_TRANSPORT_BACKUP: + { + PerformFullTransportBackupTask task = (PerformFullTransportBackupTask) msg.obj; + (new Thread(task, "transport-backup")).start(); break; } @@ -673,7 +684,7 @@ public class BackupManagerService extends IBackupManager.Stub { break; } - case MSG_RUN_FULL_RESTORE: + case MSG_RUN_ADB_RESTORE: { // TODO: refactor full restore to be a looper-based state machine // similar to normal backup/restore. @@ -681,7 +692,7 @@ public class BackupManagerService extends IBackupManager.Stub { PerformFullRestoreTask task = new PerformFullRestoreTask(params.fd, params.curPassword, params.encryptPassword, params.observer, params.latch); - (new Thread(task)).start(); + (new Thread(task, "adb-restore")).start(); break; } @@ -2618,13 +2629,6 @@ public class BackupManagerService extends IBackupManager.Stub { // ----- Full backup/restore to a file/socket ----- - abstract class ObbServiceClient { - public IObbBackupService mObbService; - public void setObbBinder(IObbBackupService binder) { - mObbService = binder; - } - } - class FullBackupObbConnection implements ServiceConnection { volatile IObbBackupService mService; @@ -2736,24 +2740,15 @@ public class BackupManagerService extends IBackupManager.Stub { } } - class PerformFullBackupTask extends ObbServiceClient implements Runnable { - ParcelFileDescriptor mOutputFile; - DeflaterOutputStream mDeflater; + // Core logic for performing one package's full backup, gathering the tarball from the + // application and emitting it to the designated OutputStream. + class FullBackupEngine { + OutputStream mOutput; IFullBackupRestoreObserver mObserver; - boolean mIncludeApks; - boolean mIncludeObbs; - boolean mIncludeShared; - boolean mDoWidgets; - boolean mAllApps; - final boolean mIncludeSystem; - ArrayList<String> mPackages; - String mCurrentPassword; - String mEncryptPassword; - AtomicBoolean mLatchObject; File mFilesDir; File mManifestFile; File mMetadataFile; - + boolean mIncludeApks; class FullBackupRunner implements Runnable { PackageInfo mPackage; @@ -2823,12 +2818,281 @@ public class BackupManagerService extends IBackupManager.Stub { } } - PerformFullBackupTask(ParcelFileDescriptor fd, IFullBackupRestoreObserver observer, + FullBackupEngine(OutputStream output, String packageName, boolean alsoApks) { + mOutput = output; + mIncludeApks = alsoApks; + mFilesDir = new File("/data/system"); + mManifestFile = new File(mFilesDir, BACKUP_MANIFEST_FILENAME); + mMetadataFile = new File(mFilesDir, BACKUP_METADATA_FILENAME); + } + + + public int backupOnePackage(PackageInfo pkg) throws RemoteException { + int result = BackupTransport.TRANSPORT_OK; + Slog.d(TAG, "Binding to full backup agent : " + pkg.packageName); + + IBackupAgent agent = bindToAgentSynchronous(pkg.applicationInfo, + IApplicationThread.BACKUP_MODE_FULL); + if (agent != null) { + ParcelFileDescriptor[] pipes = null; + try { + pipes = ParcelFileDescriptor.createPipe(); + + ApplicationInfo app = pkg.applicationInfo; + final boolean isSharedStorage = pkg.packageName.equals(SHARED_BACKUP_AGENT_PACKAGE); + final boolean sendApk = mIncludeApks + && !isSharedStorage + && ((app.flags & ApplicationInfo.FLAG_FORWARD_LOCK) == 0) + && ((app.flags & ApplicationInfo.FLAG_SYSTEM) == 0 || + (app.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0); + + byte[] widgetBlob = AppWidgetBackupBridge.getWidgetState(pkg.packageName, + UserHandle.USER_OWNER); + + final int token = generateToken(); + FullBackupRunner runner = new FullBackupRunner(pkg, agent, pipes[1], + token, sendApk, !isSharedStorage, widgetBlob); + pipes[1].close(); // the runner has dup'd it + pipes[1] = null; + Thread t = new Thread(runner, "app-data-runner"); + t.start(); + + // Now pull data from the app and stuff it into the output + try { + routeSocketDataToOutput(pipes[0], mOutput); + } catch (IOException e) { + Slog.i(TAG, "Caught exception reading from agent", e); + result = BackupTransport.AGENT_ERROR; + } + + if (!waitUntilOperationComplete(token)) { + Slog.e(TAG, "Full backup failed on package " + pkg.packageName); + result = BackupTransport.AGENT_ERROR; + } else { + if (DEBUG) Slog.d(TAG, "Full package backup success: " + pkg.packageName); + } + + } catch (IOException e) { + Slog.e(TAG, "Error backing up " + pkg.packageName, e); + result = BackupTransport.AGENT_ERROR; + } finally { + try { + // flush after every package + mOutput.flush(); + if (pipes != null) { + if (pipes[0] != null) pipes[0].close(); + if (pipes[1] != null) pipes[1].close(); + } + } catch (IOException e) { + Slog.w(TAG, "Error bringing down backup stack"); + result = BackupTransport.TRANSPORT_ERROR; + } + } + } else { + Slog.w(TAG, "Unable to bind to full agent for " + pkg.packageName); + result = BackupTransport.AGENT_ERROR; + } + tearDown(pkg); + return result; + } + + private void writeApkToBackup(PackageInfo pkg, BackupDataOutput output) { + // Forward-locked apps, system-bundled .apks, etc are filtered out before we get here + final String appSourceDir = pkg.applicationInfo.sourceDir; + final String apkDir = new File(appSourceDir).getParent(); + FullBackup.backupToTar(pkg.packageName, FullBackup.APK_TREE_TOKEN, null, + apkDir, appSourceDir, output); + + // TODO: migrate this to SharedStorageBackup, since AID_SYSTEM + // doesn't have access to external storage. + + // Save associated .obb content if it exists and we did save the apk + // check for .obb and save those too + final UserEnvironment userEnv = new UserEnvironment(UserHandle.USER_OWNER); + final File obbDir = userEnv.buildExternalStorageAppObbDirs(pkg.packageName)[0]; + if (obbDir != null) { + if (MORE_DEBUG) Log.i(TAG, "obb dir: " + obbDir.getAbsolutePath()); + File[] obbFiles = obbDir.listFiles(); + if (obbFiles != null) { + final String obbDirName = obbDir.getAbsolutePath(); + for (File obb : obbFiles) { + FullBackup.backupToTar(pkg.packageName, FullBackup.OBB_TREE_TOKEN, null, + obbDirName, obb.getAbsolutePath(), output); + } + } + } + } + + private void writeAppManifest(PackageInfo pkg, File manifestFile, + boolean withApk, boolean withWidgets) throws IOException { + // Manifest format. All data are strings ending in LF: + // BACKUP_MANIFEST_VERSION, currently 1 + // + // Version 1: + // package name + // package's versionCode + // platform versionCode + // getInstallerPackageName() for this package (maybe empty) + // boolean: "1" if archive includes .apk; any other string means not + // number of signatures == N + // N*: signature byte array in ascii format per Signature.toCharsString() + StringBuilder builder = new StringBuilder(4096); + StringBuilderPrinter printer = new StringBuilderPrinter(builder); + + printer.println(Integer.toString(BACKUP_MANIFEST_VERSION)); + printer.println(pkg.packageName); + printer.println(Integer.toString(pkg.versionCode)); + printer.println(Integer.toString(Build.VERSION.SDK_INT)); + + String installerName = mPackageManager.getInstallerPackageName(pkg.packageName); + printer.println((installerName != null) ? installerName : ""); + + printer.println(withApk ? "1" : "0"); + if (pkg.signatures == null) { + printer.println("0"); + } else { + printer.println(Integer.toString(pkg.signatures.length)); + for (Signature sig : pkg.signatures) { + printer.println(sig.toCharsString()); + } + } + + FileOutputStream outstream = new FileOutputStream(manifestFile); + outstream.write(builder.toString().getBytes()); + outstream.close(); + } + + // Widget metadata format. All header entries are strings ending in LF: + // + // Version 1 header: + // BACKUP_METADATA_VERSION, currently "1" + // package name + // + // File data (all integers are binary in network byte order) + // *N: 4 : integer token identifying which metadata blob + // 4 : integer size of this blob = N + // N : raw bytes of this metadata blob + // + // Currently understood blobs (always in network byte order): + // + // widgets : metadata token = 0x01FFED01 (BACKUP_WIDGET_METADATA_TOKEN) + // + // Unrecognized blobs are *ignored*, not errors. + private void writeMetadata(PackageInfo pkg, File destination, byte[] widgetData) + throws IOException { + StringBuilder b = new StringBuilder(512); + StringBuilderPrinter printer = new StringBuilderPrinter(b); + printer.println(Integer.toString(BACKUP_METADATA_VERSION)); + printer.println(pkg.packageName); + + FileOutputStream fout = new FileOutputStream(destination); + BufferedOutputStream bout = new BufferedOutputStream(fout); + DataOutputStream out = new DataOutputStream(bout); + bout.write(b.toString().getBytes()); // bypassing DataOutputStream + + if (widgetData != null && widgetData.length > 0) { + out.writeInt(BACKUP_WIDGET_METADATA_TOKEN); + out.writeInt(widgetData.length); + out.write(widgetData); + } + bout.flush(); + out.close(); + } + + private void tearDown(PackageInfo pkg) { + if (pkg != null) { + final ApplicationInfo app = pkg.applicationInfo; + if (app != null) { + try { + // unbind and tidy up even on timeout or failure, just in case + mActivityManager.unbindBackupAgent(app); + + // The agent was running with a stub Application object, so shut it down. + if (app.uid != Process.SYSTEM_UID + && app.uid != Process.PHONE_UID) { + if (MORE_DEBUG) Slog.d(TAG, "Backup complete, killing host process"); + mActivityManager.killApplicationProcess(app.processName, app.uid); + } else { + if (MORE_DEBUG) Slog.d(TAG, "Not killing after backup: " + app.processName); + } + } catch (RemoteException e) { + Slog.d(TAG, "Lost app trying to shut down"); + } + } + } + } + } + + // Generic driver skeleton for full backup operations + abstract class FullBackupTask implements Runnable { + IFullBackupRestoreObserver mObserver; + + FullBackupTask(IFullBackupRestoreObserver observer) { + mObserver = observer; + } + + // wrappers for observer use + final void sendStartBackup() { + if (mObserver != null) { + try { + mObserver.onStartBackup(); + } catch (RemoteException e) { + Slog.w(TAG, "full backup observer went away: startBackup"); + mObserver = null; + } + } + } + + final void sendOnBackupPackage(String name) { + if (mObserver != null) { + try { + // TODO: use a more user-friendly name string + mObserver.onBackupPackage(name); + } catch (RemoteException e) { + Slog.w(TAG, "full backup observer went away: backupPackage"); + mObserver = null; + } + } + } + + final void sendEndBackup() { + if (mObserver != null) { + try { + mObserver.onEndBackup(); + } catch (RemoteException e) { + Slog.w(TAG, "full backup observer went away: endBackup"); + mObserver = null; + } + } + } + } + + // Full backup task variant used for adb backup + class PerformAdbBackupTask extends FullBackupTask { + FullBackupEngine mBackupEngine; + final AtomicBoolean mLatch; + + ParcelFileDescriptor mOutputFile; + DeflaterOutputStream mDeflater; + boolean mIncludeApks; + boolean mIncludeObbs; + boolean mIncludeShared; + boolean mDoWidgets; + boolean mAllApps; + boolean mIncludeSystem; + boolean mCompress; + ArrayList<String> mPackages; + String mCurrentPassword; + String mEncryptPassword; + + PerformAdbBackupTask(ParcelFileDescriptor fd, IFullBackupRestoreObserver observer, boolean includeApks, boolean includeObbs, boolean includeShared, boolean doWidgets, String curPassword, String encryptPassword, boolean doAllApps, - boolean doSystem, String[] packages, AtomicBoolean latch) { + boolean doSystem, boolean doCompress, String[] packages, AtomicBoolean latch) { + super(observer); + mLatch = latch; + mOutputFile = fd; - mObserver = observer; mIncludeApks = includeApks; mIncludeObbs = includeObbs; mIncludeShared = includeShared; @@ -2848,11 +3112,7 @@ public class BackupManagerService extends IBackupManager.Stub { } else { mEncryptPassword = encryptPassword; } - mLatchObject = latch; - - mFilesDir = new File("/data/system"); - mManifestFile = new File(mFilesDir, BACKUP_MANIFEST_FILENAME); - mMetadataFile = new File(mFilesDir, BACKUP_METADATA_FILENAME); + mCompress = doCompress; } void addPackagesToSet(TreeMap<String, PackageInfo> set, List<String> pkgNames) { @@ -2869,9 +3129,90 @@ public class BackupManagerService extends IBackupManager.Stub { } } + private OutputStream emitAesBackupHeader(StringBuilder headerbuf, + OutputStream ofstream) throws Exception { + // User key will be used to encrypt the master key. + byte[] newUserSalt = randomBytes(PBKDF2_SALT_SIZE); + SecretKey userKey = buildPasswordKey(PBKDF_CURRENT, mEncryptPassword, newUserSalt, + PBKDF2_HASH_ROUNDS); + + // the master key is random for each backup + byte[] masterPw = new byte[256 / 8]; + mRng.nextBytes(masterPw); + byte[] checksumSalt = randomBytes(PBKDF2_SALT_SIZE); + + // primary encryption of the datastream with the random key + Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding"); + SecretKeySpec masterKeySpec = new SecretKeySpec(masterPw, "AES"); + c.init(Cipher.ENCRYPT_MODE, masterKeySpec); + OutputStream finalOutput = new CipherOutputStream(ofstream, c); + + // line 4: name of encryption algorithm + headerbuf.append(ENCRYPTION_ALGORITHM_NAME); + headerbuf.append('\n'); + // line 5: user password salt [hex] + headerbuf.append(byteArrayToHex(newUserSalt)); + headerbuf.append('\n'); + // line 6: master key checksum salt [hex] + headerbuf.append(byteArrayToHex(checksumSalt)); + headerbuf.append('\n'); + // line 7: number of PBKDF2 rounds used [decimal] + headerbuf.append(PBKDF2_HASH_ROUNDS); + headerbuf.append('\n'); + + // line 8: IV of the user key [hex] + Cipher mkC = Cipher.getInstance("AES/CBC/PKCS5Padding"); + mkC.init(Cipher.ENCRYPT_MODE, userKey); + + byte[] IV = mkC.getIV(); + headerbuf.append(byteArrayToHex(IV)); + headerbuf.append('\n'); + + // line 9: master IV + key blob, encrypted by the user key [hex]. Blob format: + // [byte] IV length = Niv + // [array of Niv bytes] IV itself + // [byte] master key length = Nmk + // [array of Nmk bytes] master key itself + // [byte] MK checksum hash length = Nck + // [array of Nck bytes] master key checksum hash + // + // The checksum is the (master key + checksum salt), run through the + // stated number of PBKDF2 rounds + IV = c.getIV(); + byte[] mk = masterKeySpec.getEncoded(); + byte[] checksum = makeKeyChecksum(PBKDF_CURRENT, masterKeySpec.getEncoded(), + checksumSalt, PBKDF2_HASH_ROUNDS); + + ByteArrayOutputStream blob = new ByteArrayOutputStream(IV.length + mk.length + + checksum.length + 3); + DataOutputStream mkOut = new DataOutputStream(blob); + mkOut.writeByte(IV.length); + mkOut.write(IV); + mkOut.writeByte(mk.length); + mkOut.write(mk); + mkOut.writeByte(checksum.length); + mkOut.write(checksum); + mkOut.flush(); + byte[] encryptedMk = mkC.doFinal(blob.toByteArray()); + headerbuf.append(byteArrayToHex(encryptedMk)); + headerbuf.append('\n'); + + return finalOutput; + } + + private void finalizeBackup(OutputStream out) { + try { + // A standard 'tar' EOF sequence: two 512-byte blocks of all zeroes. + byte[] eof = new byte[512 * 2]; // newly allocated == zero filled + out.write(eof); + } catch (IOException e) { + Slog.w(TAG, "Error attempting to finalize backup stream"); + } + } + @Override public void run() { - Slog.i(TAG, "--- Performing full-dataset backup ---"); + Slog.i(TAG, "--- Performing full-dataset adb backup ---"); TreeMap<String, PackageInfo> packagesToBackup = new TreeMap<String, PackageInfo>(); FullBackupObbConnection obbConnection = new FullBackupObbConnection(); @@ -2944,14 +3285,12 @@ public class BackupManagerService extends IBackupManager.Stub { // flatten the set of packages now so we can explicitly control the ordering ArrayList<PackageInfo> backupQueue = new ArrayList<PackageInfo>(packagesToBackup.values()); - FileOutputStream ofstream = new FileOutputStream(mOutputFile.getFileDescriptor()); OutputStream out = null; PackageInfo pkg = null; try { boolean encrypting = (mEncryptPassword != null && mEncryptPassword.length() > 0); - boolean compressing = COMPRESS_FULL_BACKUPS; OutputStream finalOutput = ofstream; // Verify that the given password matches the currently-active @@ -2990,7 +3329,7 @@ public class BackupManagerService extends IBackupManager.Stub { headerbuf.append(BACKUP_FILE_HEADER_MAGIC); headerbuf.append(BACKUP_FILE_VERSION); // integer, no trailing \n - headerbuf.append(compressing ? "\n1\n" : "\n0\n"); + headerbuf.append(mCompress ? "\n1\n" : "\n0\n"); try { // Set up the encryption stage if appropriate, and emit the correct header @@ -3004,7 +3343,7 @@ public class BackupManagerService extends IBackupManager.Stub { ofstream.write(header); // Set up the compression stage feeding into the encryption stage (if any) - if (compressing) { + if (mCompress) { Deflater deflater = new Deflater(Deflater.BEST_COMPRESSION); finalOutput = new DeflaterOutputStream(finalOutput, deflater, true); } @@ -3026,11 +3365,16 @@ public class BackupManagerService extends IBackupManager.Stub { } } - // Now back up the app data via the agent mechanism + // Now actually run the constructed backup sequence int N = backupQueue.size(); for (int i = 0; i < N; i++) { pkg = backupQueue.get(i); - backupOnePackage(pkg, out); + final boolean isSharedStorage = + pkg.packageName.equals(SHARED_BACKUP_AGENT_PACKAGE); + + mBackupEngine = new FullBackupEngine(out, pkg.packageName, mIncludeApks); + sendOnBackupPackage(isSharedStorage ? "Shared storage" : pkg.packageName); + mBackupEngine.backupOnePackage(pkg); // after the app's agent runs to handle its private filesystem // contents, back up any OBB content it has on its behalf. @@ -3049,7 +3393,6 @@ public class BackupManagerService extends IBackupManager.Stub { } catch (Exception e) { Slog.e(TAG, "Internal exception during full backup", e); } finally { - tearDown(pkg); try { if (out != null) out.close(); mOutputFile.close(); @@ -3059,9 +3402,9 @@ public class BackupManagerService extends IBackupManager.Stub { synchronized (mCurrentOpLock) { mCurrentOperations.clear(); } - synchronized (mLatchObject) { - mLatchObject.set(true); - mLatchObject.notifyAll(); + synchronized (mLatch) { + mLatch.set(true); + mLatch.notifyAll(); } sendEndBackup(); obbConnection.tearDown(); @@ -3069,315 +3412,212 @@ public class BackupManagerService extends IBackupManager.Stub { mWakelock.release(); } } + } - private OutputStream emitAesBackupHeader(StringBuilder headerbuf, - OutputStream ofstream) throws Exception { - // User key will be used to encrypt the master key. - byte[] newUserSalt = randomBytes(PBKDF2_SALT_SIZE); - SecretKey userKey = buildPasswordKey(PBKDF_CURRENT, mEncryptPassword, newUserSalt, - PBKDF2_HASH_ROUNDS); - - // the master key is random for each backup - byte[] masterPw = new byte[256 / 8]; - mRng.nextBytes(masterPw); - byte[] checksumSalt = randomBytes(PBKDF2_SALT_SIZE); - - // primary encryption of the datastream with the random key - Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding"); - SecretKeySpec masterKeySpec = new SecretKeySpec(masterPw, "AES"); - c.init(Cipher.ENCRYPT_MODE, masterKeySpec); - OutputStream finalOutput = new CipherOutputStream(ofstream, c); - - // line 4: name of encryption algorithm - headerbuf.append(ENCRYPTION_ALGORITHM_NAME); - headerbuf.append('\n'); - // line 5: user password salt [hex] - headerbuf.append(byteArrayToHex(newUserSalt)); - headerbuf.append('\n'); - // line 6: master key checksum salt [hex] - headerbuf.append(byteArrayToHex(checksumSalt)); - headerbuf.append('\n'); - // line 7: number of PBKDF2 rounds used [decimal] - headerbuf.append(PBKDF2_HASH_ROUNDS); - headerbuf.append('\n'); - - // line 8: IV of the user key [hex] - Cipher mkC = Cipher.getInstance("AES/CBC/PKCS5Padding"); - mkC.init(Cipher.ENCRYPT_MODE, userKey); - - byte[] IV = mkC.getIV(); - headerbuf.append(byteArrayToHex(IV)); - headerbuf.append('\n'); - - // line 9: master IV + key blob, encrypted by the user key [hex]. Blob format: - // [byte] IV length = Niv - // [array of Niv bytes] IV itself - // [byte] master key length = Nmk - // [array of Nmk bytes] master key itself - // [byte] MK checksum hash length = Nck - // [array of Nck bytes] master key checksum hash - // - // The checksum is the (master key + checksum salt), run through the - // stated number of PBKDF2 rounds - IV = c.getIV(); - byte[] mk = masterKeySpec.getEncoded(); - byte[] checksum = makeKeyChecksum(PBKDF_CURRENT, masterKeySpec.getEncoded(), - checksumSalt, PBKDF2_HASH_ROUNDS); - - ByteArrayOutputStream blob = new ByteArrayOutputStream(IV.length + mk.length - + checksum.length + 3); - DataOutputStream mkOut = new DataOutputStream(blob); - mkOut.writeByte(IV.length); - mkOut.write(IV); - mkOut.writeByte(mk.length); - mkOut.write(mk); - mkOut.writeByte(checksum.length); - mkOut.write(checksum); - mkOut.flush(); - byte[] encryptedMk = mkC.doFinal(blob.toByteArray()); - headerbuf.append(byteArrayToHex(encryptedMk)); - headerbuf.append('\n'); - - return finalOutput; - } + // Full backup task extension used for transport-oriented operation + class PerformFullTransportBackupTask extends FullBackupTask { + static final String TAG = "PFTBT"; + ArrayList<PackageInfo> mPackages; + AtomicBoolean mLatch; - private void backupOnePackage(PackageInfo pkg, OutputStream out) - throws RemoteException { - Slog.d(TAG, "Binding to full backup agent : " + pkg.packageName); + PerformFullTransportBackupTask(IFullBackupRestoreObserver observer, + String[] whichPackages, AtomicBoolean latch) { + super(observer); + mLatch = latch; + mPackages = new ArrayList<PackageInfo>(whichPackages.length); - IBackupAgent agent = bindToAgentSynchronous(pkg.applicationInfo, - IApplicationThread.BACKUP_MODE_FULL); - if (agent != null) { - ParcelFileDescriptor[] pipes = null; + for (String pkg : whichPackages) { try { - pipes = ParcelFileDescriptor.createPipe(); - - ApplicationInfo app = pkg.applicationInfo; - final boolean isSharedStorage = pkg.packageName.equals(SHARED_BACKUP_AGENT_PACKAGE); - final boolean sendApk = mIncludeApks - && !isSharedStorage - && ((app.flags & ApplicationInfo.FLAG_FORWARD_LOCK) == 0) - && ((app.flags & ApplicationInfo.FLAG_SYSTEM) == 0 || - (app.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0); - - byte[] widgetBlob = AppWidgetBackupBridge.getWidgetState(pkg.packageName, - UserHandle.USER_OWNER); - sendOnBackupPackage(isSharedStorage ? "Shared storage" : pkg.packageName); - - final int token = generateToken(); - FullBackupRunner runner = new FullBackupRunner(pkg, agent, pipes[1], - token, sendApk, !isSharedStorage, widgetBlob); - pipes[1].close(); // the runner has dup'd it - pipes[1] = null; - Thread t = new Thread(runner); - t.start(); - - // Now pull data from the app and stuff it into the compressor - try { - routeSocketDataToOutput(pipes[0], out); - } catch (IOException e) { - Slog.i(TAG, "Caught exception reading from agent", e); - } - - if (!waitUntilOperationComplete(token)) { - Slog.e(TAG, "Full backup failed on package " + pkg.packageName); - } else { - if (DEBUG) Slog.d(TAG, "Full package backup success: " + pkg.packageName); - } - - } catch (IOException e) { - Slog.e(TAG, "Error backing up " + pkg.packageName, e); - } finally { - try { - // flush after every package - out.flush(); - if (pipes != null) { - if (pipes[0] != null) pipes[0].close(); - if (pipes[1] != null) pipes[1].close(); + PackageInfo info = mPackageManager.getPackageInfo(pkg, + PackageManager.GET_SIGNATURES); + if ((info.applicationInfo.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) == 0 + || pkg.equals(SHARED_BACKUP_AGENT_PACKAGE)) { + // Cull any packages that have indicated that backups are not permitted, + // 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 opted-out package " + pkg); } - } catch (IOException e) { - Slog.w(TAG, "Error bringing down backup stack"); + continue; + } else if ((info.applicationInfo.uid < Process.FIRST_APPLICATION_UID) + && (info.applicationInfo.backupAgentName == null)) { + // Cull any packages that run as system-domain uids but do not define their + // own backup agents + if (MORE_DEBUG) { + Slog.d(TAG, "Ignoring non-agent system package " + pkg); + } + continue; } + mPackages.add(info); + } catch (NameNotFoundException e) { + Slog.i(TAG, "Requested package " + pkg + " not found; ignoring"); } - } else { - Slog.w(TAG, "Unable to bind to full agent for " + pkg.packageName); } - tearDown(pkg); } - private void writeApkToBackup(PackageInfo pkg, BackupDataOutput output) { - // Forward-locked apps, system-bundled .apks, etc are filtered out before we get here - final String appSourceDir = pkg.applicationInfo.sourceDir; - final String apkDir = new File(appSourceDir).getParent(); - FullBackup.backupToTar(pkg.packageName, FullBackup.APK_TREE_TOKEN, null, - apkDir, appSourceDir, output); - - // TODO: migrate this to SharedStorageBackup, since AID_SYSTEM - // doesn't have access to external storage. - - // Save associated .obb content if it exists and we did save the apk - // check for .obb and save those too - final UserEnvironment userEnv = new UserEnvironment(UserHandle.USER_OWNER); - final File obbDir = userEnv.buildExternalStorageAppObbDirs(pkg.packageName)[0]; - if (obbDir != null) { - if (MORE_DEBUG) Log.i(TAG, "obb dir: " + obbDir.getAbsolutePath()); - File[] obbFiles = obbDir.listFiles(); - if (obbFiles != null) { - final String obbDirName = obbDir.getAbsolutePath(); - for (File obb : obbFiles) { - FullBackup.backupToTar(pkg.packageName, FullBackup.OBB_TREE_TOKEN, null, - obbDirName, obb.getAbsolutePath(), output); - } - } + @Override + public void run() { + IBackupTransport transport = getTransport(mCurrentTransport); + if (transport == null) { + Slog.w(TAG, "Transport not present; full data backup not performed"); + return; } - } - private void finalizeBackup(OutputStream out) { - try { - // A standard 'tar' EOF sequence: two 512-byte blocks of all zeroes. - byte[] eof = new byte[512 * 2]; // newly allocated == zero filled - out.write(eof); - } catch (IOException e) { - Slog.w(TAG, "Error attempting to finalize backup stream"); - } - } + // data from the app, passed to us for bridging to the transport + ParcelFileDescriptor[] enginePipes = null; - private void writeAppManifest(PackageInfo pkg, File manifestFile, - boolean withApk, boolean withWidgets) throws IOException { - // Manifest format. All data are strings ending in LF: - // BACKUP_MANIFEST_VERSION, currently 1 - // - // Version 1: - // package name - // package's versionCode - // platform versionCode - // getInstallerPackageName() for this package (maybe empty) - // boolean: "1" if archive includes .apk; any other string means not - // number of signatures == N - // N*: signature byte array in ascii format per Signature.toCharsString() - StringBuilder builder = new StringBuilder(4096); - StringBuilderPrinter printer = new StringBuilderPrinter(builder); + // Pipe through which we write data to the transport + ParcelFileDescriptor[] transportPipes = null; - printer.println(Integer.toString(BACKUP_MANIFEST_VERSION)); - printer.println(pkg.packageName); - printer.println(Integer.toString(pkg.versionCode)); - printer.println(Integer.toString(Build.VERSION.SDK_INT)); + try { + // Set up to send data to the transport + if (transport != null) { + for (PackageInfo target : mPackages) { + if (DEBUG) { + Slog.i(TAG, "Initiating full-data transport backup of " + + target.packageName); + } + transportPipes = ParcelFileDescriptor.createPipe(); + + // Tell the transport the data's coming + int result = transport.performFullBackup(target, transportPipes[0]); + if (result == 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; + + // Now set up the backup engine / data source end of things + enginePipes = ParcelFileDescriptor.createPipe(); + AtomicBoolean runnerLatch = new AtomicBoolean(false); + SinglePackageBackupRunner backupRunner = + new SinglePackageBackupRunner(enginePipes[1], target, + runnerLatch); + // The runner dup'd the pipe half, so we close it here + enginePipes[1].close(); + enginePipes[1] = null; + + // Spin off the runner to fetch the app's data and pipe it + // into the engine pipes + (new Thread(backupRunner, "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. + FileInputStream in = new FileInputStream( + enginePipes[0].getFileDescriptor()); + FileOutputStream out = new FileOutputStream( + transportPipes[1].getFileDescriptor()); + byte[] buffer = new byte[8192]; + int nRead = 0; + do { + nRead = in.read(buffer); + if (nRead > 0) { + out.write(buffer, 0, nRead); + result = transport.sendBackupData(nRead); + } + } while (nRead > 0 && result == BackupTransport.TRANSPORT_OK); - String installerName = mPackageManager.getInstallerPackageName(pkg.packageName); - printer.println((installerName != null) ? installerName : ""); + // Done -- how did it turn out? + if (result == BackupTransport.TRANSPORT_OK){ + result = transport.finishBackup(); + } else { + Slog.w(TAG, "Error backing up " + target.packageName); + } + } else if (result == BackupTransport.TRANSPORT_PACKAGE_REJECTED) { + if (DEBUG) { + Slog.i(TAG, "Transport rejected backup of " + target.packageName + + ", skipping"); + } + // do nothing, clean up, and continue looping + } else { + if (DEBUG) { + Slog.i(TAG, "Transport failed; aborting backup"); + return; + } + } + cleanUpPipes(transportPipes); + cleanUpPipes(enginePipes); + } - printer.println(withApk ? "1" : "0"); - if (pkg.signatures == null) { - printer.println("0"); - } else { - printer.println(Integer.toString(pkg.signatures.length)); - for (Signature sig : pkg.signatures) { - printer.println(sig.toCharsString()); + if (DEBUG) { + Slog.i(TAG, "Full backup completed."); + } + } + } catch (Exception e) { + Slog.w(TAG, "Exception trying full transport backup", e); + } finally { + cleanUpPipes(transportPipes); + cleanUpPipes(enginePipes); + synchronized (mLatch) { + mLatch.set(true); + mLatch.notifyAll(); } } - - FileOutputStream outstream = new FileOutputStream(manifestFile); - outstream.write(builder.toString().getBytes()); - outstream.close(); - } - - // Widget metadata format. All header entries are strings ending in LF: - // - // Version 1 header: - // BACKUP_METADATA_VERSION, currently "1" - // package name - // - // File data (all integers are binary in network byte order) - // *N: 4 : integer token identifying which metadata blob - // 4 : integer size of this blob = N - // N : raw bytes of this metadata blob - // - // Currently understood blobs (always in network byte order): - // - // widgets : metadata token = 0x01FFED01 (BACKUP_WIDGET_METADATA_TOKEN) - // - // Unrecognized blobs are *ignored*, not errors. - private void writeMetadata(PackageInfo pkg, File destination, byte[] widgetData) - throws IOException { - StringBuilder b = new StringBuilder(512); - StringBuilderPrinter printer = new StringBuilderPrinter(b); - printer.println(Integer.toString(BACKUP_METADATA_VERSION)); - printer.println(pkg.packageName); - - FileOutputStream fout = new FileOutputStream(destination); - BufferedOutputStream bout = new BufferedOutputStream(fout); - DataOutputStream out = new DataOutputStream(bout); - bout.write(b.toString().getBytes()); // bypassing DataOutputStream - - if (widgetData != null && widgetData.length > 0) { - out.writeInt(BACKUP_WIDGET_METADATA_TOKEN); - out.writeInt(widgetData.length); - out.write(widgetData); - } - bout.flush(); - out.close(); } - private void tearDown(PackageInfo pkg) { - if (pkg != null) { - final ApplicationInfo app = pkg.applicationInfo; - if (app != null) { + void cleanUpPipes(ParcelFileDescriptor[] pipes) { + if (pipes != null) { + if (pipes[0] != null) { + ParcelFileDescriptor fd = pipes[0]; + pipes[0] = null; try { - // unbind and tidy up even on timeout or failure, just in case - mActivityManager.unbindBackupAgent(app); - - // The agent was running with a stub Application object, so shut it down. - if (app.uid != Process.SYSTEM_UID - && app.uid != Process.PHONE_UID) { - if (MORE_DEBUG) Slog.d(TAG, "Backup complete, killing host process"); - mActivityManager.killApplicationProcess(app.processName, app.uid); - } else { - if (MORE_DEBUG) Slog.d(TAG, "Not killing after restore: " + app.processName); - } - } catch (RemoteException e) { - Slog.d(TAG, "Lost app trying to shut down"); + fd.close(); + } catch (IOException e) { + Slog.w(TAG, "Unable to close pipe!"); } } - } - } - - // wrappers for observer use - void sendStartBackup() { - if (mObserver != null) { - try { - mObserver.onStartBackup(); - } catch (RemoteException e) { - Slog.w(TAG, "full backup observer went away: startBackup"); - mObserver = null; + if (pipes[1] != null) { + ParcelFileDescriptor fd = pipes[1]; + pipes[1] = null; + try { + fd.close(); + } catch (IOException e) { + Slog.w(TAG, "Unable to close pipe!"); + } } } } - void sendOnBackupPackage(String name) { - if (mObserver != null) { - try { - // TODO: use a more user-friendly name string - mObserver.onBackupPackage(name); - } catch (RemoteException e) { - Slog.w(TAG, "full backup observer went away: backupPackage"); - mObserver = null; - } + // 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 SinglePackageBackupRunner implements Runnable { + final ParcelFileDescriptor mOutput; + final PackageInfo mTarget; + final AtomicBoolean mLatch; + + SinglePackageBackupRunner(ParcelFileDescriptor output, PackageInfo target, + AtomicBoolean latch) throws IOException { + int oldfd = output.getFd(); + mOutput = ParcelFileDescriptor.dup(output.getFileDescriptor()); + mTarget = target; + mLatch = latch; } - } - void sendEndBackup() { - if (mObserver != null) { + @Override + public void run() { try { - mObserver.onEndBackup(); - } catch (RemoteException e) { - Slog.w(TAG, "full backup observer went away: endBackup"); - mObserver = null; + FileOutputStream out = new FileOutputStream(mOutput.getFileDescriptor()); + FullBackupEngine engine = new FullBackupEngine(out, mTarget.packageName, false); + engine.backupOnePackage(mTarget); + } catch (Exception e) { + Slog.e(TAG, "Exception during full package backup of " + mTarget); + } finally { + synchronized (mLatch) { + mLatch.set(true); + mLatch.notifyAll(); + } + try { + mOutput.close(); + } catch (IOException e) { + Slog.w(TAG, "Error closing transport pipe in runner"); + } } } + } } - // ----- Full restore from a file/socket ----- // Description of a file in the restore datastream @@ -3410,7 +3650,7 @@ public class BackupManagerService extends IBackupManager.Stub { ACCEPT_IF_APK } - class PerformFullRestoreTask extends ObbServiceClient implements Runnable { + class PerformFullRestoreTask implements Runnable { ParcelFileDescriptor mInputFile; String mCurrentPassword; String mDecryptPassword; @@ -3877,7 +4117,7 @@ public class BackupManagerService extends IBackupManager.Stub { Slog.d(TAG, "system process agent - spinning a thread"); RestoreFileRunnable runner = new RestoreFileRunnable( mAgent, info, mPipes[0], token); - new Thread(runner).start(); + new Thread(runner, "restore-sys-runner").start(); } else { mAgent.doRestoreFile(mPipes[0], info.size, info.type, info.domain, info.path, info.mode, info.mtime, @@ -5684,13 +5924,16 @@ public class BackupManagerService extends IBackupManager.Stub { return (Settings.Global.getInt(resolver, Settings.Global.DEVICE_PROVISIONED, 0) != 0); } - // Run a *full* backup pass for the given package, writing the resulting data stream + // Run a *full* backup pass for the given packages, writing the resulting data stream // to the supplied file descriptor. This method is synchronous and does not return // to the caller until the backup has been completed. + // + // This is the variant used by 'adb backup'; it requires on-screen confirmation + // by the user because it can be used to offload data over untrusted USB. @Override public void fullBackup(ParcelFileDescriptor fd, boolean includeApks, boolean includeObbs, boolean includeShared, boolean doWidgets, - boolean doAllApps, boolean includeSystem, String[] pkgList) { + boolean doAllApps, boolean includeSystem, boolean compress, String[] pkgList) { mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "fullBackup"); final int callingUserHandle = UserHandle.getCallingUserId(); @@ -5725,7 +5968,7 @@ public class BackupManagerService extends IBackupManager.Stub { Slog.i(TAG, "Beginning full backup..."); FullBackupParams params = new FullBackupParams(fd, includeApks, includeObbs, - includeShared, doWidgets, doAllApps, includeSystem, pkgList); + includeShared, doWidgets, doAllApps, includeSystem, compress, pkgList); final int token = generateToken(); synchronized (mFullConfirmations) { mFullConfirmations.put(token, params); @@ -5759,6 +6002,36 @@ public class BackupManagerService extends IBackupManager.Stub { } } + @Override + public void fullTransportBackup(String[] pkgNames) { + mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, + "fullTransportBackup"); + + final int callingUserHandle = UserHandle.getCallingUserId(); + if (callingUserHandle != UserHandle.USER_OWNER) { + throw new IllegalStateException("Restore supported only for the device owner"); + } + + if (DEBUG) { + Slog.d(TAG, "fullTransportBackup()"); + } + + AtomicBoolean latch = new AtomicBoolean(false); + PerformFullTransportBackupTask task = new PerformFullTransportBackupTask(null, pkgNames, latch); + (new Thread(task, "full-transport-master")).start(); + synchronized (latch) { + try { + while (latch.get() == false) { + latch.wait(); + } + } catch (InterruptedException e) {} + } + if (DEBUG) { + Slog.d(TAG, "Done with full transport backup."); + } + } + + @Override public void fullRestore(ParcelFileDescriptor fd) { mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "fullRestore"); @@ -5876,8 +6149,8 @@ public class BackupManagerService extends IBackupManager.Stub { if (allow) { final int verb = params instanceof FullBackupParams - ? MSG_RUN_FULL_BACKUP - : MSG_RUN_FULL_RESTORE; + ? MSG_RUN_ADB_BACKUP + : MSG_RUN_ADB_RESTORE; params.observer = observer; params.curPassword = curPassword; |