Add some global metadata to the restore set
In addition to the signatures of each participating application, we now also store the versionCode of each backed-up package, plus the OS version running on the device that contributed the backup set. We also refuse to process a backup from a later OS revision to an earlier one, or from a later app version to an earlier. LocalTransport has been modified as well to be more resilient to changes in the system's use of metadata pseudopackages.
This commit is contained in:
@@ -170,18 +170,13 @@ public class LocalTransport extends IBackupTransport.Stub {
|
||||
// The restore set is the concatenation of the individual record blobs,
|
||||
// each of which is a file in the package's directory
|
||||
File[] blobs = packageDir.listFiles();
|
||||
if (DEBUG) Log.v(TAG, " found " + blobs.length + " key files");
|
||||
int err = 0;
|
||||
if (blobs != null && blobs.length > 0) {
|
||||
BackupDataOutput out = new BackupDataOutput(outFd.getFileDescriptor());
|
||||
try {
|
||||
for (File f : blobs) {
|
||||
FileInputStream in = new FileInputStream(f);
|
||||
int size = (int) f.length();
|
||||
byte[] buf = new byte[size];
|
||||
in.read(buf);
|
||||
String key = new String(Base64.decode(f.getName()));
|
||||
out.writeEntityHeader(key, size);
|
||||
out.writeEntityData(buf, size);
|
||||
copyToRestoreData(f, out);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Unable to read backup records");
|
||||
@@ -190,4 +185,16 @@ public class LocalTransport extends IBackupTransport.Stub {
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
private void copyToRestoreData(File f, BackupDataOutput out) throws IOException {
|
||||
FileInputStream in = new FileInputStream(f);
|
||||
int size = (int) f.length();
|
||||
byte[] buf = new byte[size];
|
||||
in.read(buf);
|
||||
String key = new String(Base64.decode(f.getName()));
|
||||
if (DEBUG) Log.v(TAG, " ... copy to stream: key=" + key
|
||||
+ " size=" + size);
|
||||
out.writeEntityHeader(key, size);
|
||||
out.writeEntityData(buf, size);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ import android.content.pm.PackageManager;
|
||||
import android.content.pm.Signature;
|
||||
import android.net.Uri;
|
||||
import android.os.Binder;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.os.Handler;
|
||||
@@ -850,18 +851,27 @@ class BackupManagerService extends IBackupManager.Stub {
|
||||
if (app != null) {
|
||||
// Validate against the backed-up signature block, too
|
||||
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);
|
||||
if (info != null) {
|
||||
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 restoring " + app.packageName);
|
||||
Log.i(TAG, "Restore set for " + app.packageName
|
||||
+ " is too new [" + info.versionCode
|
||||
+ "] for installed app version "
|
||||
+ app.versionCode);
|
||||
}
|
||||
} else {
|
||||
Log.i(TAG, "Restore set for " + app.packageName
|
||||
+ " is too new [" + info.versionCode
|
||||
+ "] for installed app version " + app.versionCode);
|
||||
Log.d(TAG, "Unable to get metadata for "
|
||||
+ app.packageName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.content.pm.Signature;
|
||||
import android.os.Build;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.util.Log;
|
||||
|
||||
@@ -52,6 +53,10 @@ public class PackageManagerBackupAgent extends BackupAgent {
|
||||
private static final String TAG = "PMBA";
|
||||
private static final boolean DEBUG = true;
|
||||
|
||||
// key under which we store global metadata (individual app metadata
|
||||
// is stored using the package name as a key)
|
||||
private static final String GLOBAL_METADATA_KEY = "@meta@";
|
||||
|
||||
private List<ApplicationInfo> mAllApps;
|
||||
private PackageManager mPackageManager;
|
||||
private HashMap<String, Metadata> mRestoredSignatures;
|
||||
@@ -76,6 +81,7 @@ public class PackageManagerBackupAgent extends BackupAgent {
|
||||
|
||||
public Metadata getRestoredMetadata(String packageName) {
|
||||
if (mRestoredSignatures == null) {
|
||||
Log.w(TAG, "getRestoredMetadata() before metadata read!");
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -86,74 +92,97 @@ public class PackageManagerBackupAgent extends BackupAgent {
|
||||
// the package name.
|
||||
public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
|
||||
ParcelFileDescriptor newState) {
|
||||
HashSet<String> existing = parseStateFile(oldState);
|
||||
|
||||
// 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)) {
|
||||
// We haven't stored this app's signatures yet, so we do that now
|
||||
try {
|
||||
PackageInfo info = mPackageManager.getPackageInfo(packName,
|
||||
PackageManager.GET_SIGNATURES);
|
||||
/*
|
||||
* Metadata for each package:
|
||||
*
|
||||
* int version -- [4] the package's versionCode
|
||||
* byte[] signatures -- [len] flattened Signature[] of the package
|
||||
*/
|
||||
HashSet<String> existing = parseStateFile(oldState);
|
||||
|
||||
// marshall the version code in a canonical form
|
||||
bufStream.reset();
|
||||
outWriter.writeInt(info.versionCode);
|
||||
byte[] versionBuf = bufStream.toByteArray();
|
||||
try {
|
||||
/*
|
||||
* Global metadata:
|
||||
*
|
||||
* int version -- the SDK version of the OS itself on the device
|
||||
* that produced this backup set. Used to reject
|
||||
* backups from later OSes onto earlier ones.
|
||||
*/
|
||||
if (!existing.contains(GLOBAL_METADATA_KEY)) {
|
||||
if (DEBUG) Log.v(TAG, "Storing global metadata key");
|
||||
outWriter.writeInt(Build.VERSION.SDK_INT);
|
||||
byte[] metadata = bufStream.toByteArray();
|
||||
data.writeEntityHeader(GLOBAL_METADATA_KEY, metadata.length);
|
||||
data.writeEntityData(metadata, metadata.length);
|
||||
} else {
|
||||
if (DEBUG) Log.v(TAG, "Global metadata key already stored");
|
||||
}
|
||||
|
||||
byte[] sigs = flattenSignatureArray(info.signatures);
|
||||
// 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.
|
||||
for (ApplicationInfo app : mAllApps) {
|
||||
String packName = app.packageName;
|
||||
if (!existing.contains(packName)) {
|
||||
// We haven't stored this app's signatures yet, so we do that now
|
||||
try {
|
||||
PackageInfo info = mPackageManager.getPackageInfo(packName,
|
||||
PackageManager.GET_SIGNATURES);
|
||||
/*
|
||||
* Metadata for each package:
|
||||
*
|
||||
* int version -- [4] the package's versionCode
|
||||
* byte[] signatures -- [len] flattened Signature[] of the package
|
||||
*/
|
||||
|
||||
// !!! TODO: take out this debugging
|
||||
if (DEBUG) {
|
||||
Log.v(TAG, "+ metadata for " + packName + " version=" + info.versionCode);
|
||||
// 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
|
||||
if (DEBUG) {
|
||||
Log.v(TAG, "+ metadata for " + packName
|
||||
+ " version=" + info.versionCode
|
||||
+ " versionLen=" + versionBuf.length
|
||||
+ " sigsLen=" + 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.
|
||||
// Treat it as having been removed from the device.
|
||||
existing.add(packName);
|
||||
}
|
||||
// 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.
|
||||
// Treat it as having been removed from the device.
|
||||
existing.add(packName);
|
||||
} 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!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// At this point, the only entries in 'existing' are apps that were
|
||||
// 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) {
|
||||
// Real error writing data
|
||||
Log.e(TAG, "Unable to write package backup data file!");
|
||||
Log.e(TAG, "Unable to write package deletions!");
|
||||
return;
|
||||
}
|
||||
} 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!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// At this point, the only entries in 'existing' are apps that were
|
||||
// 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) {
|
||||
Log.e(TAG, "Unable to write package deletions!");
|
||||
return;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// Real error writing data
|
||||
Log.e(TAG, "Unable to write package backup data file!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Finally, write the new state blob -- just the list of all apps we handled
|
||||
@@ -167,30 +196,53 @@ public class PackageManagerBackupAgent extends BackupAgent {
|
||||
throws IOException {
|
||||
List<ApplicationInfo> restoredApps = new ArrayList<ApplicationInfo>();
|
||||
HashMap<String, Metadata> sigMap = new HashMap<String, Metadata>();
|
||||
if (DEBUG) Log.v(TAG, "onRestore()");
|
||||
int storedSystemVersion = -1;
|
||||
|
||||
while (data.readNextHeader()) {
|
||||
String key = data.getKey();
|
||||
int dataSize = data.getDataSize();
|
||||
byte[] buf = new byte[dataSize];
|
||||
data.readEntityData(buf, 0, dataSize);
|
||||
|
||||
ByteArrayInputStream bufStream = new ByteArrayInputStream(buf);
|
||||
DataInputStream in = new DataInputStream(bufStream);
|
||||
int versionCode = in.readInt();
|
||||
if (DEBUG) Log.v(TAG, " got key=" + key + " dataSize=" + dataSize);
|
||||
|
||||
Signature[] sigs = unflattenSignatureArray(in);
|
||||
String pkg = data.getKey();
|
||||
// !!! TODO: take out this debugging
|
||||
if (DEBUG) {
|
||||
Log.i(TAG, "+ restored metadata for " + pkg
|
||||
+ " versionCode=" + versionCode + " sigs=" + sigs);
|
||||
// generic setup to parse any entity data
|
||||
byte[] dataBuf = new byte[dataSize];
|
||||
data.readEntityData(dataBuf, 0, dataSize);
|
||||
ByteArrayInputStream baStream = new ByteArrayInputStream(dataBuf);
|
||||
DataInputStream in = new DataInputStream(baStream);
|
||||
|
||||
if (key.equals(GLOBAL_METADATA_KEY)) {
|
||||
storedSystemVersion = in.readInt();
|
||||
if (DEBUG) Log.v(TAG, " storedSystemVersion = " + storedSystemVersion);
|
||||
if (storedSystemVersion > Build.VERSION.SDK_INT) {
|
||||
// returning before setting the sig map means we rejected the restore set
|
||||
Log.w(TAG, "Restore set was from a later version of Android; not restoring");
|
||||
return;
|
||||
}
|
||||
// !!! TODO: remove this debugging output
|
||||
if (DEBUG) {
|
||||
Log.i(TAG, "Restore set version " + storedSystemVersion
|
||||
+ " is compatible with OS version " + Build.VERSION.SDK_INT);
|
||||
}
|
||||
} else {
|
||||
// it's a file metadata record
|
||||
int versionCode = in.readInt();
|
||||
Signature[] sigs = unflattenSignatureArray(in);
|
||||
// !!! TODO: take out this debugging
|
||||
if (DEBUG) {
|
||||
Log.i(TAG, " restored metadata for " + key
|
||||
+ " dataSize=" + dataSize
|
||||
+ " versionCode=" + versionCode + " sigs=" + sigs);
|
||||
}
|
||||
|
||||
ApplicationInfo app = new ApplicationInfo();
|
||||
app.packageName = key;
|
||||
restoredApps.add(app);
|
||||
sigMap.put(key, new Metadata(versionCode, sigs));
|
||||
}
|
||||
|
||||
ApplicationInfo app = new ApplicationInfo();
|
||||
app.packageName = pkg;
|
||||
restoredApps.add(app);
|
||||
sigMap.put(pkg, new Metadata(versionCode, sigs));
|
||||
}
|
||||
|
||||
// On successful completion, cache the signature map for the Backup Manager to use
|
||||
mRestoredSignatures = sigMap;
|
||||
}
|
||||
|
||||
@@ -225,6 +277,7 @@ public class PackageManagerBackupAgent extends BackupAgent {
|
||||
|
||||
try {
|
||||
int num = in.readInt();
|
||||
Log.v(TAG, " ... unflatten read " + num);
|
||||
sigs = new Signature[num];
|
||||
for (int i = 0; i < num; i++) {
|
||||
int len = in.readInt();
|
||||
@@ -273,20 +326,26 @@ public class PackageManagerBackupAgent extends BackupAgent {
|
||||
return set;
|
||||
}
|
||||
|
||||
// Util: write a set of names into a new state file
|
||||
// Util: write out our new backup state file
|
||||
private void writeStateFile(List<ApplicationInfo> apps, ParcelFileDescriptor stateFile) {
|
||||
FileOutputStream outstream = new FileOutputStream(stateFile.getFileDescriptor());
|
||||
DataOutputStream out = new DataOutputStream(outstream);
|
||||
|
||||
for (ApplicationInfo app : apps) {
|
||||
try {
|
||||
try {
|
||||
// by the time we get here we know we've stored the global metadata record
|
||||
byte[] metaNameBuf = GLOBAL_METADATA_KEY.getBytes();
|
||||
out.writeInt(metaNameBuf.length);
|
||||
out.write(metaNameBuf);
|
||||
|
||||
// now write all the app names too
|
||||
for (ApplicationInfo app : apps) {
|
||||
byte[] pkgNameBuf = app.packageName.getBytes();
|
||||
out.writeInt(pkgNameBuf.length);
|
||||
out.write(pkgNameBuf);
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Unable to write package manager state file!");
|
||||
return;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Unable to write package manager state file!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user