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 android.app.backup;
18
19import android.app.IBackupAgent;
20import android.app.QueuedWork;
21import android.content.Context;
22import android.content.ContextWrapper;
23import android.content.pm.ApplicationInfo;
24import android.os.Binder;
25import android.os.Handler;
26import android.os.IBinder;
27import android.os.Looper;
28import android.os.ParcelFileDescriptor;
29import android.os.Process;
30import android.os.RemoteException;
31import android.system.ErrnoException;
32import android.system.Os;
33import android.system.OsConstants;
34import android.system.StructStat;
35import android.util.ArraySet;
36import android.util.Log;
37
38import org.xmlpull.v1.XmlPullParserException;
39
40import java.io.File;
41import java.io.FileOutputStream;
42import java.io.IOException;
43import java.util.Collection;
44import java.util.LinkedList;
45import java.util.Map;
46import java.util.Set;
47import java.util.concurrent.CountDownLatch;
48
49/**
50 * Provides the central interface between an
51 * application and Android's data backup infrastructure.  An application that wishes
52 * to participate in the backup and restore mechanism will declare a subclass of
53 * {@link android.app.backup.BackupAgent}, implement the
54 * {@link #onBackup(ParcelFileDescriptor, BackupDataOutput, ParcelFileDescriptor) onBackup()}
55 * and {@link #onRestore(BackupDataInput, int, ParcelFileDescriptor) onRestore()} methods,
56 * and provide the name of its backup agent class in its {@code AndroidManifest.xml} file via
57 * the <code>
58 * <a href="{@docRoot}guide/topics/manifest/application-element.html">&lt;application&gt;</a></code>
59 * tag's {@code android:backupAgent} attribute.
60 *
61 * <div class="special reference">
62 * <h3>Developer Guides</h3>
63 * <p>For more information about using BackupAgent, read the
64 * <a href="{@docRoot}guide/topics/data/backup.html">Data Backup</a> developer guide.</p></div>
65 *
66 * <h3>Basic Operation</h3>
67 * <p>
68 * When the application makes changes to data that it wishes to keep backed up,
69 * it should call the
70 * {@link android.app.backup.BackupManager#dataChanged() BackupManager.dataChanged()} method.
71 * This notifies the Android Backup Manager that the application needs an opportunity
72 * to update its backup image.  The Backup Manager, in turn, schedules a
73 * backup pass to be performed at an opportune time.
74 * <p>
75 * Restore operations are typically performed only when applications are first
76 * installed on a device.  At that time, the operating system checks to see whether
77 * there is a previously-saved data set available for the application being installed, and if so,
78 * begins an immediate restore pass to deliver the backup data as part of the installation
79 * process.
80 * <p>
81 * When a backup or restore pass is run, the application's process is launched
82 * (if not already running), the manifest-declared backup agent class (in the {@code
83 * android:backupAgent} attribute) is instantiated within
84 * that process, and the agent's {@link #onCreate()} method is invoked.  This prepares the
85 * agent instance to run the actual backup or restore logic.  At this point the
86 * agent's
87 * {@link #onBackup(ParcelFileDescriptor, BackupDataOutput, ParcelFileDescriptor) onBackup()} or
88 * {@link #onRestore(BackupDataInput, int, ParcelFileDescriptor) onRestore()} method will be
89 * invoked as appropriate for the operation being performed.
90 * <p>
91 * A backup data set consists of one or more "entities," flattened binary data
92 * records that are each identified with a key string unique within the data set.  Adding a
93 * record to the active data set or updating an existing record is done by simply
94 * writing new entity data under the desired key.  Deleting an entity from the data set
95 * is done by writing an entity under that key with header specifying a negative data
96 * size, and no actual entity data.
97 * <p>
98 * <b>Helper Classes</b>
99 * <p>
100 * An extensible agent based on convenient helper classes is available in
101 * {@link android.app.backup.BackupAgentHelper}.  That class is particularly
102 * suited to handling of simple file or {@link android.content.SharedPreferences}
103 * backup and restore.
104 *
105 * @see android.app.backup.BackupManager
106 * @see android.app.backup.BackupAgentHelper
107 * @see android.app.backup.BackupDataInput
108 * @see android.app.backup.BackupDataOutput
109 */
110public abstract class BackupAgent extends ContextWrapper {
111    private static final String TAG = "BackupAgent";
112    private static final boolean DEBUG = false;
113
114    /** @hide */
115    public static final int TYPE_EOF = 0;
116
117    /**
118     * During a full restore, indicates that the file system object being restored
119     * is an ordinary file.
120     */
121    public static final int TYPE_FILE = 1;
122
123    /**
124     * During a full restore, indicates that the file system object being restored
125     * is a directory.
126     */
127    public static final int TYPE_DIRECTORY = 2;
128
129    /** @hide */
130    public static final int TYPE_SYMLINK = 3;
131
132    Handler mHandler = null;
133
134    Handler getHandler() {
135        if (mHandler == null) {
136            mHandler = new Handler(Looper.getMainLooper());
137        }
138        return mHandler;
139    }
140
141    class SharedPrefsSynchronizer implements Runnable {
142        public final CountDownLatch mLatch = new CountDownLatch(1);
143
144        @Override
145        public void run() {
146            QueuedWork.waitToFinish();
147            mLatch.countDown();
148        }
149    };
150
151    // Syncing shared preferences deferred writes needs to happen on the main looper thread
152    private void waitForSharedPrefs() {
153        Handler h = getHandler();
154        final SharedPrefsSynchronizer s = new SharedPrefsSynchronizer();
155        h.postAtFrontOfQueue(s);
156        try {
157            s.mLatch.await();
158        } catch (InterruptedException e) { /* ignored */ }
159    }
160
161
162    public BackupAgent() {
163        super(null);
164    }
165
166    /**
167     * Provided as a convenience for agent implementations that need an opportunity
168     * to do one-time initialization before the actual backup or restore operation
169     * is begun.
170     * <p>
171     */
172    public void onCreate() {
173    }
174
175    /**
176     * Provided as a convenience for agent implementations that need to do some
177     * sort of shutdown process after backup or restore is completed.
178     * <p>
179     * Agents do not need to override this method.
180     */
181    public void onDestroy() {
182    }
183
184    /**
185     * The application is being asked to write any data changed since the last
186     * time it performed a backup operation. The state data recorded during the
187     * last backup pass is provided in the <code>oldState</code> file
188     * descriptor. If <code>oldState</code> is <code>null</code>, no old state
189     * is available and the application should perform a full backup. In both
190     * cases, a representation of the final backup state after this pass should
191     * be written to the file pointed to by the file descriptor wrapped in
192     * <code>newState</code>.
193     * <p>
194     * Each entity written to the {@link android.app.backup.BackupDataOutput}
195     * <code>data</code> stream will be transmitted
196     * over the current backup transport and stored in the remote data set under
197     * the key supplied as part of the entity.  Writing an entity with a negative
198     * data size instructs the transport to delete whatever entity currently exists
199     * under that key from the remote data set.
200     *
201     * @param oldState An open, read-only ParcelFileDescriptor pointing to the
202     *            last backup state provided by the application. May be
203     *            <code>null</code>, in which case no prior state is being
204     *            provided and the application should perform a full backup.
205     * @param data A structured wrapper around an open, read/write
206     *            file descriptor pointing to the backup data destination.
207     *            Typically the application will use backup helper classes to
208     *            write to this file.
209     * @param newState An open, read/write ParcelFileDescriptor pointing to an
210     *            empty file. The application should record the final backup
211     *            state here after writing the requested data to the <code>data</code>
212     *            output stream.
213     */
214    public abstract void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
215            ParcelFileDescriptor newState) throws IOException;
216
217    /**
218     * The application is being restored from backup and should replace any
219     * existing data with the contents of the backup. The backup data is
220     * provided through the <code>data</code> parameter. Once
221     * the restore is finished, the application should write a representation of
222     * the final state to the <code>newState</code> file descriptor.
223     * <p>
224     * The application is responsible for properly erasing its old data and
225     * replacing it with the data supplied to this method. No "clear user data"
226     * operation will be performed automatically by the operating system. The
227     * exception to this is in the case of a failed restore attempt: if
228     * onRestore() throws an exception, the OS will assume that the
229     * application's data may now be in an incoherent state, and will clear it
230     * before proceeding.
231     *
232     * @param data A structured wrapper around an open, read-only
233     *            file descriptor pointing to a full snapshot of the
234     *            application's data.  The application should consume every
235     *            entity represented in this data stream.
236     * @param appVersionCode The value of the <a
237     * href="{@docRoot}guide/topics/manifest/manifest-element.html#vcode">{@code
238     *            android:versionCode}</a> manifest attribute,
239     *            from the application that backed up this particular data set. This
240     *            makes it possible for an application's agent to distinguish among any
241     *            possible older data versions when asked to perform the restore
242     *            operation.
243     * @param newState An open, read/write ParcelFileDescriptor pointing to an
244     *            empty file. The application should record the final backup
245     *            state here after restoring its data from the <code>data</code> stream.
246     *            When a full-backup dataset is being restored, this will be <code>null</code>.
247     */
248    public abstract void onRestore(BackupDataInput data, int appVersionCode,
249            ParcelFileDescriptor newState) throws IOException;
250
251    /**
252     * The application is having its entire file system contents backed up.  {@code data}
253     * points to the backup destination, and the app has the opportunity to choose which
254     * files are to be stored.  To commit a file as part of the backup, call the
255     * {@link #fullBackupFile(File, FullBackupDataOutput)} helper method.  After all file
256     * data is written to the output, the agent returns from this method and the backup
257     * operation concludes.
258     *
259     * <p>Certain parts of the app's data are never backed up even if the app explicitly
260     * sends them to the output:
261     *
262     * <ul>
263     * <li>The contents of the {@link #getCacheDir()} directory</li>
264     * <li>The contents of the {@link #getCodeCacheDir()} directory</li>
265     * <li>The contents of the {@link #getNoBackupFilesDir()} directory</li>
266     * <li>The contents of the app's shared library directory</li>
267     * </ul>
268     *
269     * <p>The default implementation of this method backs up the entirety of the
270     * application's "owned" file system trees to the output other than the few exceptions
271     * listed above.  Apps only need to override this method if they need to impose special
272     * limitations on which files are being stored beyond the control that
273     * {@link #getNoBackupFilesDir()} offers.
274     * Alternatively they can provide an xml resource to specify what data to include or exclude.
275     *
276     *
277     * @param data A structured wrapper pointing to the backup destination.
278     * @throws IOException
279     *
280     * @see Context#getNoBackupFilesDir()
281     * @see ApplicationInfo#fullBackupContent
282     * @see #fullBackupFile(File, FullBackupDataOutput)
283     * @see #onRestoreFile(ParcelFileDescriptor, long, File, int, long, long)
284     */
285    public void onFullBackup(FullBackupDataOutput data) throws IOException {
286        FullBackup.BackupScheme backupScheme = FullBackup.getBackupScheme(this);
287        if (!backupScheme.isFullBackupContentEnabled()) {
288            return;
289        }
290
291        Map<String, Set<String>> manifestIncludeMap;
292        ArraySet<String> manifestExcludeSet;
293        try {
294            manifestIncludeMap =
295                    backupScheme.maybeParseAndGetCanonicalIncludePaths();
296            manifestExcludeSet = backupScheme.maybeParseAndGetCanonicalExcludePaths();
297        } catch (IOException | XmlPullParserException e) {
298            if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
299                Log.v(FullBackup.TAG_XML_PARSER,
300                        "Exception trying to parse fullBackupContent xml file!"
301                                + " Aborting full backup.", e);
302            }
303            return;
304        }
305
306        final String packageName = getPackageName();
307        final ApplicationInfo appInfo = getApplicationInfo();
308
309        // System apps have control over where their default storage context
310        // is pointed, so we're always explicit when building paths.
311        final Context ceContext = createCredentialProtectedStorageContext();
312        final String rootDir = ceContext.getDataDir().getCanonicalPath();
313        final String filesDir = ceContext.getFilesDir().getCanonicalPath();
314        final String noBackupDir = ceContext.getNoBackupFilesDir().getCanonicalPath();
315        final String databaseDir = ceContext.getDatabasePath("foo").getParentFile()
316                .getCanonicalPath();
317        final String sharedPrefsDir = ceContext.getSharedPreferencesPath("foo").getParentFile()
318                .getCanonicalPath();
319        final String cacheDir = ceContext.getCacheDir().getCanonicalPath();
320        final String codeCacheDir = ceContext.getCodeCacheDir().getCanonicalPath();
321
322        final Context deContext = createDeviceProtectedStorageContext();
323        final String deviceRootDir = deContext.getDataDir().getCanonicalPath();
324        final String deviceFilesDir = deContext.getFilesDir().getCanonicalPath();
325        final String deviceNoBackupDir = deContext.getNoBackupFilesDir().getCanonicalPath();
326        final String deviceDatabaseDir = deContext.getDatabasePath("foo").getParentFile()
327                .getCanonicalPath();
328        final String deviceSharedPrefsDir = deContext.getSharedPreferencesPath("foo")
329                .getParentFile().getCanonicalPath();
330        final String deviceCacheDir = deContext.getCacheDir().getCanonicalPath();
331        final String deviceCodeCacheDir = deContext.getCodeCacheDir().getCanonicalPath();
332
333        final String libDir = (appInfo.nativeLibraryDir != null)
334                ? new File(appInfo.nativeLibraryDir).getCanonicalPath()
335                : null;
336
337        // Maintain a set of excluded directories so that as we traverse the tree we know we're not
338        // going places we don't expect, and so the manifest includes can't take precedence over
339        // what the framework decides is not to be included.
340        final ArraySet<String> traversalExcludeSet = new ArraySet<String>();
341
342        // Add the directories we always exclude.
343        traversalExcludeSet.add(filesDir);
344        traversalExcludeSet.add(noBackupDir);
345        traversalExcludeSet.add(databaseDir);
346        traversalExcludeSet.add(sharedPrefsDir);
347        traversalExcludeSet.add(cacheDir);
348        traversalExcludeSet.add(codeCacheDir);
349
350        traversalExcludeSet.add(deviceFilesDir);
351        traversalExcludeSet.add(deviceNoBackupDir);
352        traversalExcludeSet.add(deviceDatabaseDir);
353        traversalExcludeSet.add(deviceSharedPrefsDir);
354        traversalExcludeSet.add(deviceCacheDir);
355        traversalExcludeSet.add(deviceCodeCacheDir);
356
357        if (libDir != null) {
358            traversalExcludeSet.add(libDir);
359        }
360
361        // Root dir first.
362        applyXmlFiltersAndDoFullBackupForDomain(
363                packageName, FullBackup.ROOT_TREE_TOKEN, manifestIncludeMap,
364                manifestExcludeSet, traversalExcludeSet, data);
365        traversalExcludeSet.add(rootDir);
366
367        applyXmlFiltersAndDoFullBackupForDomain(
368                packageName, FullBackup.DEVICE_ROOT_TREE_TOKEN, manifestIncludeMap,
369                manifestExcludeSet, traversalExcludeSet, data);
370        traversalExcludeSet.add(deviceRootDir);
371
372        // Data dir next.
373        traversalExcludeSet.remove(filesDir);
374        applyXmlFiltersAndDoFullBackupForDomain(
375                packageName, FullBackup.FILES_TREE_TOKEN, manifestIncludeMap,
376                manifestExcludeSet, traversalExcludeSet, data);
377        traversalExcludeSet.add(filesDir);
378
379        traversalExcludeSet.remove(deviceFilesDir);
380        applyXmlFiltersAndDoFullBackupForDomain(
381                packageName, FullBackup.DEVICE_FILES_TREE_TOKEN, manifestIncludeMap,
382                manifestExcludeSet, traversalExcludeSet, data);
383        traversalExcludeSet.add(deviceFilesDir);
384
385        // Database directory.
386        traversalExcludeSet.remove(databaseDir);
387        applyXmlFiltersAndDoFullBackupForDomain(
388                packageName, FullBackup.DATABASE_TREE_TOKEN, manifestIncludeMap,
389                manifestExcludeSet, traversalExcludeSet, data);
390        traversalExcludeSet.add(databaseDir);
391
392        traversalExcludeSet.remove(deviceDatabaseDir);
393        applyXmlFiltersAndDoFullBackupForDomain(
394                packageName, FullBackup.DEVICE_DATABASE_TREE_TOKEN, manifestIncludeMap,
395                manifestExcludeSet, traversalExcludeSet, data);
396        traversalExcludeSet.add(deviceDatabaseDir);
397
398        // SharedPrefs.
399        traversalExcludeSet.remove(sharedPrefsDir);
400        applyXmlFiltersAndDoFullBackupForDomain(
401                packageName, FullBackup.SHAREDPREFS_TREE_TOKEN, manifestIncludeMap,
402                manifestExcludeSet, traversalExcludeSet, data);
403        traversalExcludeSet.add(sharedPrefsDir);
404
405        traversalExcludeSet.remove(deviceSharedPrefsDir);
406        applyXmlFiltersAndDoFullBackupForDomain(
407                packageName, FullBackup.DEVICE_SHAREDPREFS_TREE_TOKEN, manifestIncludeMap,
408                manifestExcludeSet, traversalExcludeSet, data);
409        traversalExcludeSet.add(deviceSharedPrefsDir);
410
411        // getExternalFilesDir() location associated with this app.  Technically there should
412        // not be any files here if the app does not properly have permission to access
413        // external storage, but edge cases happen. fullBackupFileTree() catches
414        // IOExceptions and similar, and treats them as non-fatal, so we rely on that; and
415        // we know a priori that processes running as the system UID are not permitted to
416        // access external storage, so we check for that as well to avoid nastygrams in
417        // the log.
418        if (Process.myUid() != Process.SYSTEM_UID) {
419            File efLocation = getExternalFilesDir(null);
420            if (efLocation != null) {
421                applyXmlFiltersAndDoFullBackupForDomain(
422                        packageName, FullBackup.MANAGED_EXTERNAL_TREE_TOKEN, manifestIncludeMap,
423                        manifestExcludeSet, traversalExcludeSet, data);
424            }
425
426        }
427    }
428
429    /**
430     * Notification that the application's current backup operation causes it to exceed
431     * the maximum size permitted by the transport.  The ongoing backup operation is
432     * halted and rolled back: any data that had been stored by a previous backup operation
433     * is still intact.  Typically the quota-exceeded state will be detected before any data
434     * is actually transmitted over the network.
435     *
436     * <p>The {@code quotaBytes} value is the total data size currently permitted for this
437     * application.  If desired, the application can use this as a hint for determining
438     * how much data to store.  For example, a messaging application might choose to
439     * store only the newest messages, dropping enough older content to stay under
440     * the quota.
441     *
442     * <p class="note">Note that the maximum quota for the application can change over
443     * time.  In particular, in the future the quota may grow.  Applications that adapt
444     * to the quota when deciding what data to store should be aware of this and implement
445     * their data storage mechanisms in a way that can take advantage of additional
446     * quota.
447     *
448     * @param backupDataBytes The amount of data measured while initializing the backup
449     *    operation, if the total exceeds the app's alloted quota.  If initial measurement
450     *    suggested that the data would fit but then too much data was actually submitted
451     *    as part of the operation, then this value is the amount of data that had been
452     *    streamed into the transport at the time the quota was reached.
453     * @param quotaBytes The maximum data size that the transport currently permits
454     *    this application to store as a backup.
455     */
456    public void onQuotaExceeded(long backupDataBytes, long quotaBytes) {
457    }
458
459    /**
460     * Check whether the xml yielded any <include/> tag for the provided <code>domainToken</code>.
461     * If so, perform a {@link #fullBackupFileTree} which backs up the file or recurses if the path
462     * is a directory.
463     */
464    private void applyXmlFiltersAndDoFullBackupForDomain(String packageName, String domainToken,
465                                                         Map<String, Set<String>> includeMap,
466                                                         ArraySet<String> filterSet,
467                                                         ArraySet<String> traversalExcludeSet,
468                                                         FullBackupDataOutput data)
469            throws IOException {
470        if (includeMap == null || includeMap.size() == 0) {
471            // Do entire sub-tree for the provided token.
472            fullBackupFileTree(packageName, domainToken,
473                    FullBackup.getBackupScheme(this).tokenToDirectoryPath(domainToken),
474                    filterSet, traversalExcludeSet, data);
475        } else if (includeMap.get(domainToken) != null) {
476            // This will be null if the xml parsing didn't yield any rules for
477            // this domain (there may still be rules for other domains).
478            for (String includeFile : includeMap.get(domainToken)) {
479                fullBackupFileTree(packageName, domainToken, includeFile, filterSet,
480                        traversalExcludeSet, data);
481            }
482        }
483    }
484
485    /**
486     * Write an entire file as part of a full-backup operation.  The file's contents
487     * will be delivered to the backup destination along with the metadata necessary
488     * to place it with the proper location and permissions on the device where the
489     * data is restored.
490     *
491     * <p class="note">Attempting to back up files in directories that are ignored by
492     * the backup system will have no effect.  For example, if the app calls this method
493     * with a file inside the {@link #getNoBackupFilesDir()} directory, it will be ignored.
494     * See {@link #onFullBackup(FullBackupDataOutput) for details on what directories
495     * are excluded from backups.
496     *
497     * @param file The file to be backed up.  The file must exist and be readable by
498     *     the caller.
499     * @param output The destination to which the backed-up file data will be sent.
500     */
501    public final void fullBackupFile(File file, FullBackupDataOutput output) {
502        // Look up where all of our various well-defined dir trees live on this device
503        final String rootDir;
504        final String filesDir;
505        final String nbFilesDir;
506        final String dbDir;
507        final String spDir;
508        final String cacheDir;
509        final String codeCacheDir;
510        final String deviceRootDir;
511        final String deviceFilesDir;
512        final String deviceNbFilesDir;
513        final String deviceDbDir;
514        final String deviceSpDir;
515        final String deviceCacheDir;
516        final String deviceCodeCacheDir;
517        final String libDir;
518
519        String efDir = null;
520        String filePath;
521
522        ApplicationInfo appInfo = getApplicationInfo();
523
524        try {
525            // System apps have control over where their default storage context
526            // is pointed, so we're always explicit when building paths.
527            final Context ceContext = createCredentialProtectedStorageContext();
528            rootDir = ceContext.getDataDir().getCanonicalPath();
529            filesDir = ceContext.getFilesDir().getCanonicalPath();
530            nbFilesDir = ceContext.getNoBackupFilesDir().getCanonicalPath();
531            dbDir = ceContext.getDatabasePath("foo").getParentFile().getCanonicalPath();
532            spDir = ceContext.getSharedPreferencesPath("foo").getParentFile().getCanonicalPath();
533            cacheDir = ceContext.getCacheDir().getCanonicalPath();
534            codeCacheDir = ceContext.getCodeCacheDir().getCanonicalPath();
535
536            final Context deContext = createDeviceProtectedStorageContext();
537            deviceRootDir = deContext.getDataDir().getCanonicalPath();
538            deviceFilesDir = deContext.getFilesDir().getCanonicalPath();
539            deviceNbFilesDir = deContext.getNoBackupFilesDir().getCanonicalPath();
540            deviceDbDir = deContext.getDatabasePath("foo").getParentFile().getCanonicalPath();
541            deviceSpDir = deContext.getSharedPreferencesPath("foo").getParentFile()
542                    .getCanonicalPath();
543            deviceCacheDir = deContext.getCacheDir().getCanonicalPath();
544            deviceCodeCacheDir = deContext.getCodeCacheDir().getCanonicalPath();
545
546            libDir = (appInfo.nativeLibraryDir == null)
547                    ? null
548                    : new File(appInfo.nativeLibraryDir).getCanonicalPath();
549
550            // may or may not have external files access to attempt backup/restore there
551            if (Process.myUid() != Process.SYSTEM_UID) {
552                File efLocation = getExternalFilesDir(null);
553                if (efLocation != null) {
554                    efDir = efLocation.getCanonicalPath();
555                }
556            }
557
558            // Now figure out which well-defined tree the file is placed in, working from
559            // most to least specific.  We also specifically exclude the lib, cache,
560            // and code_cache dirs.
561            filePath = file.getCanonicalPath();
562        } catch (IOException e) {
563            Log.w(TAG, "Unable to obtain canonical paths");
564            return;
565        }
566
567        if (filePath.startsWith(cacheDir)
568                || filePath.startsWith(codeCacheDir)
569                || filePath.startsWith(nbFilesDir)
570                || filePath.startsWith(deviceCacheDir)
571                || filePath.startsWith(deviceCodeCacheDir)
572                || filePath.startsWith(deviceNbFilesDir)
573                || filePath.startsWith(libDir)) {
574            Log.w(TAG, "lib, cache, code_cache, and no_backup files are not backed up");
575            return;
576        }
577
578        final String domain;
579        String rootpath = null;
580        if (filePath.startsWith(dbDir)) {
581            domain = FullBackup.DATABASE_TREE_TOKEN;
582            rootpath = dbDir;
583        } else if (filePath.startsWith(spDir)) {
584            domain = FullBackup.SHAREDPREFS_TREE_TOKEN;
585            rootpath = spDir;
586        } else if (filePath.startsWith(filesDir)) {
587            domain = FullBackup.FILES_TREE_TOKEN;
588            rootpath = filesDir;
589        } else if (filePath.startsWith(rootDir)) {
590            domain = FullBackup.ROOT_TREE_TOKEN;
591            rootpath = rootDir;
592        } else if (filePath.startsWith(deviceDbDir)) {
593            domain = FullBackup.DEVICE_DATABASE_TREE_TOKEN;
594            rootpath = deviceDbDir;
595        } else if (filePath.startsWith(deviceSpDir)) {
596            domain = FullBackup.DEVICE_SHAREDPREFS_TREE_TOKEN;
597            rootpath = deviceSpDir;
598        } else if (filePath.startsWith(deviceFilesDir)) {
599            domain = FullBackup.DEVICE_FILES_TREE_TOKEN;
600            rootpath = deviceFilesDir;
601        } else if (filePath.startsWith(deviceRootDir)) {
602            domain = FullBackup.DEVICE_ROOT_TREE_TOKEN;
603            rootpath = deviceRootDir;
604        } else if ((efDir != null) && filePath.startsWith(efDir)) {
605            domain = FullBackup.MANAGED_EXTERNAL_TREE_TOKEN;
606            rootpath = efDir;
607        } else {
608            Log.w(TAG, "File " + filePath + " is in an unsupported location; skipping");
609            return;
610        }
611
612        // And now that we know where it lives, semantically, back it up appropriately
613        // In the measurement case, backupToTar() updates the size in output and returns
614        // without transmitting any file data.
615        if (DEBUG) Log.i(TAG, "backupFile() of " + filePath + " => domain=" + domain
616                + " rootpath=" + rootpath);
617
618        FullBackup.backupToTar(getPackageName(), domain, null, rootpath, filePath, output);
619    }
620
621    /**
622     * Scan the dir tree (if it actually exists) and process each entry we find.  If the
623     * 'excludes' parameters are non-null, they are consulted each time a new file system entity
624     * is visited to see whether that entity (and its subtree, if appropriate) should be
625     * omitted from the backup process.
626     *
627     * @param systemExcludes An optional list of excludes.
628     * @hide
629     */
630    protected final void fullBackupFileTree(String packageName, String domain, String startingPath,
631                                            ArraySet<String> manifestExcludes,
632                                            ArraySet<String> systemExcludes,
633            FullBackupDataOutput output) {
634        // Pull out the domain and set it aside to use when making the tarball.
635        String domainPath = FullBackup.getBackupScheme(this).tokenToDirectoryPath(domain);
636        if (domainPath == null) {
637            // Should never happen.
638            return;
639        }
640
641        File rootFile = new File(startingPath);
642        if (rootFile.exists()) {
643            LinkedList<File> scanQueue = new LinkedList<File>();
644            scanQueue.add(rootFile);
645
646            while (scanQueue.size() > 0) {
647                File file = scanQueue.remove(0);
648                String filePath;
649                try {
650                    // Ignore symlinks outright
651                    StructStat stat = Os.lstat(file.getPath());
652                    if (OsConstants.S_ISLNK(stat.st_mode)) {
653                        if (DEBUG) Log.i(TAG, "Symlink (skipping)!: " + file);
654                        continue;
655                    }
656
657                    // For all other verification, look at the canonicalized path
658                    filePath = file.getCanonicalPath();
659
660                    // prune this subtree?
661                    if (manifestExcludes != null && manifestExcludes.contains(filePath)) {
662                        continue;
663                    }
664                    if (systemExcludes != null && systemExcludes.contains(filePath)) {
665                        continue;
666                    }
667
668                    // If it's a directory, enqueue its contents for scanning.
669                    if (OsConstants.S_ISDIR(stat.st_mode)) {
670                        File[] contents = file.listFiles();
671                        if (contents != null) {
672                            for (File entry : contents) {
673                                scanQueue.add(0, entry);
674                            }
675                        }
676                    }
677                } catch (IOException e) {
678                    if (DEBUG) Log.w(TAG, "Error canonicalizing path of " + file);
679                    if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
680                        Log.v(FullBackup.TAG_XML_PARSER, "Error canonicalizing path of " + file);
681                    }
682                    continue;
683                } catch (ErrnoException e) {
684                    if (DEBUG) Log.w(TAG, "Error scanning file " + file + " : " + e);
685                    if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
686                        Log.v(FullBackup.TAG_XML_PARSER, "Error scanning file " + file + " : " + e);
687                    }
688                    continue;
689                }
690
691                // Finally, back this file up (or measure it) before proceeding
692                FullBackup.backupToTar(packageName, domain, null, domainPath, filePath, output);
693            }
694        }
695    }
696
697    /**
698     * Handle the data delivered via the given file descriptor during a full restore
699     * operation.  The agent is given the path to the file's original location as well
700     * as its size and metadata.
701     * <p>
702     * The file descriptor can only be read for {@code size} bytes; attempting to read
703     * more data has undefined behavior.
704     * <p>
705     * The default implementation creates the destination file/directory and populates it
706     * with the data from the file descriptor, then sets the file's access mode and
707     * modification time to match the restore arguments.
708     *
709     * @param data A read-only file descriptor from which the agent can read {@code size}
710     *     bytes of file data.
711     * @param size The number of bytes of file content to be restored to the given
712     *     destination.  If the file system object being restored is a directory, {@code size}
713     *     will be zero.
714     * @param destination The File on disk to be restored with the given data.
715     * @param type The kind of file system object being restored.  This will be either
716     *     {@link BackupAgent#TYPE_FILE} or {@link BackupAgent#TYPE_DIRECTORY}.
717     * @param mode The access mode to be assigned to the destination after its data is
718     *     written.  This is in the standard format used by {@code chmod()}.
719     * @param mtime The modification time of the file when it was backed up, suitable to
720     *     be assigned to the file after its data is written.
721     * @throws IOException
722     */
723    public void onRestoreFile(ParcelFileDescriptor data, long size,
724            File destination, int type, long mode, long mtime)
725            throws IOException {
726
727        final boolean accept = isFileEligibleForRestore(destination);
728        // If we don't accept the file, consume the bytes from the pipe anyway.
729        FullBackup.restoreFile(data, size, type, mode, mtime, accept ? destination : null);
730    }
731
732    private boolean isFileEligibleForRestore(File destination) throws IOException {
733        FullBackup.BackupScheme bs = FullBackup.getBackupScheme(this);
734        if (!bs.isFullBackupContentEnabled()) {
735            if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
736                Log.v(FullBackup.TAG_XML_PARSER,
737                        "onRestoreFile \"" + destination.getCanonicalPath()
738                                + "\" : fullBackupContent not enabled for " + getPackageName());
739            }
740            return false;
741        }
742
743        Map<String, Set<String>> includes = null;
744        ArraySet<String> excludes = null;
745        final String destinationCanonicalPath = destination.getCanonicalPath();
746        try {
747            includes = bs.maybeParseAndGetCanonicalIncludePaths();
748            excludes = bs.maybeParseAndGetCanonicalExcludePaths();
749        } catch (XmlPullParserException e) {
750            if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
751                Log.v(FullBackup.TAG_XML_PARSER,
752                        "onRestoreFile \"" + destinationCanonicalPath
753                                + "\" : Exception trying to parse fullBackupContent xml file!"
754                                + " Aborting onRestoreFile.", e);
755            }
756            return false;
757        }
758
759        if (excludes != null &&
760                isFileSpecifiedInPathList(destination, excludes)) {
761            if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
762                Log.v(FullBackup.TAG_XML_PARSER,
763                        "onRestoreFile: \"" + destinationCanonicalPath + "\": listed in"
764                                + " excludes; skipping.");
765            }
766            return false;
767        }
768
769        if (includes != null && !includes.isEmpty()) {
770            // Rather than figure out the <include/> domain based on the path (a lot of code, and
771            // it's a small list), we'll go through and look for it.
772            boolean explicitlyIncluded = false;
773            for (Set<String> domainIncludes : includes.values()) {
774                explicitlyIncluded |= isFileSpecifiedInPathList(destination, domainIncludes);
775                if (explicitlyIncluded) {
776                    break;
777                }
778            }
779            if (!explicitlyIncluded) {
780                if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
781                    Log.v(FullBackup.TAG_XML_PARSER,
782                            "onRestoreFile: Trying to restore \""
783                                    + destinationCanonicalPath + "\" but it isn't specified"
784                                    + " in the included files; skipping.");
785                }
786                return false;
787            }
788        }
789        return true;
790    }
791
792    /**
793     * @return True if the provided file is either directly in the provided list, or the provided
794     * file is within a directory in the list.
795     */
796    private boolean isFileSpecifiedInPathList(File file, Collection<String> canonicalPathList)
797            throws IOException {
798        for (String canonicalPath : canonicalPathList) {
799            File fileFromList = new File(canonicalPath);
800            if (fileFromList.isDirectory()) {
801                if (file.isDirectory()) {
802                    // If they are both directories check exact equals.
803                    return file.equals(fileFromList);
804                } else {
805                    // O/w we have to check if the file is within the directory from the list.
806                    return file.getCanonicalPath().startsWith(canonicalPath);
807                }
808            } else {
809                if (file.equals(fileFromList)) {
810                    // Need to check the explicit "equals" so we don't end up with substrings.
811                    return true;
812                }
813            }
814        }
815        return false;
816    }
817
818    /**
819     * Only specialized platform agents should overload this entry point to support
820     * restores to crazy non-app locations.
821     * @hide
822     */
823    protected void onRestoreFile(ParcelFileDescriptor data, long size,
824            int type, String domain, String path, long mode, long mtime)
825            throws IOException {
826        String basePath = null;
827
828        if (DEBUG) Log.d(TAG, "onRestoreFile() size=" + size + " type=" + type
829                + " domain=" + domain + " relpath=" + path + " mode=" + mode
830                + " mtime=" + mtime);
831
832        basePath = FullBackup.getBackupScheme(this).tokenToDirectoryPath(domain);
833        if (domain.equals(FullBackup.MANAGED_EXTERNAL_TREE_TOKEN)) {
834            mode = -1;  // < 0 is a token to skip attempting a chmod()
835        }
836
837        // Now that we've figured out where the data goes, send it on its way
838        if (basePath != null) {
839            // Canonicalize the nominal path and verify that it lies within the stated domain
840            File outFile = new File(basePath, path);
841            String outPath = outFile.getCanonicalPath();
842            if (outPath.startsWith(basePath + File.separatorChar)) {
843                if (DEBUG) Log.i(TAG, "[" + domain + " : " + path + "] mapped to " + outPath);
844                onRestoreFile(data, size, outFile, type, mode, mtime);
845                return;
846            } else {
847                // Attempt to restore to a path outside the file's nominal domain.
848                if (DEBUG) {
849                    Log.e(TAG, "Cross-domain restore attempt: " + outPath);
850                }
851            }
852        }
853
854        // Not a supported output location, or bad path:  we need to consume the data
855        // anyway, so just use the default "copy the data out" implementation
856        // with a null destination.
857        if (DEBUG) Log.i(TAG, "[ skipping file " + path + "]");
858        FullBackup.restoreFile(data, size, type, mode, mtime, null);
859    }
860
861    /**
862     * The application's restore operation has completed.  This method is called after
863     * all available data has been delivered to the application for restore (via either
864     * the {@link #onRestore(BackupDataInput, int, ParcelFileDescriptor) onRestore()} or
865     * {@link #onRestoreFile(ParcelFileDescriptor, long, File, int, long, long) onRestoreFile()}
866     * callbacks).  This provides the app with a stable end-of-restore opportunity to
867     * perform any appropriate post-processing on the data that was just delivered.
868     *
869     * @see #onRestore(BackupDataInput, int, ParcelFileDescriptor)
870     * @see #onRestoreFile(ParcelFileDescriptor, long, File, int, long, long)
871     */
872    public void onRestoreFinished() {
873    }
874
875    // ----- Core implementation -----
876
877    /** @hide */
878    public final IBinder onBind() {
879        return mBinder;
880    }
881
882    private final IBinder mBinder = new BackupServiceBinder().asBinder();
883
884    /** @hide */
885    public void attach(Context context) {
886        attachBaseContext(context);
887    }
888
889    // ----- IBackupService binder interface -----
890    private class BackupServiceBinder extends IBackupAgent.Stub {
891        private static final String TAG = "BackupServiceBinder";
892
893        @Override
894        public void doBackup(ParcelFileDescriptor oldState,
895                ParcelFileDescriptor data,
896                ParcelFileDescriptor newState,
897                int token, IBackupManager callbackBinder) throws RemoteException {
898            // Ensure that we're running with the app's normal permission level
899            long ident = Binder.clearCallingIdentity();
900
901            if (DEBUG) Log.v(TAG, "doBackup() invoked");
902            BackupDataOutput output = new BackupDataOutput(data.getFileDescriptor());
903
904            try {
905                BackupAgent.this.onBackup(oldState, output, newState);
906            } catch (IOException ex) {
907                Log.d(TAG, "onBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
908                throw new RuntimeException(ex);
909            } catch (RuntimeException ex) {
910                Log.d(TAG, "onBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
911                throw ex;
912            } finally {
913                // Ensure that any SharedPreferences writes have landed after the backup,
914                // in case the app code has side effects (since apps cannot provide this
915                // guarantee themselves).
916                waitForSharedPrefs();
917
918                Binder.restoreCallingIdentity(ident);
919                try {
920                    callbackBinder.opComplete(token, 0);
921                } catch (RemoteException e) {
922                    // we'll time out anyway, so we're safe
923                }
924            }
925        }
926
927        @Override
928        public void doRestore(ParcelFileDescriptor data, int appVersionCode,
929                ParcelFileDescriptor newState,
930                int token, IBackupManager callbackBinder) throws RemoteException {
931            // Ensure that we're running with the app's normal permission level
932            long ident = Binder.clearCallingIdentity();
933
934            if (DEBUG) Log.v(TAG, "doRestore() invoked");
935            BackupDataInput input = new BackupDataInput(data.getFileDescriptor());
936            try {
937                BackupAgent.this.onRestore(input, appVersionCode, newState);
938            } catch (IOException ex) {
939                Log.d(TAG, "onRestore (" + BackupAgent.this.getClass().getName() + ") threw", ex);
940                throw new RuntimeException(ex);
941            } catch (RuntimeException ex) {
942                Log.d(TAG, "onRestore (" + BackupAgent.this.getClass().getName() + ") threw", ex);
943                throw ex;
944            } finally {
945                // Ensure that any side-effect SharedPreferences writes have landed
946                waitForSharedPrefs();
947
948                Binder.restoreCallingIdentity(ident);
949                try {
950                    callbackBinder.opComplete(token, 0);
951                } catch (RemoteException e) {
952                    // we'll time out anyway, so we're safe
953                }
954            }
955        }
956
957        @Override
958        public void doFullBackup(ParcelFileDescriptor data,
959                int token, IBackupManager callbackBinder) {
960            // Ensure that we're running with the app's normal permission level
961            long ident = Binder.clearCallingIdentity();
962
963            if (DEBUG) Log.v(TAG, "doFullBackup() invoked");
964
965            // Ensure that any SharedPreferences writes have landed *before*
966            // we potentially try to back up the underlying files directly.
967            waitForSharedPrefs();
968
969            try {
970                BackupAgent.this.onFullBackup(new FullBackupDataOutput(data));
971            } catch (IOException ex) {
972                Log.d(TAG, "onFullBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
973                throw new RuntimeException(ex);
974            } catch (RuntimeException ex) {
975                Log.d(TAG, "onFullBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
976                throw ex;
977            } finally {
978                // ... and then again after, as in the doBackup() case
979                waitForSharedPrefs();
980
981                // Send the EOD marker indicating that there is no more data
982                // forthcoming from this agent.
983                try {
984                    FileOutputStream out = new FileOutputStream(data.getFileDescriptor());
985                    byte[] buf = new byte[4];
986                    out.write(buf);
987                } catch (IOException e) {
988                    Log.e(TAG, "Unable to finalize backup stream!");
989                }
990
991                Binder.restoreCallingIdentity(ident);
992                try {
993                    callbackBinder.opComplete(token, 0);
994                } catch (RemoteException e) {
995                    // we'll time out anyway, so we're safe
996                }
997            }
998        }
999
1000        public void doMeasureFullBackup(int token, IBackupManager callbackBinder) {
1001            // Ensure that we're running with the app's normal permission level
1002            final long ident = Binder.clearCallingIdentity();
1003            FullBackupDataOutput measureOutput = new FullBackupDataOutput();
1004
1005            waitForSharedPrefs();
1006            try {
1007                BackupAgent.this.onFullBackup(measureOutput);
1008            } catch (IOException ex) {
1009                Log.d(TAG, "onFullBackup[M] (" + BackupAgent.this.getClass().getName() + ") threw", ex);
1010                throw new RuntimeException(ex);
1011            } catch (RuntimeException ex) {
1012                Log.d(TAG, "onFullBackup[M] (" + BackupAgent.this.getClass().getName() + ") threw", ex);
1013                throw ex;
1014            } finally {
1015                Binder.restoreCallingIdentity(ident);
1016                try {
1017                    callbackBinder.opComplete(token, measureOutput.getSize());
1018                } catch (RemoteException e) {
1019                    // timeout, so we're safe
1020                }
1021            }
1022        }
1023
1024        @Override
1025        public void doRestoreFile(ParcelFileDescriptor data, long size,
1026                int type, String domain, String path, long mode, long mtime,
1027                int token, IBackupManager callbackBinder) throws RemoteException {
1028            long ident = Binder.clearCallingIdentity();
1029            try {
1030                BackupAgent.this.onRestoreFile(data, size, type, domain, path, mode, mtime);
1031            } catch (IOException e) {
1032                Log.d(TAG, "onRestoreFile (" + BackupAgent.this.getClass().getName() + ") threw", e);
1033                throw new RuntimeException(e);
1034            } finally {
1035                // Ensure that any side-effect SharedPreferences writes have landed
1036                waitForSharedPrefs();
1037
1038                Binder.restoreCallingIdentity(ident);
1039                try {
1040                    callbackBinder.opComplete(token, 0);
1041                } catch (RemoteException e) {
1042                    // we'll time out anyway, so we're safe
1043                }
1044            }
1045        }
1046
1047        @Override
1048        public void doRestoreFinished(int token, IBackupManager callbackBinder) {
1049            long ident = Binder.clearCallingIdentity();
1050            try {
1051                BackupAgent.this.onRestoreFinished();
1052            } catch (Exception e) {
1053                Log.d(TAG, "onRestoreFinished (" + BackupAgent.this.getClass().getName() + ") threw", e);
1054                throw e;
1055            } finally {
1056                // Ensure that any side-effect SharedPreferences writes have landed
1057                waitForSharedPrefs();
1058
1059                Binder.restoreCallingIdentity(ident);
1060                try {
1061                    callbackBinder.opComplete(token, 0);
1062                } catch (RemoteException e) {
1063                    // we'll time out anyway, so we're safe
1064                }
1065            }
1066        }
1067
1068        @Override
1069        public void fail(String message) {
1070            getHandler().post(new FailRunnable(message));
1071        }
1072
1073        @Override
1074        public void doQuotaExceeded(long backupDataBytes, long quotaBytes) {
1075            long ident = Binder.clearCallingIdentity();
1076            try {
1077                BackupAgent.this.onQuotaExceeded(backupDataBytes, quotaBytes);
1078            } catch (Exception e) {
1079                Log.d(TAG, "onQuotaExceeded(" + BackupAgent.this.getClass().getName() + ") threw",
1080                        e);
1081                throw e;
1082            } finally {
1083                waitForSharedPrefs();
1084                Binder.restoreCallingIdentity(ident);
1085            }
1086        }
1087    }
1088
1089    static class FailRunnable implements Runnable {
1090        private String mMessage;
1091
1092        FailRunnable(String message) {
1093            mMessage = message;
1094        }
1095
1096        @Override
1097        public void run() {
1098            throw new IllegalStateException(mMessage);
1099        }
1100    }
1101}
1102