Home | History | Annotate | Download | only in server
      1 /*
      2  * Copyright (C) 2009 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.server;
     18 
     19 import android.app.backup.BackupAgent;
     20 import android.app.backup.BackupDataInput;
     21 import android.app.backup.BackupDataOutput;
     22 import android.content.pm.ApplicationInfo;
     23 import android.content.pm.PackageInfo;
     24 import android.content.pm.PackageManager;
     25 import android.content.pm.PackageManager.NameNotFoundException;
     26 import android.content.pm.Signature;
     27 import android.os.Build;
     28 import android.os.ParcelFileDescriptor;
     29 import android.util.Slog;
     30 
     31 import java.io.ByteArrayInputStream;
     32 import java.io.ByteArrayOutputStream;
     33 import java.io.DataInputStream;
     34 import java.io.DataOutputStream;
     35 import java.io.EOFException;
     36 import java.io.FileInputStream;
     37 import java.io.FileOutputStream;
     38 import java.io.IOException;
     39 import java.util.ArrayList;
     40 import java.util.HashMap;
     41 import java.util.HashSet;
     42 import java.util.List;
     43 import java.util.Set;
     44 
     45 /**
     46  * We back up the signatures of each package so that during a system restore,
     47  * we can verify that the app whose data we think we have matches the app
     48  * actually resident on the device.
     49  *
     50  * Since the Package Manager isn't a proper "application" we just provide a
     51  * direct IBackupAgent implementation and hand-construct it at need.
     52  */
     53 public class PackageManagerBackupAgent extends BackupAgent {
     54     private static final String TAG = "PMBA";
     55     private static final boolean DEBUG = false;
     56 
     57     // key under which we store global metadata (individual app metadata
     58     // is stored using the package name as a key)
     59     private static final String GLOBAL_METADATA_KEY = "@meta@";
     60 
     61     private List<PackageInfo> mAllPackages;
     62     private PackageManager mPackageManager;
     63     // version & signature info of each app in a restore set
     64     private HashMap<String, Metadata> mRestoredSignatures;
     65     // The version info of each backed-up app as read from the state file
     66     private HashMap<String, Metadata> mStateVersions = new HashMap<String, Metadata>();
     67 
     68     private final HashSet<String> mExisting = new HashSet<String>();
     69     private int mStoredSdkVersion;
     70     private String mStoredIncrementalVersion;
     71     private boolean mHasMetadata;
     72 
     73     public class Metadata {
     74         public int versionCode;
     75         public Signature[] signatures;
     76 
     77         Metadata(int version, Signature[] sigs) {
     78             versionCode = version;
     79             signatures = sigs;
     80         }
     81     }
     82 
     83     // We're constructed with the set of applications that are participating
     84     // in backup.  This set changes as apps are installed & removed.
     85     PackageManagerBackupAgent(PackageManager packageMgr, List<PackageInfo> packages) {
     86         mPackageManager = packageMgr;
     87         mAllPackages = packages;
     88         mRestoredSignatures = null;
     89         mHasMetadata = false;
     90     }
     91 
     92     public boolean hasMetadata() {
     93         return mHasMetadata;
     94     }
     95 
     96     public Metadata getRestoredMetadata(String packageName) {
     97         if (mRestoredSignatures == null) {
     98             Slog.w(TAG, "getRestoredMetadata() before metadata read!");
     99             return null;
    100         }
    101 
    102         return mRestoredSignatures.get(packageName);
    103     }
    104 
    105     public Set<String> getRestoredPackages() {
    106         if (mRestoredSignatures == null) {
    107             Slog.w(TAG, "getRestoredPackages() before metadata read!");
    108             return null;
    109         }
    110 
    111         // This is technically the set of packages on the originating handset
    112         // that had backup agents at all, not limited to the set of packages
    113         // that had actually contributed a restore dataset, but it's a
    114         // close enough approximation for our purposes and does not require any
    115         // additional involvement by the transport to obtain.
    116         return mRestoredSignatures.keySet();
    117     }
    118 
    119     // The backed up data is the signature block for each app, keyed by
    120     // the package name.
    121     public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
    122             ParcelFileDescriptor newState) {
    123         if (DEBUG) Slog.v(TAG, "onBackup()");
    124 
    125         ByteArrayOutputStream outputBuffer = new ByteArrayOutputStream();  // we'll reuse these
    126         DataOutputStream outputBufferStream = new DataOutputStream(outputBuffer);
    127         parseStateFile(oldState);
    128 
    129         // If the stored version string differs, we need to re-backup all
    130         // of the metadata.  We force this by removing everything from the
    131         // "already backed up" map built by parseStateFile().
    132         if (mStoredIncrementalVersion == null
    133                 || !mStoredIncrementalVersion.equals(Build.VERSION.INCREMENTAL)) {
    134             Slog.i(TAG, "Previous metadata " + mStoredIncrementalVersion + " mismatch vs "
    135                     + Build.VERSION.INCREMENTAL + " - rewriting");
    136             mExisting.clear();
    137         }
    138 
    139         try {
    140             /*
    141              * Global metadata:
    142              *
    143              * int SDKversion -- the SDK version of the OS itself on the device
    144              *                   that produced this backup set.  Used to reject
    145              *                   backups from later OSes onto earlier ones.
    146              * String incremental -- the incremental release name of the OS stored in
    147              *                       the backup set.
    148              */
    149             if (!mExisting.contains(GLOBAL_METADATA_KEY)) {
    150                 if (DEBUG) Slog.v(TAG, "Storing global metadata key");
    151                 outputBufferStream.writeInt(Build.VERSION.SDK_INT);
    152                 outputBufferStream.writeUTF(Build.VERSION.INCREMENTAL);
    153                 writeEntity(data, GLOBAL_METADATA_KEY, outputBuffer.toByteArray());
    154             } else {
    155                 if (DEBUG) Slog.v(TAG, "Global metadata key already stored");
    156                 // don't consider it to have been skipped/deleted
    157                 mExisting.remove(GLOBAL_METADATA_KEY);
    158             }
    159 
    160             // For each app we have on device, see if we've backed it up yet.  If not,
    161             // write its signature block to the output, keyed on the package name.
    162             for (PackageInfo pkg : mAllPackages) {
    163                 String packName = pkg.packageName;
    164                 if (packName.equals(GLOBAL_METADATA_KEY)) {
    165                     // We've already handled the metadata key; skip it here
    166                     continue;
    167                 } else {
    168                     PackageInfo info = null;
    169                     try {
    170                         info = mPackageManager.getPackageInfo(packName,
    171                                 PackageManager.GET_SIGNATURES);
    172                     } catch (NameNotFoundException e) {
    173                         // Weird; we just found it, and now are told it doesn't exist.
    174                         // Treat it as having been removed from the device.
    175                         mExisting.add(packName);
    176                         continue;
    177                     }
    178 
    179                     if (mExisting.contains(packName)) {
    180                         // We have backed up this app before.  Check whether the version
    181                         // of the backup matches the version of the current app; if they
    182                         // don't match, the app has been updated and we need to store its
    183                         // metadata again.  In either case, take it out of mExisting so that
    184                         // we don't consider it deleted later.
    185                         mExisting.remove(packName);
    186                         if (info.versionCode == mStateVersions.get(packName).versionCode) {
    187                             continue;
    188                         }
    189                     }
    190 
    191                     if (info.signatures == null || info.signatures.length == 0)
    192                     {
    193                         Slog.w(TAG, "Not backing up package " + packName
    194                                 + " since it appears to have no signatures.");
    195                         continue;
    196                     }
    197 
    198                     // We need to store this app's metadata
    199                     /*
    200                      * Metadata for each package:
    201                      *
    202                      * int version       -- [4] the package's versionCode
    203                      * byte[] signatures -- [len] flattened Signature[] of the package
    204                      */
    205 
    206                     // marshal the version code in a canonical form
    207                     outputBuffer.reset();
    208                     outputBufferStream.writeInt(info.versionCode);
    209                     writeSignatureArray(outputBufferStream, info.signatures);
    210 
    211                     if (DEBUG) {
    212                         Slog.v(TAG, "+ writing metadata for " + packName
    213                                 + " version=" + info.versionCode
    214                                 + " entityLen=" + outputBuffer.size());
    215                     }
    216 
    217                     // Now we can write the backup entity for this package
    218                     writeEntity(data, packName, outputBuffer.toByteArray());
    219                 }
    220             }
    221 
    222             // At this point, the only entries in 'existing' are apps that were
    223             // mentioned in the saved state file, but appear to no longer be present
    224             // on the device.  Write a deletion entity for them.
    225             for (String app : mExisting) {
    226                 if (DEBUG) Slog.v(TAG, "- removing metadata for deleted pkg " + app);
    227                 try {
    228                     data.writeEntityHeader(app, -1);
    229                 } catch (IOException e) {
    230                     Slog.e(TAG, "Unable to write package deletions!");
    231                     return;
    232                 }
    233             }
    234         } catch (IOException e) {
    235             // Real error writing data
    236             Slog.e(TAG, "Unable to write package backup data file!");
    237             return;
    238         }
    239 
    240         // Finally, write the new state blob -- just the list of all apps we handled
    241         writeStateFile(mAllPackages, newState);
    242     }
    243 
    244     private static void writeEntity(BackupDataOutput data, String key, byte[] bytes)
    245             throws IOException {
    246         data.writeEntityHeader(key, bytes.length);
    247         data.writeEntityData(bytes, bytes.length);
    248     }
    249 
    250     // "Restore" here is a misnomer.  What we're really doing is reading back the
    251     // set of app signatures associated with each backed-up app in this restore
    252     // image.  We'll use those later to determine what we can legitimately restore.
    253     public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)
    254             throws IOException {
    255         List<ApplicationInfo> restoredApps = new ArrayList<ApplicationInfo>();
    256         HashMap<String, Metadata> sigMap = new HashMap<String, Metadata>();
    257         if (DEBUG) Slog.v(TAG, "onRestore()");
    258         int storedSystemVersion = -1;
    259 
    260         while (data.readNextHeader()) {
    261             String key = data.getKey();
    262             int dataSize = data.getDataSize();
    263 
    264             if (DEBUG) Slog.v(TAG, "   got key=" + key + " dataSize=" + dataSize);
    265 
    266             // generic setup to parse any entity data
    267             byte[] inputBytes = new byte[dataSize];
    268             data.readEntityData(inputBytes, 0, dataSize);
    269             ByteArrayInputStream inputBuffer = new ByteArrayInputStream(inputBytes);
    270             DataInputStream inputBufferStream = new DataInputStream(inputBuffer);
    271 
    272             if (key.equals(GLOBAL_METADATA_KEY)) {
    273                 int storedSdkVersion = inputBufferStream.readInt();
    274                 if (DEBUG) Slog.v(TAG, "   storedSystemVersion = " + storedSystemVersion);
    275                 if (storedSystemVersion > Build.VERSION.SDK_INT) {
    276                     // returning before setting the sig map means we rejected the restore set
    277                     Slog.w(TAG, "Restore set was from a later version of Android; not restoring");
    278                     return;
    279                 }
    280                 mStoredSdkVersion = storedSdkVersion;
    281                 mStoredIncrementalVersion = inputBufferStream.readUTF();
    282                 mHasMetadata = true;
    283                 if (DEBUG) {
    284                     Slog.i(TAG, "Restore set version " + storedSystemVersion
    285                             + " is compatible with OS version " + Build.VERSION.SDK_INT
    286                             + " (" + mStoredIncrementalVersion + " vs "
    287                             + Build.VERSION.INCREMENTAL + ")");
    288                 }
    289             } else {
    290                 // it's a file metadata record
    291                 int versionCode = inputBufferStream.readInt();
    292                 Signature[] sigs = readSignatureArray(inputBufferStream);
    293                 if (DEBUG) {
    294                     Slog.i(TAG, "   read metadata for " + key
    295                             + " dataSize=" + dataSize
    296                             + " versionCode=" + versionCode + " sigs=" + sigs);
    297                 }
    298 
    299                 if (sigs == null || sigs.length == 0) {
    300                     Slog.w(TAG, "Not restoring package " + key
    301                             + " since it appears to have no signatures.");
    302                     continue;
    303                 }
    304 
    305                 ApplicationInfo app = new ApplicationInfo();
    306                 app.packageName = key;
    307                 restoredApps.add(app);
    308                 sigMap.put(key, new Metadata(versionCode, sigs));
    309             }
    310         }
    311 
    312         // On successful completion, cache the signature map for the Backup Manager to use
    313         mRestoredSignatures = sigMap;
    314     }
    315 
    316     private static void writeSignatureArray(DataOutputStream out, Signature[] sigs)
    317             throws IOException {
    318         // write the number of signatures in the array
    319         out.writeInt(sigs.length);
    320 
    321         // write the signatures themselves, length + flattened buffer
    322         for (Signature sig : sigs) {
    323             byte[] flat = sig.toByteArray();
    324             out.writeInt(flat.length);
    325             out.write(flat);
    326         }
    327     }
    328 
    329     private static Signature[] readSignatureArray(DataInputStream in) {
    330         try {
    331             int num;
    332             try {
    333                 num = in.readInt();
    334             } catch (EOFException e) {
    335                 // clean termination
    336                 Slog.w(TAG, "Read empty signature block");
    337                 return null;
    338             }
    339 
    340             if (DEBUG) Slog.v(TAG, " ... unflatten read " + num);
    341 
    342             // Sensical?
    343             if (num > 20) {
    344                 Slog.e(TAG, "Suspiciously large sig count in restore data; aborting");
    345                 throw new IllegalStateException("Bad restore state");
    346             }
    347 
    348             Signature[] sigs = new Signature[num];
    349             for (int i = 0; i < num; i++) {
    350                 int len = in.readInt();
    351                 byte[] flatSig = new byte[len];
    352                 in.read(flatSig);
    353                 sigs[i] = new Signature(flatSig);
    354             }
    355             return sigs;
    356         } catch (IOException e) {
    357             Slog.e(TAG, "Unable to read signatures");
    358             return null;
    359         }
    360     }
    361 
    362     // Util: parse out an existing state file into a usable structure
    363     private void parseStateFile(ParcelFileDescriptor stateFile) {
    364         mExisting.clear();
    365         mStateVersions.clear();
    366         mStoredSdkVersion = 0;
    367         mStoredIncrementalVersion = null;
    368 
    369         // The state file is just the list of app names we have stored signatures for
    370         // with the exception of the metadata block, to which is also appended the
    371         // version numbers corresponding with the last time we wrote this PM block.
    372         // If they mismatch the current system, we'll re-store the metadata key.
    373         FileInputStream instream = new FileInputStream(stateFile.getFileDescriptor());
    374         DataInputStream in = new DataInputStream(instream);
    375 
    376         int bufSize = 256;
    377         byte[] buf = new byte[bufSize];
    378         try {
    379             String pkg = in.readUTF();
    380             if (pkg.equals(GLOBAL_METADATA_KEY)) {
    381                 mStoredSdkVersion = in.readInt();
    382                 mStoredIncrementalVersion = in.readUTF();
    383                 mExisting.add(GLOBAL_METADATA_KEY);
    384             } else {
    385                 Slog.e(TAG, "No global metadata in state file!");
    386                 return;
    387             }
    388 
    389             // The global metadata was first; now read all the apps
    390             while (true) {
    391                 pkg = in.readUTF();
    392                 int versionCode = in.readInt();
    393                 mExisting.add(pkg);
    394                 mStateVersions.put(pkg, new Metadata(versionCode, null));
    395             }
    396         } catch (EOFException eof) {
    397             // safe; we're done
    398         } catch (IOException e) {
    399             // whoops, bad state file.  abort.
    400             Slog.e(TAG, "Unable to read Package Manager state file: " + e);
    401         }
    402     }
    403 
    404     // Util: write out our new backup state file
    405     private void writeStateFile(List<PackageInfo> pkgs, ParcelFileDescriptor stateFile) {
    406         FileOutputStream outstream = new FileOutputStream(stateFile.getFileDescriptor());
    407         DataOutputStream out = new DataOutputStream(outstream);
    408 
    409         try {
    410             // by the time we get here we know we've stored the global metadata record
    411             out.writeUTF(GLOBAL_METADATA_KEY);
    412             out.writeInt(Build.VERSION.SDK_INT);
    413             out.writeUTF(Build.VERSION.INCREMENTAL);
    414 
    415             // now write all the app names too
    416             for (PackageInfo pkg : pkgs) {
    417                 out.writeUTF(pkg.packageName);
    418                 out.writeInt(pkg.versionCode);
    419             }
    420         } catch (IOException e) {
    421             Slog.e(TAG, "Unable to write package manager state file!");
    422             return;
    423         }
    424     }
    425 }
    426