diff options
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; |