Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 07eac016 authored by Android (Google) Code Review's avatar Android (Google) Code Review
Browse files

Merge change 4682 into donut

* changes:
  Store the app signatures as part of the backup set
parents c9dc19bb 6785dd84
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -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) {
+54 −7
Original line number Diff line number Diff line
@@ -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);
        }
    }

+267 −0
Original line number Diff line number Diff line
/*
 * 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;
            }
        }
    }
}