Fix some backup reader/writer issues; make local transport do backup

As of this change, LocalTransport is successfully propagating data changes from
the backup data format into a repository stored in /cache/backup/[packagename].
Each backup key gets a separate file there for ease of manipulation and testing.

The general semantics of BackupDataReader have been tweaked, too; it now just
returns simple "we're done with the data" when it hits the end, even if no
footer has been found, because on the writing side the footer isn't being
written.  Also, reading an entity now merely requires a "big enough" buffer, not
an exactly-sized one.

This is all a work in progress, but this is at least working now for purposes of
this local transport.

Still to do: proper change vs deletion detection, as well as expanding the data
format itself to include necessary metadata etc.
This commit is contained in:
Christopher Tate
2009-06-12 15:20:04 -07:00
parent 31390c7528
commit 2fdd428e0f
3 changed files with 114 additions and 55 deletions

View File

@ -1,5 +1,6 @@
package com.android.internal.backup;
import android.backup.BackupDataInput;
import android.backup.RestoreSet;
import android.content.Context;
import android.content.pm.PackageInfo;
@ -24,7 +25,7 @@ import java.util.ArrayList;
public class LocalTransport extends IBackupTransport.Stub {
private static final String TAG = "LocalTransport";
private static final String DATA_FILE_NAME = "data";
private static final boolean DEBUG = true;
private Context mContext;
private PackageManager mPackageManager;
@ -37,6 +38,7 @@ public class LocalTransport extends IBackupTransport.Stub {
public LocalTransport(Context context) {
if (DEBUG) Log.v(TAG, "Transport constructed");
mContext = context;
mPackageManager = context.getPackageManager();
}
@ -47,29 +49,63 @@ public class LocalTransport extends IBackupTransport.Stub {
}
public int startSession() throws RemoteException {
if (DEBUG) Log.v(TAG, "session started");
mDataDir.mkdirs();
return 0;
}
public int endSession() throws RemoteException {
if (DEBUG) Log.v(TAG, "session ended");
return 0;
}
public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor data)
throws RemoteException {
if (DEBUG) Log.v(TAG, "performBackup() pkg=" + packageInfo.packageName);
int err = 0;
File packageDir = new File(mDataDir, packageInfo.packageName);
File imageFileName = new File(packageDir, DATA_FILE_NAME);
packageDir.mkdirs();
//!!! TODO: process the (partial) update into the persistent restore set:
// Parse out the existing image file into the key/value map
// Each 'record' in the restore set is kept in its own file, named by
// the record key. Wind through the data file, extracting individual
// record operations and building a set of all the updates to apply
// in this update.
BackupDataInput changeSet = new BackupDataInput(data.getFileDescriptor());
try {
int bufSize = 512;
byte[] buf = new byte[bufSize];
while (changeSet.readNextHeader()) {
String key = changeSet.getKey();
int dataSize = changeSet.getDataSize();
if (DEBUG) Log.v(TAG, "Got change set key=" + key + " size=" + dataSize);
if (dataSize > bufSize) {
bufSize = dataSize;
buf = new byte[bufSize];
}
changeSet.readEntityData(buf, dataSize);
if (DEBUG) Log.v(TAG, " + data size " + dataSize);
// Parse out the backup data into the key/value updates
File entityFile = new File(packageDir, key);
FileOutputStream entity = new FileOutputStream(entityFile);
try {
entity.write(buf, 0, dataSize);
} catch (IOException e) {
Log.e(TAG, "Unable to update key file "
+ entityFile.getAbsolutePath());
err = -1;
} finally {
entity.close();
}
}
} catch (IOException e) {
// oops, something went wrong. abort the operation and return error.
Log.v(TAG, "Exception reading backup input:");
e.printStackTrace();
err = -1;
}
// Apply the backup key/value updates to the image
// Write out the image in the canonical format
return -1;
return err;
}
// Restore handling
@ -83,6 +119,7 @@ public class LocalTransport extends IBackupTransport.Stub {
}
public PackageInfo[] getAppSet(int token) throws android.os.RemoteException {
if (DEBUG) Log.v(TAG, "getting app set " + token);
// the available packages are the extant subdirs of mDatadir
File[] packageDirs = mDataDir.listFiles(mDirFileFilter);
ArrayList<PackageInfo> packages = new ArrayList<PackageInfo>();
@ -99,9 +136,11 @@ public class LocalTransport extends IBackupTransport.Stub {
}
}
Log.v(TAG, "Built app set of " + packages.size() + " entries:");
for (PackageInfo p : packages) {
Log.v(TAG, " + " + p.packageName);
if (DEBUG) {
Log.v(TAG, "Built app set of " + packages.size() + " entries:");
for (PackageInfo p : packages) {
Log.v(TAG, " + " + p.packageName);
}
}
PackageInfo[] result = new PackageInfo[packages.size()];
@ -110,16 +149,25 @@ public class LocalTransport extends IBackupTransport.Stub {
public int getRestoreData(int token, PackageInfo packageInfo, ParcelFileDescriptor output)
throws android.os.RemoteException {
if (DEBUG) Log.v(TAG, "getting restore data " + token + " : " + packageInfo.packageName);
// we only support one hardcoded restore set
if (token != 0) return -1;
// the data for a given package is at a known location
File packageDir = new File(mDataDir, packageInfo.packageName);
File imageFile = new File(packageDir, DATA_FILE_NAME);
// restore is relatively easy: we already maintain the full data set in
// the canonical form understandable to the BackupAgent
return copyFileToFD(imageFile, output);
// 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();
int err = 0;
if (blobs != null && blobs.length > 0) {
for (File f : blobs) {
err = copyFileToFD(f, output);
if (err != 0) break;
}
}
return err;
}
private int copyFileToFD(File source, ParcelFileDescriptor dest) {

View File

@ -62,43 +62,54 @@ readNextHeader_native(JNIEnv* env, jobject clazz, int r, jobject entity)
return err < 0 ? err : -1;
}
while (reader->HasEntities()) {
int type;
int type = 0;
err = reader->ReadNextHeader(&type);
err = reader->ReadNextHeader(&type);
if (err == EIO) {
// Clean EOF with no footer block; just claim we're done
return 1;
}
if (err != 0) {
return err < 0 ? err : -1;
}
switch (type) {
case BACKUP_HEADER_APP_V1:
{
String8 packageName;
int cookie;
err = reader->ReadAppHeader(&packageName, &cookie);
if (err != 0) {
LOGD("ReadAppHeader() returned %d; aborting", err);
return err < 0 ? err : -1;
}
switch (type) {
case BACKUP_HEADER_APP_V1:
{
String8 packageName;
int cookie;
err = reader->ReadAppHeader(&packageName, &cookie);
if (err != 0) {
return err < 0 ? err : -1;
}
break;
}
case BACKUP_HEADER_ENTITY_V1:
{
String8 key;
size_t dataSize;
err = reader->ReadEntityHeader(&key, &dataSize);
if (err != 0) {
return err < 0 ? err : -1;
}
// TODO: Set the fields in the entity object
return 0;
}
case BACKUP_FOOTER_APP_V1:
break;
default:
LOGD("Unknown header type: 0x%08x\n", type);
return -1;
}
break;
}
case BACKUP_HEADER_ENTITY_V1:
{
String8 key;
size_t dataSize;
err = reader->ReadEntityHeader(&key, &dataSize);
if (err != 0) {
LOGD("ReadEntityHeader(); aborting", err);
return err < 0 ? err : -1;
}
// TODO: Set the fields in the entity object
jstring keyStr = env->NewStringUTF(key.string());
env->SetObjectField(entity, s_keyField, keyStr);
env->SetIntField(entity, s_dataSizeField, dataSize);
return 0;
}
case BACKUP_FOOTER_APP_V1:
{
break;
}
default:
LOGD("Unknown header type: 0x%08x\n", type);
return -1;
}
// done
return 1;
}
@ -109,17 +120,17 @@ readEntityData_native(JNIEnv* env, jobject clazz, int r, jbyteArray data, int si
int err;
BackupDataReader* reader = (BackupDataReader*)r;
if (env->GetArrayLength(data) > size) {
if (env->GetArrayLength(data) < size) {
// size mismatch
return -1;
}
jbyte* dataBytes = env->GetByteArrayElements(data, NULL);
if (dataBytes == NULL) {
return -1;
return -2;
}
err = reader->ReadEntityData(dataBytes, size);
err = reader->ReadEntityData(dataBytes, size);
env->ReleaseByteArrayElements(data, dataBytes, 0);
@ -136,7 +147,7 @@ static const JNINativeMethod g_methods[] = {
int register_android_backup_BackupDataInput(JNIEnv* env)
{
LOGD("register_android_backup_BackupDataInput");
//LOGD("register_android_backup_BackupDataInput");
jclass clazz;

View File

@ -96,7 +96,7 @@ static const JNINativeMethod g_methods[] = {
int register_android_backup_BackupDataOutput(JNIEnv* env)
{
LOGD("register_android_backup_BackupDataOutput");
//LOGD("register_android_backup_BackupDataOutput");
jclass clazz;