summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java21
-rw-r--r--cmds/bu/src/com/android/commands/bu/Backup.java7
-rw-r--r--core/java/android/app/backup/BackupTransport.java67
-rw-r--r--core/java/android/app/backup/IBackupManager.aidl10
-rw-r--r--core/java/com/android/internal/backup/IBackupTransport.aidl4
-rw-r--r--core/java/com/android/internal/backup/LocalTransport.java177
-rw-r--r--services/backup/java/com/android/server/backup/BackupManagerService.java933
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;