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