From 6aa41f4c575479672661f7eb4c704ef59d26a629 Mon Sep 17 00:00:00 2001 From: Christopher Tate Date: Fri, 19 Jun 2009 14:14:22 -0700 Subject: [PATCH] Add app version to the backup metadata We now record the version number of the app (drawn from its manifest versionCode attribute) along with its signatures. At restore time, we compare the version associated with the restore set with the version present on the device. If the restore set is from a newer version of the app than is present on device, we do not perform the restore operation. Also fix the pending-backup iteration in 'dumpsys backup'. --- .../android/server/BackupManagerService.java | 49 +++++++------ .../server/PackageManagerBackupAgent.java | 69 +++++++++++++------ 2 files changed, 75 insertions(+), 43 deletions(-) diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java index fa88111a2707f..75e0e64b1d927 100644 --- a/services/java/com/android/server/BackupManagerService.java +++ b/services/java/com/android/server/BackupManagerService.java @@ -54,6 +54,7 @@ import com.android.internal.backup.LocalTransport; import com.android.internal.backup.IBackupTransport; import com.android.server.PackageManagerBackupAgent; +import com.android.server.PackageManagerBackupAgent.Metadata; import java.io.EOFException; import java.io.File; @@ -111,9 +112,8 @@ class BackupManagerService extends IBackupManager.Stub { // Do we need to back up the package manager metadata on the next pass? private boolean mDoPackageManager; private static final String PACKAGE_MANAGER_SENTINEL = "@pm@"; - // Backups that we have started. These are separate to prevent starvation - // if an app keeps re-enqueuing itself. - private ArrayList mBackupQueue; + + // locking around the pending-backup management private final Object mQueueLock = new Object(); // The thread performing the sequence of queued backups binds to each app's agent @@ -296,6 +296,7 @@ class BackupManagerService extends IBackupManager.Stub { } // snapshot the pending-backup set and work on that + ArrayList queue = new ArrayList(); File oldJournal = mJournal; synchronized (mQueueLock) { if (mPendingBackups.size() == 0) { @@ -303,13 +304,11 @@ class BackupManagerService extends IBackupManager.Stub { break; } - if (mBackupQueue == null) { - mBackupQueue = new ArrayList(); - for (BackupRequest b: mPendingBackups.values()) { - mBackupQueue.add(b); - } - mPendingBackups = new HashMap(); + for (BackupRequest b: mPendingBackups.values()) { + queue.add(b); } + Log.v(TAG, "clearing pending backups"); + mPendingBackups.clear(); // Start a new backup-queue journal file too if (mJournalStream != null) { @@ -328,7 +327,7 @@ class BackupManagerService extends IBackupManager.Stub { // at next boot and the journaled requests fulfilled. } - (new PerformBackupThread(transport, mBackupQueue, oldJournal)).start(); + (new PerformBackupThread(transport, queue, oldJournal)).start(); break; } @@ -759,6 +758,8 @@ class BackupManagerService extends IBackupManager.Stub { private boolean signaturesMatch(Signature[] storedSigs, Signature[] deviceSigs) { // Allow unsigned apps, but not signed on one device and unsigned on the other // !!! TODO: is this the right policy? + if (DEBUG) Log.v(TAG, "signaturesMatch(): stored=" + storedSigs + + " device=" + deviceSigs); if ((storedSigs == null || storedSigs.length == 0) && (deviceSigs == null || deviceSigs.length == 0)) { return true; @@ -800,6 +801,7 @@ class BackupManagerService extends IBackupManager.Stub { @Override public void run() { + if (DEBUG) Log.v(TAG, "Beginning restore process"); /** * Restore sequence: * @@ -847,13 +849,19 @@ class BackupManagerService extends IBackupManager.Stub { PackageInfo app = isRestorable(pkg); if (app != null) { // Validate against the backed-up signature block, too - Signature[] storedSigs - = pmAgent.getRestoredSignatures(app.packageName); - // !!! TODO: check app version here as well - if (signaturesMatch(storedSigs, app.signatures)) { - appsToRestore.add(app); + Metadata info = pmAgent.getRestoredMetadata(app.packageName); + if (app.versionCode >= info.versionCode) { + if (DEBUG) Log.v(TAG, "Restore version " + info.versionCode + + " compatible with app version " + app.versionCode); + if (signaturesMatch(info.signatures, app.signatures)) { + appsToRestore.add(app); + } else { + Log.w(TAG, "Sig mismatch restoring " + app.packageName); + } } else { - Log.w(TAG, "Sig mismatch on restore of " + app.packageName); + Log.i(TAG, "Restore set for " + app.packageName + + " is too new [" + info.versionCode + + "] for installed app version " + app.versionCode); } } } @@ -1202,11 +1210,10 @@ class BackupManagerService extends IBackupManager.Stub { pw.println(app.toString()); } } - pw.println("Pending:"); - Iterator br = mPendingBackups.values().iterator(); - while (br.hasNext()) { - pw.print(" "); - pw.println(br); + pw.println("Pending: " + mPendingBackups.size()); + for (BackupRequest req : mPendingBackups.values()) { + pw.print(" "); + pw.println(req); } } } diff --git a/services/java/com/android/server/PackageManagerBackupAgent.java b/services/java/com/android/server/PackageManagerBackupAgent.java index 6bd32a02f464a..cc8443087e133 100644 --- a/services/java/com/android/server/PackageManagerBackupAgent.java +++ b/services/java/com/android/server/PackageManagerBackupAgent.java @@ -40,9 +40,6 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; -// !!!TODO: take this out -import java.util.zip.CRC32; - /** * We back up the signatures of each package so that during a system restore, * we can verify that the app whose data we think we have matches the app @@ -57,7 +54,17 @@ public class PackageManagerBackupAgent extends BackupAgent { private List mAllApps; private PackageManager mPackageManager; - private HashMap mRestoredSignatures; + private HashMap mRestoredSignatures; + + public class Metadata { + public int versionCode; + public Signature[] signatures; + + Metadata(int version, Signature[] sigs) { + versionCode = version; + signatures = sigs; + } + } // We're constructed with the set of applications that are participating // in backup. This set changes as apps are installed & removed. @@ -67,7 +74,7 @@ public class PackageManagerBackupAgent extends BackupAgent { mRestoredSignatures = null; } - public Signature[] getRestoredSignatures(String packageName) { + public Metadata getRestoredMetadata(String packageName) { if (mRestoredSignatures == null) { return null; } @@ -83,6 +90,9 @@ public class PackageManagerBackupAgent extends BackupAgent { // For each app we have on device, see if we've backed it up yet. If not, // write its signature block to the output, keyed on the package name. + if (DEBUG) Log.v(TAG, "onBackup()"); + ByteArrayOutputStream bufStream = new ByteArrayOutputStream(); // we'll reuse these + DataOutputStream outWriter = new DataOutputStream(bufStream); for (ApplicationInfo app : mAllApps) { String packName = app.packageName; if (!existing.contains(packName)) { @@ -90,16 +100,27 @@ public class PackageManagerBackupAgent extends BackupAgent { try { PackageInfo info = mPackageManager.getPackageInfo(packName, PackageManager.GET_SIGNATURES); - // build a byte array out of the signature list + /* + * Metadata for each package: + * + * int version -- [4] the package's versionCode + * byte[] signatures -- [len] flattened Signature[] of the package + */ + + // marshall the version code in a canonical form + bufStream.reset(); + outWriter.writeInt(info.versionCode); + byte[] versionBuf = bufStream.toByteArray(); + byte[] sigs = flattenSignatureArray(info.signatures); -// !!! TODO: take out this debugging + + // !!! TODO: take out this debugging if (DEBUG) { - CRC32 crc = new CRC32(); - crc.update(sigs); - Log.i(TAG, "+ flat sig array for " + packName + " : " - + crc.getValue()); + Log.v(TAG, "+ metadata for " + packName + " version=" + info.versionCode); } - data.writeEntityHeader(packName, sigs.length); + // Now we can write the backup entity for this package + data.writeEntityHeader(packName, versionBuf.length + sigs.length); + data.writeEntityData(versionBuf, versionBuf.length); data.writeEntityData(sigs, sigs.length); } catch (NameNotFoundException e) { // Weird; we just found it, and now are told it doesn't exist. @@ -113,6 +134,8 @@ public class PackageManagerBackupAgent extends BackupAgent { } else { // We've already backed up this app. Remove it from the set so // we can tell at the end what has disappeared from the device. + // !!! TODO: take out the debugging message + if (DEBUG) Log.v(TAG, "= already backed up metadata for " + packName); if (!existing.remove(packName)) { Log.d(TAG, "*** failed to remove " + packName + " from package set!"); } @@ -123,6 +146,8 @@ public class PackageManagerBackupAgent extends BackupAgent { // mentioned in the saved state file, but appear to no longer be present // on the device. Write a deletion entity for them. for (String app : existing) { + // !!! TODO: take out this msg + if (DEBUG) Log.v(TAG, "- removing metadata for deleted pkg " + app); try { data.writeEntityHeader(app, -1); } catch (IOException e) { @@ -141,27 +166,29 @@ public class PackageManagerBackupAgent extends BackupAgent { public void onRestore(BackupDataInput data, ParcelFileDescriptor newState) throws IOException { List restoredApps = new ArrayList(); - HashMap sigMap = new HashMap(); + HashMap sigMap = new HashMap(); while (data.readNextHeader()) { int dataSize = data.getDataSize(); byte[] buf = new byte[dataSize]; data.readEntityData(buf, 0, dataSize); - Signature[] sigs = unflattenSignatureArray(buf); + ByteArrayInputStream bufStream = new ByteArrayInputStream(buf); + DataInputStream in = new DataInputStream(bufStream); + int versionCode = in.readInt(); + + Signature[] sigs = unflattenSignatureArray(in); String pkg = data.getKey(); // !!! TODO: take out this debugging if (DEBUG) { - CRC32 crc = new CRC32(); - crc.update(buf); - Log.i(TAG, "- unflat sig array for " + pkg + " : " - + crc.getValue()); + Log.i(TAG, "+ restored metadata for " + pkg + + " versionCode=" + versionCode + " sigs=" + sigs); } ApplicationInfo app = new ApplicationInfo(); app.packageName = pkg; restoredApps.add(app); - sigMap.put(pkg, sigs); + sigMap.put(pkg, new Metadata(versionCode, sigs)); } mRestoredSignatures = sigMap; @@ -193,9 +220,7 @@ public class PackageManagerBackupAgent extends BackupAgent { return outBuf.toByteArray(); } - private Signature[] unflattenSignatureArray(byte[] buffer) { - ByteArrayInputStream inBufStream = new ByteArrayInputStream(buffer); - DataInputStream in = new DataInputStream(inBufStream); + private Signature[] unflattenSignatureArray(/*byte[] buffer*/ DataInputStream in) { Signature[] sigs = null; try {