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:
@ -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) {
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
Reference in New Issue
Block a user