Merge "Ancestral restore versioning for PackageManagerBackupAgent"

This commit is contained in:
Michal Karpinski
2018-01-05 22:46:08 +00:00
committed by Android (Google) Code Review

View File

@@ -70,12 +70,28 @@ public class PackageManagerBackupAgent extends BackupAgent {
private static final String DEFAULT_HOME_KEY = "@home@";
// Sentinel: start of state file, followed by a version number
// Note that STATE_FILE_VERSION=2 is tied to UNDEFINED_ANCESTRAL_RECORD_VERSION=-1 *as well as*
// ANCESTRAL_RECORD_VERSION=1 (introduced Android P).
// Should the ANCESTRAL_RECORD_VERSION be bumped up in the future, STATE_FILE_VERSION will also
// need bumping up, assuming more data needs saving to the state file.
private static final String STATE_FILE_HEADER = "=state=";
private static final int STATE_FILE_VERSION = 2;
// Current version of the saved ancestral-dataset file format
// key under which we store the saved ancestral-dataset format (starting from Android P)
// IMPORTANT: this key needs to come first in the restore data stream (to find out
// whether this version of Android knows how to restore the incoming data set), so it needs
// to be always the first one in alphabetical order of all the keys
private static final String ANCESTRAL_RECORD_KEY = "@ancestral_record@";
// Current version of the saved ancestral-dataset format
// Note that this constant was not used until Android P, and started being used
// to version @pm@ data for forwards-compatibility.
private static final int ANCESTRAL_RECORD_VERSION = 1;
// Undefined version of the saved ancestral-dataset file format means that the restore data
// is coming from pre-Android P device.
private static final int UNDEFINED_ANCESTRAL_RECORD_VERSION = -1;
private List<PackageInfo> mAllPackages;
private PackageManager mPackageManager;
// version & signature info of each app in a restore set
@@ -175,9 +191,8 @@ public class PackageManagerBackupAgent extends BackupAgent {
// additional involvement by the transport to obtain.
return mRestoredSignatures.keySet();
}
// The backed up data is the signature block for each app, keyed by
// the package name.
// The backed up data is the signature block for each app, keyed by the package name.
public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
ParcelFileDescriptor newState) {
if (DEBUG) Slog.v(TAG, "onBackup()");
@@ -196,6 +211,22 @@ public class PackageManagerBackupAgent extends BackupAgent {
mExisting.clear();
}
/*
* Ancestral record version:
*
* int ancestralRecordVersion -- the version of the format in which this backup set is
* produced
*/
try {
if (DEBUG) Slog.v(TAG, "Storing ancestral record version key");
outputBufferStream.writeInt(ANCESTRAL_RECORD_VERSION);
writeEntity(data, ANCESTRAL_RECORD_KEY, outputBuffer.toByteArray());
} catch (IOException e) {
// Real error writing data
Slog.e(TAG, "Unable to write package backup data file!");
return;
}
long homeVersion = 0;
ArrayList<byte[]> homeSigHashes = null;
PackageInfo homeInfo = null;
@@ -230,6 +261,7 @@ public class PackageManagerBackupAgent extends BackupAgent {
Slog.i(TAG, "Home preference changed; backing up new state " + home);
}
if (home != null) {
outputBuffer.reset();
outputBufferStream.writeUTF(home.flattenToString());
outputBufferStream.writeLong(homeVersion);
outputBufferStream.writeUTF(homeInstaller != null ? homeInstaller : "" );
@@ -244,8 +276,8 @@ public class PackageManagerBackupAgent extends BackupAgent {
* Global metadata:
*
* int SDKversion -- 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.
* that produced this backup set. Before Android P it was used to
* reject backups from later OSes onto earlier ones.
* String incremental -- the incremental release name of the OS stored in
* the backup set.
*/
@@ -354,7 +386,7 @@ public class PackageManagerBackupAgent extends BackupAgent {
// Finally, write the new state blob -- just the list of all apps we handled
writeStateFile(mAllPackages, home, homeVersion, homeSigHashes, newState);
}
private static void writeEntity(BackupDataOutput data, String key, byte[] bytes)
throws IOException {
data.writeEntityHeader(key, bytes.length);
@@ -366,83 +398,57 @@ public class PackageManagerBackupAgent extends BackupAgent {
// image. We'll use those later to determine what we can legitimately restore.
public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)
throws IOException {
List<ApplicationInfo> restoredApps = new ArrayList<ApplicationInfo>();
HashMap<String, Metadata> sigMap = new HashMap<String, Metadata>();
if (DEBUG) Slog.v(TAG, "onRestore()");
int storedSystemVersion = -1;
while (data.readNextHeader()) {
// we expect the ANCESTRAL_RECORD_KEY ("@ancestral_record@") to always come first in the
// restore set - based on that value we use different mechanisms to consume the data;
// if the ANCESTRAL_RECORD_KEY is missing in the restore set, it means that the data is
// is coming from a pre-Android P device, and we consume the header data in the legacy way
// TODO: add a CTS test to verify that backups of PMBA generated on Android P+ always
// contain the ANCESTRAL_RECORD_KEY, and it's always the first key
int ancestralRecordVersion = getAncestralRecordVersionValue(data);
RestoreDataConsumer consumer = getRestoreDataConsumer(ancestralRecordVersion);
if (consumer == null) {
Slog.w(TAG, "Ancestral restore set version is unknown"
+ " to this Android version; not restoring");
return;
} else {
consumer.consumeRestoreData(data);
}
}
private int getAncestralRecordVersionValue(BackupDataInput data) throws IOException {
int ancestralRecordVersionValue = UNDEFINED_ANCESTRAL_RECORD_VERSION;
if (data.readNextHeader()) {
String key = data.getKey();
int dataSize = data.getDataSize();
if (DEBUG) Slog.v(TAG, " got key=" + key + " dataSize=" + dataSize);
// generic setup to parse any entity data
byte[] inputBytes = new byte[dataSize];
data.readEntityData(inputBytes, 0, dataSize);
ByteArrayInputStream inputBuffer = new ByteArrayInputStream(inputBytes);
DataInputStream inputBufferStream = new DataInputStream(inputBuffer);
if (ANCESTRAL_RECORD_KEY.equals(key)) {
// generic setup to parse any entity data
byte[] inputBytes = new byte[dataSize];
data.readEntityData(inputBytes, 0, dataSize);
ByteArrayInputStream inputBuffer = new ByteArrayInputStream(inputBytes);
DataInputStream inputBufferStream = new DataInputStream(inputBuffer);
if (key.equals(GLOBAL_METADATA_KEY)) {
int storedSdkVersion = inputBufferStream.readInt();
if (DEBUG) Slog.v(TAG, " storedSystemVersion = " + storedSystemVersion);
if (storedSystemVersion > Build.VERSION.SDK_INT) {
// returning before setting the sig map means we rejected the restore set
Slog.w(TAG, "Restore set was from a later version of Android; not restoring");
return;
}
mStoredSdkVersion = storedSdkVersion;
mStoredIncrementalVersion = inputBufferStream.readUTF();
mHasMetadata = true;
if (DEBUG) {
Slog.i(TAG, "Restore set version " + storedSystemVersion
+ " is compatible with OS version " + Build.VERSION.SDK_INT
+ " (" + mStoredIncrementalVersion + " vs "
+ Build.VERSION.INCREMENTAL + ")");
}
} else if (key.equals(DEFAULT_HOME_KEY)) {
String cn = inputBufferStream.readUTF();
mRestoredHome = ComponentName.unflattenFromString(cn);
mRestoredHomeVersion = inputBufferStream.readLong();
mRestoredHomeInstaller = inputBufferStream.readUTF();
mRestoredHomeSigHashes = readSignatureHashArray(inputBufferStream);
if (DEBUG) {
Slog.i(TAG, " read preferred home app " + mRestoredHome
+ " version=" + mRestoredHomeVersion
+ " installer=" + mRestoredHomeInstaller
+ " sig=" + mRestoredHomeSigHashes);
}
} else {
// it's a file metadata record
int versionCodeInt = inputBufferStream.readInt();
long versionCode;
if (versionCodeInt == Integer.MIN_VALUE) {
versionCode = inputBufferStream.readLong();
} else {
versionCode = versionCodeInt;
}
ArrayList<byte[]> sigs = readSignatureHashArray(inputBufferStream);
if (DEBUG) {
Slog.i(TAG, " read metadata for " + key
+ " dataSize=" + dataSize
+ " versionCode=" + versionCode + " sigs=" + sigs);
}
if (sigs == null || sigs.size() == 0) {
Slog.w(TAG, "Not restoring package " + key
+ " since it appears to have no signatures.");
continue;
}
ApplicationInfo app = new ApplicationInfo();
app.packageName = key;
restoredApps.add(app);
sigMap.put(key, new Metadata(versionCode, sigs));
ancestralRecordVersionValue = inputBufferStream.readInt();
}
}
return ancestralRecordVersionValue;
}
// On successful completion, cache the signature map for the Backup Manager to use
mRestoredSignatures = sigMap;
private RestoreDataConsumer getRestoreDataConsumer(int ancestralRecordVersion) {
switch (ancestralRecordVersion) {
case UNDEFINED_ANCESTRAL_RECORD_VERSION:
return new LegacyRestoreDataConsumer();
case 1:
return new AncestralVersion1RestoreDataConsumer();
default:
Slog.e(TAG, "Unrecognized ANCESTRAL_RECORD_VERSION: " + ancestralRecordVersion);
return null;
}
}
private static void writeSignatureHashArray(DataOutputStream out, ArrayList<byte[]> hashes)
@@ -639,4 +645,173 @@ public class PackageManagerBackupAgent extends BackupAgent {
Slog.e(TAG, "Unable to write package manager state file!");
}
}
interface RestoreDataConsumer {
void consumeRestoreData(BackupDataInput data) throws IOException;
}
private class LegacyRestoreDataConsumer implements RestoreDataConsumer {
public void consumeRestoreData(BackupDataInput data) throws IOException {
List<ApplicationInfo> restoredApps = new ArrayList<ApplicationInfo>();
HashMap<String, Metadata> sigMap = new HashMap<String, Metadata>();
int storedSystemVersion = -1;
if (DEBUG) Slog.i(TAG, "Using LegacyRestoreDataConsumer");
// we already have the first header read and "cached", since ANCESTRAL_RECORD_KEY
// was missing
while (true) {
String key = data.getKey();
int dataSize = data.getDataSize();
if (DEBUG) Slog.v(TAG, " got key=" + key + " dataSize=" + dataSize);
// generic setup to parse any entity data
byte[] inputBytes = new byte[dataSize];
data.readEntityData(inputBytes, 0, dataSize);
ByteArrayInputStream inputBuffer = new ByteArrayInputStream(inputBytes);
DataInputStream inputBufferStream = new DataInputStream(inputBuffer);
if (key.equals(GLOBAL_METADATA_KEY)) {
int storedSdkVersion = inputBufferStream.readInt();
if (DEBUG) Slog.v(TAG, " storedSystemVersion = " + storedSystemVersion);
mStoredSdkVersion = storedSdkVersion;
mStoredIncrementalVersion = inputBufferStream.readUTF();
mHasMetadata = true;
if (DEBUG) {
Slog.i(TAG, "Restore set version " + storedSystemVersion
+ " is compatible with OS version " + Build.VERSION.SDK_INT
+ " (" + mStoredIncrementalVersion + " vs "
+ Build.VERSION.INCREMENTAL + ")");
}
} else if (key.equals(DEFAULT_HOME_KEY)) {
String cn = inputBufferStream.readUTF();
mRestoredHome = ComponentName.unflattenFromString(cn);
mRestoredHomeVersion = inputBufferStream.readLong();
mRestoredHomeInstaller = inputBufferStream.readUTF();
mRestoredHomeSigHashes = readSignatureHashArray(inputBufferStream);
if (DEBUG) {
Slog.i(TAG, " read preferred home app " + mRestoredHome
+ " version=" + mRestoredHomeVersion
+ " installer=" + mRestoredHomeInstaller
+ " sig=" + mRestoredHomeSigHashes);
}
} else {
// it's a file metadata record
int versionCodeInt = inputBufferStream.readInt();
long versionCode;
if (versionCodeInt == Integer.MIN_VALUE) {
versionCode = inputBufferStream.readLong();
} else {
versionCode = versionCodeInt;
}
ArrayList<byte[]> sigs = readSignatureHashArray(inputBufferStream);
if (DEBUG) {
Slog.i(TAG, " read metadata for " + key
+ " dataSize=" + dataSize
+ " versionCode=" + versionCode + " sigs=" + sigs);
}
if (sigs == null || sigs.size() == 0) {
Slog.w(TAG, "Not restoring package " + key
+ " since it appears to have no signatures.");
continue;
}
ApplicationInfo app = new ApplicationInfo();
app.packageName = key;
restoredApps.add(app);
sigMap.put(key, new Metadata(versionCode, sigs));
}
boolean readNextHeader = data.readNextHeader();
if (!readNextHeader) {
if (DEBUG) Slog.v(TAG, "LegacyRestoreDataConsumer:"
+ " we're done reading all the headers");
break;
}
}
// On successful completion, cache the signature map for the Backup Manager to use
mRestoredSignatures = sigMap;
}
}
private class AncestralVersion1RestoreDataConsumer implements RestoreDataConsumer {
public void consumeRestoreData(BackupDataInput data) throws IOException {
List<ApplicationInfo> restoredApps = new ArrayList<ApplicationInfo>();
HashMap<String, Metadata> sigMap = new HashMap<String, Metadata>();
int storedSystemVersion = -1;
if (DEBUG) Slog.i(TAG, "Using AncestralVersion1RestoreDataConsumer");
while (data.readNextHeader()) {
String key = data.getKey();
int dataSize = data.getDataSize();
if (DEBUG) Slog.v(TAG, " got key=" + key + " dataSize=" + dataSize);
// generic setup to parse any entity data
byte[] inputBytes = new byte[dataSize];
data.readEntityData(inputBytes, 0, dataSize);
ByteArrayInputStream inputBuffer = new ByteArrayInputStream(inputBytes);
DataInputStream inputBufferStream = new DataInputStream(inputBuffer);
if (key.equals(GLOBAL_METADATA_KEY)) {
int storedSdkVersion = inputBufferStream.readInt();
if (DEBUG) Slog.v(TAG, " storedSystemVersion = " + storedSystemVersion);
mStoredSdkVersion = storedSdkVersion;
mStoredIncrementalVersion = inputBufferStream.readUTF();
mHasMetadata = true;
if (DEBUG) {
Slog.i(TAG, "Restore set version " + storedSystemVersion
+ " is compatible with OS version " + Build.VERSION.SDK_INT
+ " (" + mStoredIncrementalVersion + " vs "
+ Build.VERSION.INCREMENTAL + ")");
}
} else if (key.equals(DEFAULT_HOME_KEY)) {
String cn = inputBufferStream.readUTF();
mRestoredHome = ComponentName.unflattenFromString(cn);
mRestoredHomeVersion = inputBufferStream.readLong();
mRestoredHomeInstaller = inputBufferStream.readUTF();
mRestoredHomeSigHashes = readSignatureHashArray(inputBufferStream);
if (DEBUG) {
Slog.i(TAG, " read preferred home app " + mRestoredHome
+ " version=" + mRestoredHomeVersion
+ " installer=" + mRestoredHomeInstaller
+ " sig=" + mRestoredHomeSigHashes);
}
} else {
// it's a file metadata record
int versionCodeInt = inputBufferStream.readInt();
long versionCode;
if (versionCodeInt == Integer.MIN_VALUE) {
versionCode = inputBufferStream.readLong();
} else {
versionCode = versionCodeInt;
}
ArrayList<byte[]> sigs = readSignatureHashArray(inputBufferStream);
if (DEBUG) {
Slog.i(TAG, " read metadata for " + key
+ " dataSize=" + dataSize
+ " versionCode=" + versionCode + " sigs=" + sigs);
}
if (sigs == null || sigs.size() == 0) {
Slog.w(TAG, "Not restoring package " + key
+ " since it appears to have no signatures.");
continue;
}
ApplicationInfo app = new ApplicationInfo();
app.packageName = key;
restoredApps.add(app);
sigMap.put(key, new Metadata(versionCode, sigs));
}
}
// On successful completion, cache the signature map for the Backup Manager to use
mRestoredSignatures = sigMap;
}
}
}