Merge change 4682 into donut

* changes:
  Store the app signatures as part of the backup set
This commit is contained in:
Android (Google) Code Review
2009-06-18 16:15:47 -07:00
3 changed files with 322 additions and 7 deletions

View File

@@ -36,6 +36,7 @@ public class BackupDataOutput {
}
}
// A dataSize of -1 indicates that the record under this key should be deleted
public int writeEntityHeader(String key, int dataSize) throws IOException {
int result = writeEntityHeader_native(mBackupWriter, key, dataSize);
if (result >= 0) {

View File

@@ -52,6 +52,8 @@ import android.backup.RestoreSet;
import com.android.internal.backup.LocalTransport;
import com.android.internal.backup.IBackupTransport;
import com.android.server.PackageManagerBackupAgent;
import java.io.EOFException;
import java.io.File;
import java.io.FileDescriptor;
@@ -70,6 +72,7 @@ class BackupManagerService extends IBackupManager.Stub {
private static final String TAG = "BackupManagerService";
private static final boolean DEBUG = true;
// Default time to wait after data changes before we back up the data
private static final long COLLECTION_INTERVAL = 1000;
//private static final long COLLECTION_INTERVAL = 3 * 60 * 1000;
@@ -104,6 +107,9 @@ class BackupManagerService extends IBackupManager.Stub {
// Backups that we haven't started yet.
private HashMap<ApplicationInfo,BackupRequest> mPendingBackups
= new HashMap<ApplicationInfo,BackupRequest>();
// 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<BackupRequest> mBackupQueue;
@@ -363,12 +369,13 @@ class BackupManagerService extends IBackupManager.Stub {
mBackupParticipants.put(uid, set);
}
set.add(app);
backUpPackageManagerData();
}
}
}
// Remove the given package's backup services from our known active set. If
// 'packageName' is null, *all* backup services will be removed.
// Remove the given package's entry from our known active set. If
// 'packageName' is null, *all* participating apps will be removed.
void removePackageParticipantsLocked(String packageName) {
if (DEBUG) Log.v(TAG, "removePackageParticipantsLocked: " + packageName);
List<ApplicationInfo> allApps = null;
@@ -406,6 +413,7 @@ class BackupManagerService extends IBackupManager.Stub {
for (ApplicationInfo entry: set) {
if (entry.packageName.equals(app.packageName)) {
set.remove(entry);
backUpPackageManagerData();
break;
}
}
@@ -418,6 +426,7 @@ class BackupManagerService extends IBackupManager.Stub {
// Returns the set of all applications that define an android:backupAgent attribute
private List<ApplicationInfo> allAgentApps() {
// !!! TODO: cache this and regenerate only when necessary
List<ApplicationInfo> allApps = mPackageManager.getInstalledApplications(0);
int N = allApps.size();
if (N > 0) {
@@ -447,13 +456,29 @@ class BackupManagerService extends IBackupManager.Stub {
addPackageParticipantsLockedInner(packageName, allApps);
}
private void backUpPackageManagerData() {
// No need to schedule a backup just for the metadata; just piggyback on
// the next actual data backup.
synchronized(this) {
mDoPackageManager = true;
}
}
// The queue lock should be held when scheduling a backup pass
private void scheduleBackupPassLocked(long timeFromNowMillis) {
mBackupHandler.removeMessages(MSG_RUN_BACKUP);
mBackupHandler.sendEmptyMessageDelayed(MSG_RUN_BACKUP, timeFromNowMillis);
}
// Return the given transport
private IBackupTransport getTransport(int transportID) {
switch (transportID) {
case BackupManager.TRANSPORT_LOCAL:
Log.v(TAG, "Supplying local transport");
return mLocalTransport;
case BackupManager.TRANSPORT_GOOGLE:
Log.v(TAG, "Supplying Google transport");
return mGoogleTransport;
default:
@@ -558,7 +583,29 @@ class BackupManagerService extends IBackupManager.Stub {
return;
}
// The transport is up and running; now run all the backups in our queue
// The transport is up and running. First, back up the package manager
// metadata if necessary
boolean doPackageManager;
synchronized (BackupManagerService.this) {
doPackageManager = mDoPackageManager;
mDoPackageManager = false;
}
if (doPackageManager) {
// The package manager doesn't have a proper <application> etc, but since
// it's running here in the system process we can just set up its agent
// directly and use a synthetic BackupRequest.
if (DEBUG) Log.i(TAG, "Running PM backup pass as well");
PackageManagerBackupAgent pmAgent = new PackageManagerBackupAgent(
mPackageManager, allAgentApps());
BackupRequest pmRequest = new BackupRequest(new ApplicationInfo(), false);
pmRequest.appInfo.packageName = PACKAGE_MANAGER_SENTINEL;
processOneBackup(pmRequest,
IBackupAgent.Stub.asInterface(pmAgent.onBind()),
mTransport);
}
// Now run all the backups in our queue
doQueuedBackups(mTransport);
// Finally, tear down the transport
@@ -735,6 +782,8 @@ class BackupManagerService extends IBackupManager.Stub {
}
if (err == 0) {
// !!! TODO: do the package manager signatures restore first
// build the set of apps to restore
try {
RestoreSet[] images = mTransport.getAvailableRestoreSets();
@@ -920,8 +969,7 @@ class BackupManagerService extends IBackupManager.Stub {
// Schedule a backup pass in a few minutes. As backup-eligible data
// keeps changing, continue to defer the backup pass until things
// settle down, to avoid extra overhead.
mBackupHandler.removeMessages(MSG_RUN_BACKUP);
mBackupHandler.sendEmptyMessageDelayed(MSG_RUN_BACKUP, COLLECTION_INTERVAL);
scheduleBackupPassLocked(COLLECTION_INTERVAL);
}
} else {
Log.w(TAG, "dataChanged but no participant pkg " + packageName);
@@ -947,8 +995,7 @@ class BackupManagerService extends IBackupManager.Stub {
if (DEBUG) Log.v(TAG, "Scheduling immediate backup pass");
synchronized (mQueueLock) {
mBackupHandler.removeMessages(MSG_RUN_BACKUP);
mBackupHandler.sendEmptyMessage(MSG_RUN_BACKUP);
scheduleBackupPassLocked(0);
}
}

View File

@@ -0,0 +1,267 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server;
import android.app.BackupAgent;
import android.backup.BackupDataInput;
import android.backup.BackupDataOutput;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.Signature;
import android.os.ParcelFileDescriptor;
import android.util.Log;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
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
* actually resident on the device.
*
* Since the Package Manager isn't a proper "application" we just provide a
* direct IBackupAgent implementation and hand-construct it at need.
*/
public class PackageManagerBackupAgent extends BackupAgent {
private static final String TAG = "PMBA";
private static final boolean DEBUG = true;
private List<ApplicationInfo> mAllApps;
private PackageManager mPackageManager;
private HashMap<String, Signature[]> mRestoredSignatures;
// We're constructed with the set of applications that are participating
// in backup. This set changes as apps are installed & removed.
PackageManagerBackupAgent(PackageManager packageMgr, List<ApplicationInfo> apps) {
mPackageManager = packageMgr;
mAllApps = apps;
mRestoredSignatures = null;
}
public Signature[] getRestoredSignatures(String packageName) {
if (mRestoredSignatures == null) {
return null;
}
return mRestoredSignatures.get(packageName);
}
// 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) {
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.
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);
// build a byte array out of the signature list
byte[] sigs = flattenSignatureArray(info.signatures);
// !!! TODO: take out this debugging
if (DEBUG) {
CRC32 crc = new CRC32();
crc.update(sigs);
Log.i(TAG, "+ flat sig array for " + packName + " : "
+ crc.getValue());
}
data.writeEntityHeader(packName, sigs.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);
} catch (IOException e) {
// Real error writing data
Log.e(TAG, "Unable to write package backup data file!");
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.
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) {
try {
data.writeEntityHeader(app, -1);
} catch (IOException e) {
Log.e(TAG, "Unable to write package deletions!");
return;
}
}
// Finally, write the new state blob -- just the list of all apps we handled
writeStateFile(mAllApps, newState);
}
// "Restore" here is a misnomer. What we're really doing is reading back the
// set of app signatures associated with each backed-up app in this restore
// image. We'll use those later to determine what we can legitimately restore.
public void onRestore(BackupDataInput data, ParcelFileDescriptor newState)
throws IOException {
List<ApplicationInfo> restoredApps = new ArrayList<ApplicationInfo>();
HashMap<String, Signature[]> sigMap = new HashMap<String, Signature[]>();
while (data.readNextHeader()) {
int dataSize = data.getDataSize();
byte[] buf = new byte[dataSize];
data.readEntityData(buf, 0, dataSize);
Signature[] sigs = unflattenSignatureArray(buf);
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());
}
ApplicationInfo app = new ApplicationInfo();
app.packageName = pkg;
restoredApps.add(app);
sigMap.put(pkg, sigs);
}
mRestoredSignatures = sigMap;
}
// Util: convert an array of Signatures into a flattened byte buffer. The
// flattened format contains enough info to reconstruct the signature array.
private byte[] flattenSignatureArray(Signature[] allSigs) {
ByteArrayOutputStream outBuf = new ByteArrayOutputStream();
DataOutputStream out = new DataOutputStream(outBuf);
// build the set of subsidiary buffers
try {
// first the # of signatures in the array
out.writeInt(allSigs.length);
// then the signatures themselves, length + flattened buffer
for (Signature sig : allSigs) {
byte[] flat = sig.toByteArray();
out.writeInt(flat.length);
out.write(flat);
}
} catch (IOException e) {
// very strange; we're writing to memory here. abort.
return null;
}
return outBuf.toByteArray();
}
private Signature[] unflattenSignatureArray(byte[] buffer) {
ByteArrayInputStream inBufStream = new ByteArrayInputStream(buffer);
DataInputStream in = new DataInputStream(inBufStream);
Signature[] sigs = null;
try {
int num = in.readInt();
sigs = new Signature[num];
for (int i = 0; i < num; i++) {
int len = in.readInt();
byte[] flatSig = new byte[len];
in.read(flatSig);
sigs[i] = new Signature(flatSig);
}
} catch (EOFException e) {
// clean termination
if (sigs == null) {
Log.w(TAG, "Empty signature block found");
}
} catch (IOException e) {
Log.d(TAG, "Unable to unflatten sigs");
return null;
}
return sigs;
}
// Util: parse out an existing state file into a usable structure
private HashSet<String> parseStateFile(ParcelFileDescriptor stateFile) {
HashSet<String> set = new HashSet<String>();
// The state file is just the list of app names we have stored signatures for
FileInputStream instream = new FileInputStream(stateFile.getFileDescriptor());
DataInputStream in = new DataInputStream(instream);
int bufSize = 256;
byte[] buf = new byte[bufSize];
try {
int nameSize = in.readInt();
if (bufSize < nameSize) {
bufSize = nameSize + 32;
buf = new byte[bufSize];
}
in.read(buf, 0, nameSize);
String pkg = new String(buf, 0, nameSize);
set.add(pkg);
} catch (EOFException eof) {
// safe; we're done
} catch (IOException e) {
// whoops, bad state file. abort.
Log.e(TAG, "Unable to read Package Manager state file");
return null;
}
return set;
}
// Util: write a set of names into a new 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 {
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;
}
}
}
}