blob: 786f42305c96a00eb1616aa69b7eebef33dc1876 [file] [log] [blame]
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -07001/*
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.BackupAgent;
20import android.backup.BackupDataInput;
21import android.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.Log;
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;
43
44/**
45 * We back up the signatures of each package so that during a system restore,
46 * we can verify that the app whose data we think we have matches the app
47 * actually resident on the device.
48 *
49 * Since the Package Manager isn't a proper "application" we just provide a
50 * direct IBackupAgent implementation and hand-construct it at need.
51 */
52public class PackageManagerBackupAgent extends BackupAgent {
53 private static final String TAG = "PMBA";
54 private static final boolean DEBUG = true;
55
56 // key under which we store global metadata (individual app metadata
57 // is stored using the package name as a key)
58 private static final String GLOBAL_METADATA_KEY = "@meta@";
59
60 private List<PackageInfo> mAllPackages;
61 private PackageManager mPackageManager;
62 // version & signature info of each app in a restore set
63 private HashMap<String, Metadata> mRestoredSignatures;
64 // The version info of each backed-up app as read from the state file
65 private HashMap<String, Metadata> mStateVersions = new HashMap<String, Metadata>();
66
67 private final HashSet<String> mExisting = new HashSet<String>();
68 private int mStoredSdkVersion;
69 private String mStoredIncrementalVersion;
70 private boolean mHasMetadata;
71
72 public class Metadata {
73 public int versionCode;
74 public Signature[] signatures;
75
76 Metadata(int version, Signature[] sigs) {
77 versionCode = version;
78 signatures = sigs;
79 }
80 }
81
82 // We're constructed with the set of applications that are participating
83 // in backup. This set changes as apps are installed & removed.
84 PackageManagerBackupAgent(PackageManager packageMgr, List<PackageInfo> packages) {
85 mPackageManager = packageMgr;
86 mAllPackages = packages;
87 mRestoredSignatures = null;
88 mHasMetadata = false;
89 }
90
91 public boolean hasMetadata() {
92 return mHasMetadata;
93 }
94
95 public Metadata getRestoredMetadata(String packageName) {
96 if (mRestoredSignatures == null) {
97 Log.w(TAG, "getRestoredMetadata() before metadata read!");
98 return null;
99 }
100
101 return mRestoredSignatures.get(packageName);
102 }
103
104 // The backed up data is the signature block for each app, keyed by
105 // the package name.
106 public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
107 ParcelFileDescriptor newState) {
108 if (DEBUG) Log.v(TAG, "onBackup()");
109
110 ByteArrayOutputStream bufStream = new ByteArrayOutputStream(); // we'll reuse these
111 DataOutputStream outWriter = new DataOutputStream(bufStream);
112 parseStateFile(oldState);
113
114 // If the stored version string differs, we need to re-backup all
115 // of the metadata. We force this by removing everything from the
116 // "already backed up" map built by parseStateFile().
117 if (mStoredIncrementalVersion == null
118 || !mStoredIncrementalVersion.equals(Build.VERSION.INCREMENTAL)) {
119 Log.i(TAG, "Previous metadata " + mStoredIncrementalVersion + " mismatch vs "
120 + Build.VERSION.INCREMENTAL + " - rewriting");
121 mExisting.clear();
122 }
123
124 try {
125 /*
126 * Global metadata:
127 *
128 * int SDKversion -- the SDK version of the OS itself on the device
129 * that produced this backup set. Used to reject
130 * backups from later OSes onto earlier ones.
131 * String incremental -- the incremental release name of the OS stored in
132 * the backup set.
133 */
134 if (!mExisting.contains(GLOBAL_METADATA_KEY)) {
135 if (DEBUG) Log.v(TAG, "Storing global metadata key");
136 outWriter.writeInt(Build.VERSION.SDK_INT);
137 outWriter.writeUTF(Build.VERSION.INCREMENTAL);
138 byte[] metadata = bufStream.toByteArray();
139 data.writeEntityHeader(GLOBAL_METADATA_KEY, metadata.length);
140 data.writeEntityData(metadata, metadata.length);
141 } else {
142 if (DEBUG) Log.v(TAG, "Global metadata key already stored");
143 // don't consider it to have been skipped/deleted
144 mExisting.remove(GLOBAL_METADATA_KEY);
145 }
146
147 // For each app we have on device, see if we've backed it up yet. If not,
148 // write its signature block to the output, keyed on the package name.
149 for (PackageInfo pkg : mAllPackages) {
150 String packName = pkg.packageName;
151 if (packName.equals(GLOBAL_METADATA_KEY)) {
152 // We've already handled the metadata key; skip it here
153 continue;
154 } else {
155 PackageInfo info = null;
156 try {
157 info = mPackageManager.getPackageInfo(packName,
158 PackageManager.GET_SIGNATURES);
159 } catch (NameNotFoundException e) {
160 // Weird; we just found it, and now are told it doesn't exist.
161 // Treat it as having been removed from the device.
162 mExisting.add(packName);
163 continue;
164 }
165
166 boolean doBackup = false;
167 if (!mExisting.contains(packName)) {
168 // We haven't backed up this app before
169 doBackup = true;
170 } else {
171 // We *have* backed this one up before. Check whether the version
172 // of the backup matches the version of the current app; if they
173 // don't match, the app has been updated and we need to store its
174 // metadata again. In either case, take it out of mExisting so that
175 // we don't consider it deleted later.
176 if (info.versionCode != mStateVersions.get(packName).versionCode) {
177 doBackup = true;
178 }
179 mExisting.remove(packName);
180 }
181
182 if (doBackup) {
183 // We need to store this app's metadata
184 /*
185 * Metadata for each package:
186 *
187 * int version -- [4] the package's versionCode
188 * byte[] signatures -- [len] flattened Signature[] of the package
189 */
190
191 // marshal the version code in a canonical form
192 bufStream.reset();
193 outWriter.writeInt(info.versionCode);
194 byte[] versionBuf = bufStream.toByteArray();
195
196 byte[] sigs = flattenSignatureArray(info.signatures);
197
198 // !!! TODO: take out this debugging
199 if (DEBUG) {
200 Log.v(TAG, "+ metadata for " + packName
201 + " version=" + info.versionCode
202 + " versionLen=" + versionBuf.length
203 + " sigsLen=" + sigs.length);
204 }
205 // Now we can write the backup entity for this package
206 data.writeEntityHeader(packName, versionBuf.length + sigs.length);
207 data.writeEntityData(versionBuf, versionBuf.length);
208 data.writeEntityData(sigs, sigs.length);
209 }
210 }
211 }
212
213 // At this point, the only entries in 'existing' are apps that were
214 // mentioned in the saved state file, but appear to no longer be present
215 // on the device. Write a deletion entity for them.
216 for (String app : mExisting) {
217 // !!! TODO: take out this msg
218 if (DEBUG) Log.v(TAG, "- removing metadata for deleted pkg " + app);
219 try {
220 data.writeEntityHeader(app, -1);
221 } catch (IOException e) {
222 Log.e(TAG, "Unable to write package deletions!");
223 return;
224 }
225 }
226 } catch (IOException e) {
227 // Real error writing data
228 Log.e(TAG, "Unable to write package backup data file!");
229 return;
230 }
231
232 // Finally, write the new state blob -- just the list of all apps we handled
233 writeStateFile(mAllPackages, newState);
234 }
235
236 // "Restore" here is a misnomer. What we're really doing is reading back the
237 // set of app signatures associated with each backed-up app in this restore
238 // image. We'll use those later to determine what we can legitimately restore.
239 public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)
240 throws IOException {
241 List<ApplicationInfo> restoredApps = new ArrayList<ApplicationInfo>();
242 HashMap<String, Metadata> sigMap = new HashMap<String, Metadata>();
243 if (DEBUG) Log.v(TAG, "onRestore()");
244 int storedSystemVersion = -1;
245
246 while (data.readNextHeader()) {
247 String key = data.getKey();
248 int dataSize = data.getDataSize();
249
250 if (DEBUG) Log.v(TAG, " got key=" + key + " dataSize=" + dataSize);
251
252 // generic setup to parse any entity data
253 byte[] dataBuf = new byte[dataSize];
254 data.readEntityData(dataBuf, 0, dataSize);
255 ByteArrayInputStream baStream = new ByteArrayInputStream(dataBuf);
256 DataInputStream in = new DataInputStream(baStream);
257
258 if (key.equals(GLOBAL_METADATA_KEY)) {
259 int storedSdkVersion = in.readInt();
260 if (DEBUG) Log.v(TAG, " storedSystemVersion = " + storedSystemVersion);
261 if (storedSystemVersion > Build.VERSION.SDK_INT) {
262 // returning before setting the sig map means we rejected the restore set
263 Log.w(TAG, "Restore set was from a later version of Android; not restoring");
264 return;
265 }
266 mStoredSdkVersion = storedSdkVersion;
267 mStoredIncrementalVersion = in.readUTF();
268 mHasMetadata = true;
269 // !!! TODO: remove this debugging output
270 if (DEBUG) {
271 Log.i(TAG, "Restore set version " + storedSystemVersion
272 + " is compatible with OS version " + Build.VERSION.SDK_INT
273 + " (" + mStoredIncrementalVersion + " vs "
274 + Build.VERSION.INCREMENTAL + ")");
275 }
276 } else {
277 // it's a file metadata record
278 int versionCode = in.readInt();
279 Signature[] sigs = unflattenSignatureArray(in);
280// !!! TODO: take out this debugging
281 if (DEBUG) {
282 Log.i(TAG, " restored metadata for " + key
283 + " dataSize=" + dataSize
284 + " versionCode=" + versionCode + " sigs=" + sigs);
285 }
286
287 ApplicationInfo app = new ApplicationInfo();
288 app.packageName = key;
289 restoredApps.add(app);
290 sigMap.put(key, new Metadata(versionCode, sigs));
291 }
292 }
293
294 // On successful completion, cache the signature map for the Backup Manager to use
295 mRestoredSignatures = sigMap;
296 }
297
298
299 // Util: convert an array of Signatures into a flattened byte buffer. The
300 // flattened format contains enough info to reconstruct the signature array.
301 private byte[] flattenSignatureArray(Signature[] allSigs) {
302 ByteArrayOutputStream outBuf = new ByteArrayOutputStream();
303 DataOutputStream out = new DataOutputStream(outBuf);
304
305 // build the set of subsidiary buffers
306 try {
307 // first the # of signatures in the array
308 out.writeInt(allSigs.length);
309
310 // then the signatures themselves, length + flattened buffer
311 for (Signature sig : allSigs) {
312 byte[] flat = sig.toByteArray();
313 out.writeInt(flat.length);
314 out.write(flat);
315 }
316 } catch (IOException e) {
317 // very strange; we're writing to memory here. abort.
318 return null;
319 }
320
321 return outBuf.toByteArray();
322 }
323
324 private Signature[] unflattenSignatureArray(/*byte[] buffer*/ DataInputStream in) {
325 Signature[] sigs = null;
326
327 try {
328 int num = in.readInt();
329 Log.v(TAG, " ... unflatten read " + num);
330 sigs = new Signature[num];
331 for (int i = 0; i < num; i++) {
332 int len = in.readInt();
333 byte[] flatSig = new byte[len];
334 in.read(flatSig);
335 sigs[i] = new Signature(flatSig);
336 }
337 } catch (EOFException e) {
338 // clean termination
339 if (sigs == null) {
340 Log.w(TAG, "Empty signature block found");
341 }
342 } catch (IOException e) {
343 Log.d(TAG, "Unable to unflatten sigs");
344 return null;
345 }
346
347 return sigs;
348 }
349
350 // Util: parse out an existing state file into a usable structure
351 private void parseStateFile(ParcelFileDescriptor stateFile) {
352 mExisting.clear();
353 mStateVersions.clear();
354 mStoredSdkVersion = 0;
355 mStoredIncrementalVersion = null;
356
357 // The state file is just the list of app names we have stored signatures for
358 // with the exception of the metadata block, to which is also appended the
359 // version numbers corresponding with the last time we wrote this PM block.
360 // If they mismatch the current system, we'll re-store the metadata key.
361 FileInputStream instream = new FileInputStream(stateFile.getFileDescriptor());
362 DataInputStream in = new DataInputStream(instream);
363
364 int bufSize = 256;
365 byte[] buf = new byte[bufSize];
366 try {
367 String pkg = in.readUTF();
368 if (pkg.equals(GLOBAL_METADATA_KEY)) {
369 mStoredSdkVersion = in.readInt();
370 mStoredIncrementalVersion = in.readUTF();
371 mExisting.add(GLOBAL_METADATA_KEY);
372 } else {
373 Log.e(TAG, "No global metadata in state file!");
374 return;
375 }
376
377 // The global metadata was first; now read all the apps
378 while (true) {
379 pkg = in.readUTF();
380 int versionCode = in.readInt();
381 mExisting.add(pkg);
382 mStateVersions.put(pkg, new Metadata(versionCode, null));
383 }
384 } catch (EOFException eof) {
385 // safe; we're done
386 } catch (IOException e) {
387 // whoops, bad state file. abort.
388 Log.e(TAG, "Unable to read Package Manager state file: " + e);
389 }
390 }
391
392 // Util: write out our new backup state file
393 private void writeStateFile(List<PackageInfo> pkgs, ParcelFileDescriptor stateFile) {
394 FileOutputStream outstream = new FileOutputStream(stateFile.getFileDescriptor());
395 DataOutputStream out = new DataOutputStream(outstream);
396
397 try {
398 // by the time we get here we know we've stored the global metadata record
399 out.writeUTF(GLOBAL_METADATA_KEY);
400 out.writeInt(Build.VERSION.SDK_INT);
401 out.writeUTF(Build.VERSION.INCREMENTAL);
402
403 // now write all the app names too
404 for (PackageInfo pkg : pkgs) {
405 out.writeUTF(pkg.packageName);
406 out.writeInt(pkg.versionCode);
407 }
408 } catch (IOException e) {
409 Log.e(TAG, "Unable to write package manager state file!");
410 return;
411 }
412 }
413}