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 17package com.android.server; 18 19import android.app.backup.BackupAgent; 20import android.app.backup.BackupDataInput; 21import android.app.backup.BackupDataOutput; 22import android.content.pm.ApplicationInfo; 23import android.content.pm.PackageInfo; 24import android.content.pm.PackageManager; 25import android.content.pm.PackageManager.NameNotFoundException; 26import android.content.pm.Signature; 27import android.os.Build; 28import android.os.ParcelFileDescriptor; 29import android.util.Slog; 30 31import java.io.ByteArrayInputStream; 32import java.io.ByteArrayOutputStream; 33import java.io.DataInputStream; 34import java.io.DataOutputStream; 35import java.io.EOFException; 36import java.io.FileInputStream; 37import java.io.FileOutputStream; 38import java.io.IOException; 39import java.util.ArrayList; 40import java.util.HashMap; 41import java.util.HashSet; 42import java.util.List; 43import 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 */ 53public 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