BackupAgent.java revision 8a372a0a280127743ce9a7ce4b6198c7a02d2a4f
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     * Tells the application agent that the backup data size exceeded current transport quota.
431     * Later calls to {@link #onBackup(ParcelFileDescriptor, BackupDataOutput, ParcelFileDescriptor)}
432     * and {@link #onFullBackup(FullBackupDataOutput)} could use this information
433     * to reduce backup size under the limit.
434     * However, the quota can change, so do not assume that the value passed in here is absolute,
435     * similarly all subsequent backups should not be restricted to this size.
436     * This callback will be invoked before data has been put onto the wire in a preflight check,
437     * so it is relatively inexpensive to hit your quota.
438     * Apps that hit quota repeatedly without dealing with it can be subject to having their backup
439     * schedule reduced.
440     * The {@code quotaBytes} is a loose guideline b/c of metadata added by the backupmanager
441     * so apps should be more aggressive in trimming their backup set.
442     *
443     * @param backupDataBytes Expected or already processed amount of data.
444     *                        Could be less than total backup size if backup process was interrupted
445     *                        before finish of processing all backup data.
446     * @param quotaBytes Current amount of backup data that is allowed for the app.
447     */
448    public void onQuotaExceeded(long backupDataBytes, long quotaBytes) {
449    }
450
451    /**
452     * Check whether the xml yielded any <include/> tag for the provided <code>domainToken</code>.
453     * If so, perform a {@link #fullBackupFileTree} which backs up the file or recurses if the path
454     * is a directory.
455     */
456    private void applyXmlFiltersAndDoFullBackupForDomain(String packageName, String domainToken,
457                                                         Map<String, Set<String>> includeMap,
458                                                         ArraySet<String> filterSet,
459                                                         ArraySet<String> traversalExcludeSet,
460                                                         FullBackupDataOutput data)
461            throws IOException {
462        if (includeMap == null || includeMap.size() == 0) {
463            // Do entire sub-tree for the provided token.
464            fullBackupFileTree(packageName, domainToken,
465                    FullBackup.getBackupScheme(this).tokenToDirectoryPath(domainToken),
466                    filterSet, traversalExcludeSet, data);
467        } else if (includeMap.get(domainToken) != null) {
468            // This will be null if the xml parsing didn't yield any rules for
469            // this domain (there may still be rules for other domains).
470            for (String includeFile : includeMap.get(domainToken)) {
471                fullBackupFileTree(packageName, domainToken, includeFile, filterSet,
472                        traversalExcludeSet, data);
473            }
474        }
475    }
476
477    /**
478     * Write an entire file as part of a full-backup operation.  The file's contents
479     * will be delivered to the backup destination along with the metadata necessary
480     * to place it with the proper location and permissions on the device where the
481     * data is restored.
482     *
483     * <p class="note">It is safe to explicitly back up files underneath your application's
484     * {@link #getNoBackupFilesDir()} directory, and they will be restored to that
485     * location correctly.
486     *
487     * @param file The file to be backed up.  The file must exist and be readable by
488     *     the caller.
489     * @param output The destination to which the backed-up file data will be sent.
490     */
491    public final void fullBackupFile(File file, FullBackupDataOutput output) {
492        // Look up where all of our various well-defined dir trees live on this device
493        final String rootDir;
494        final String filesDir;
495        final String nbFilesDir;
496        final String dbDir;
497        final String spDir;
498        final String cacheDir;
499        final String codeCacheDir;
500        final String deviceRootDir;
501        final String deviceFilesDir;
502        final String deviceNbFilesDir;
503        final String deviceDbDir;
504        final String deviceSpDir;
505        final String deviceCacheDir;
506        final String deviceCodeCacheDir;
507        final String libDir;
508
509        String efDir = null;
510        String filePath;
511
512        ApplicationInfo appInfo = getApplicationInfo();
513
514        try {
515            // System apps have control over where their default storage context
516            // is pointed, so we're always explicit when building paths.
517            final Context ceContext = createCredentialProtectedStorageContext();
518            rootDir = ceContext.getDataDir().getCanonicalPath();
519            filesDir = ceContext.getFilesDir().getCanonicalPath();
520            nbFilesDir = ceContext.getNoBackupFilesDir().getCanonicalPath();
521            dbDir = ceContext.getDatabasePath("foo").getParentFile().getCanonicalPath();
522            spDir = ceContext.getSharedPreferencesPath("foo").getParentFile().getCanonicalPath();
523            cacheDir = ceContext.getCacheDir().getCanonicalPath();
524            codeCacheDir = ceContext.getCodeCacheDir().getCanonicalPath();
525
526            final Context deContext = createDeviceProtectedStorageContext();
527            deviceRootDir = deContext.getDataDir().getCanonicalPath();
528            deviceFilesDir = deContext.getFilesDir().getCanonicalPath();
529            deviceNbFilesDir = deContext.getNoBackupFilesDir().getCanonicalPath();
530            deviceDbDir = deContext.getDatabasePath("foo").getParentFile().getCanonicalPath();
531            deviceSpDir = deContext.getSharedPreferencesPath("foo").getParentFile()
532                    .getCanonicalPath();
533            deviceCacheDir = deContext.getCacheDir().getCanonicalPath();
534            deviceCodeCacheDir = deContext.getCodeCacheDir().getCanonicalPath();
535
536            libDir = (appInfo.nativeLibraryDir == null)
537                    ? null
538                    : new File(appInfo.nativeLibraryDir).getCanonicalPath();
539
540            // may or may not have external files access to attempt backup/restore there
541            if (Process.myUid() != Process.SYSTEM_UID) {
542                File efLocation = getExternalFilesDir(null);
543                if (efLocation != null) {
544                    efDir = efLocation.getCanonicalPath();
545                }
546            }
547
548            // Now figure out which well-defined tree the file is placed in, working from
549            // most to least specific.  We also specifically exclude the lib, cache,
550            // and code_cache dirs.
551            filePath = file.getCanonicalPath();
552        } catch (IOException e) {
553            Log.w(TAG, "Unable to obtain canonical paths");
554            return;
555        }
556
557        if (filePath.startsWith(cacheDir)
558                || filePath.startsWith(codeCacheDir)
559                || filePath.startsWith(nbFilesDir)
560                || filePath.startsWith(deviceCacheDir)
561                || filePath.startsWith(deviceCodeCacheDir)
562                || filePath.startsWith(deviceNbFilesDir)
563                || filePath.startsWith(libDir)) {
564            Log.w(TAG, "lib, cache, code_cache, and no_backup files are not backed up");
565            return;
566        }
567
568        final String domain;
569        String rootpath = null;
570        if (filePath.startsWith(dbDir)) {
571            domain = FullBackup.DATABASE_TREE_TOKEN;
572            rootpath = dbDir;
573        } else if (filePath.startsWith(spDir)) {
574            domain = FullBackup.SHAREDPREFS_TREE_TOKEN;
575            rootpath = spDir;
576        } else if (filePath.startsWith(filesDir)) {
577            domain = FullBackup.FILES_TREE_TOKEN;
578            rootpath = filesDir;
579        } else if (filePath.startsWith(rootDir)) {
580            domain = FullBackup.ROOT_TREE_TOKEN;
581            rootpath = rootDir;
582        } else if (filePath.startsWith(deviceDbDir)) {
583            domain = FullBackup.DEVICE_DATABASE_TREE_TOKEN;
584            rootpath = deviceDbDir;
585        } else if (filePath.startsWith(deviceSpDir)) {
586            domain = FullBackup.DEVICE_SHAREDPREFS_TREE_TOKEN;
587            rootpath = deviceSpDir;
588        } else if (filePath.startsWith(deviceFilesDir)) {
589            domain = FullBackup.DEVICE_FILES_TREE_TOKEN;
590            rootpath = deviceFilesDir;
591        } else if (filePath.startsWith(deviceRootDir)) {
592            domain = FullBackup.DEVICE_ROOT_TREE_TOKEN;
593            rootpath = deviceRootDir;
594        } else if ((efDir != null) && filePath.startsWith(efDir)) {
595            domain = FullBackup.MANAGED_EXTERNAL_TREE_TOKEN;
596            rootpath = efDir;
597        } else {
598            Log.w(TAG, "File " + filePath + " is in an unsupported location; skipping");
599            return;
600        }
601
602        // And now that we know where it lives, semantically, back it up appropriately
603        // In the measurement case, backupToTar() updates the size in output and returns
604        // without transmitting any file data.
605        if (DEBUG) Log.i(TAG, "backupFile() of " + filePath + " => domain=" + domain
606                + " rootpath=" + rootpath);
607
608        FullBackup.backupToTar(getPackageName(), domain, null, rootpath, filePath, output);
609    }
610
611    /**
612     * Scan the dir tree (if it actually exists) and process each entry we find.  If the
613     * 'excludes' parameters are non-null, they are consulted each time a new file system entity
614     * is visited to see whether that entity (and its subtree, if appropriate) should be
615     * omitted from the backup process.
616     *
617     * @param systemExcludes An optional list of excludes.
618     * @hide
619     */
620    protected final void fullBackupFileTree(String packageName, String domain, String startingPath,
621                                            ArraySet<String> manifestExcludes,
622                                            ArraySet<String> systemExcludes,
623            FullBackupDataOutput output) {
624        // Pull out the domain and set it aside to use when making the tarball.
625        String domainPath = FullBackup.getBackupScheme(this).tokenToDirectoryPath(domain);
626        if (domainPath == null) {
627            // Should never happen.
628            return;
629        }
630
631        File rootFile = new File(startingPath);
632        if (rootFile.exists()) {
633            LinkedList<File> scanQueue = new LinkedList<File>();
634            scanQueue.add(rootFile);
635
636            while (scanQueue.size() > 0) {
637                File file = scanQueue.remove(0);
638                String filePath;
639                try {
640                    // Ignore symlinks outright
641                    StructStat stat = Os.lstat(file.getPath());
642                    if (OsConstants.S_ISLNK(stat.st_mode)) {
643                        if (DEBUG) Log.i(TAG, "Symlink (skipping)!: " + file);
644                        continue;
645                    }
646
647                    // For all other verification, look at the canonicalized path
648                    filePath = file.getCanonicalPath();
649
650                    // prune this subtree?
651                    if (manifestExcludes != null && manifestExcludes.contains(filePath)) {
652                        continue;
653                    }
654                    if (systemExcludes != null && systemExcludes.contains(filePath)) {
655                        continue;
656                    }
657
658                    // If it's a directory, enqueue its contents for scanning.
659                    if (OsConstants.S_ISDIR(stat.st_mode)) {
660                        File[] contents = file.listFiles();
661                        if (contents != null) {
662                            for (File entry : contents) {
663                                scanQueue.add(0, entry);
664                            }
665                        }
666                    }
667                } catch (IOException e) {
668                    if (DEBUG) Log.w(TAG, "Error canonicalizing path of " + file);
669                    if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
670                        Log.v(FullBackup.TAG_XML_PARSER, "Error canonicalizing path of " + file);
671                    }
672                    continue;
673                } catch (ErrnoException e) {
674                    if (DEBUG) Log.w(TAG, "Error scanning file " + file + " : " + e);
675                    if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
676                        Log.v(FullBackup.TAG_XML_PARSER, "Error scanning file " + file + " : " + e);
677                    }
678                    continue;
679                }
680
681                // Finally, back this file up (or measure it) before proceeding
682                FullBackup.backupToTar(packageName, domain, null, domainPath, filePath, output);
683            }
684        }
685    }
686
687    /**
688     * Handle the data delivered via the given file descriptor during a full restore
689     * operation.  The agent is given the path to the file's original location as well
690     * as its size and metadata.
691     * <p>
692     * The file descriptor can only be read for {@code size} bytes; attempting to read
693     * more data has undefined behavior.
694     * <p>
695     * The default implementation creates the destination file/directory and populates it
696     * with the data from the file descriptor, then sets the file's access mode and
697     * modification time to match the restore arguments.
698     *
699     * @param data A read-only file descriptor from which the agent can read {@code size}
700     *     bytes of file data.
701     * @param size The number of bytes of file content to be restored to the given
702     *     destination.  If the file system object being restored is a directory, {@code size}
703     *     will be zero.
704     * @param destination The File on disk to be restored with the given data.
705     * @param type The kind of file system object being restored.  This will be either
706     *     {@link BackupAgent#TYPE_FILE} or {@link BackupAgent#TYPE_DIRECTORY}.
707     * @param mode The access mode to be assigned to the destination after its data is
708     *     written.  This is in the standard format used by {@code chmod()}.
709     * @param mtime The modification time of the file when it was backed up, suitable to
710     *     be assigned to the file after its data is written.
711     * @throws IOException
712     */
713    public void onRestoreFile(ParcelFileDescriptor data, long size,
714            File destination, int type, long mode, long mtime)
715            throws IOException {
716
717        final boolean accept = isFileEligibleForRestore(destination);
718        // If we don't accept the file, consume the bytes from the pipe anyway.
719        FullBackup.restoreFile(data, size, type, mode, mtime, accept ? destination : null);
720    }
721
722    private boolean isFileEligibleForRestore(File destination) throws IOException {
723        FullBackup.BackupScheme bs = FullBackup.getBackupScheme(this);
724        if (!bs.isFullBackupContentEnabled()) {
725            if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
726                Log.v(FullBackup.TAG_XML_PARSER,
727                        "onRestoreFile \"" + destination.getCanonicalPath()
728                                + "\" : fullBackupContent not enabled for " + getPackageName());
729            }
730            return false;
731        }
732
733        Map<String, Set<String>> includes = null;
734        ArraySet<String> excludes = null;
735        final String destinationCanonicalPath = destination.getCanonicalPath();
736        try {
737            includes = bs.maybeParseAndGetCanonicalIncludePaths();
738            excludes = bs.maybeParseAndGetCanonicalExcludePaths();
739        } catch (XmlPullParserException e) {
740            if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
741                Log.v(FullBackup.TAG_XML_PARSER,
742                        "onRestoreFile \"" + destinationCanonicalPath
743                                + "\" : Exception trying to parse fullBackupContent xml file!"
744                                + " Aborting onRestoreFile.", e);
745            }
746            return false;
747        }
748
749        if (excludes != null &&
750                isFileSpecifiedInPathList(destination, excludes)) {
751            if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
752                Log.v(FullBackup.TAG_XML_PARSER,
753                        "onRestoreFile: \"" + destinationCanonicalPath + "\": listed in"
754                                + " excludes; skipping.");
755            }
756            return false;
757        }
758
759        if (includes != null && !includes.isEmpty()) {
760            // Rather than figure out the <include/> domain based on the path (a lot of code, and
761            // it's a small list), we'll go through and look for it.
762            boolean explicitlyIncluded = false;
763            for (Set<String> domainIncludes : includes.values()) {
764                explicitlyIncluded |= isFileSpecifiedInPathList(destination, domainIncludes);
765                if (explicitlyIncluded) {
766                    break;
767                }
768            }
769            if (!explicitlyIncluded) {
770                if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
771                    Log.v(FullBackup.TAG_XML_PARSER,
772                            "onRestoreFile: Trying to restore \""
773                                    + destinationCanonicalPath + "\" but it isn't specified"
774                                    + " in the included files; skipping.");
775                }
776                return false;
777            }
778        }
779        return true;
780    }
781
782    /**
783     * @return True if the provided file is either directly in the provided list, or the provided
784     * file is within a directory in the list.
785     */
786    private boolean isFileSpecifiedInPathList(File file, Collection<String> canonicalPathList)
787            throws IOException {
788        for (String canonicalPath : canonicalPathList) {
789            File fileFromList = new File(canonicalPath);
790            if (fileFromList.isDirectory()) {
791                if (file.isDirectory()) {
792                    // If they are both directories check exact equals.
793                    return file.equals(fileFromList);
794                } else {
795                    // O/w we have to check if the file is within the directory from the list.
796                    return file.getCanonicalPath().startsWith(canonicalPath);
797                }
798            } else {
799                if (file.equals(fileFromList)) {
800                    // Need to check the explicit "equals" so we don't end up with substrings.
801                    return true;
802                }
803            }
804        }
805        return false;
806    }
807
808    /**
809     * Only specialized platform agents should overload this entry point to support
810     * restores to crazy non-app locations.
811     * @hide
812     */
813    protected void onRestoreFile(ParcelFileDescriptor data, long size,
814            int type, String domain, String path, long mode, long mtime)
815            throws IOException {
816        String basePath = null;
817
818        if (DEBUG) Log.d(TAG, "onRestoreFile() size=" + size + " type=" + type
819                + " domain=" + domain + " relpath=" + path + " mode=" + mode
820                + " mtime=" + mtime);
821
822        basePath = FullBackup.getBackupScheme(this).tokenToDirectoryPath(domain);
823        if (domain.equals(FullBackup.MANAGED_EXTERNAL_TREE_TOKEN)) {
824            mode = -1;  // < 0 is a token to skip attempting a chmod()
825        }
826
827        // Now that we've figured out where the data goes, send it on its way
828        if (basePath != null) {
829            // Canonicalize the nominal path and verify that it lies within the stated domain
830            File outFile = new File(basePath, path);
831            String outPath = outFile.getCanonicalPath();
832            if (outPath.startsWith(basePath + File.separatorChar)) {
833                if (DEBUG) Log.i(TAG, "[" + domain + " : " + path + "] mapped to " + outPath);
834                onRestoreFile(data, size, outFile, type, mode, mtime);
835                return;
836            } else {
837                // Attempt to restore to a path outside the file's nominal domain.
838                if (DEBUG) {
839                    Log.e(TAG, "Cross-domain restore attempt: " + outPath);
840                }
841            }
842        }
843
844        // Not a supported output location, or bad path:  we need to consume the data
845        // anyway, so just use the default "copy the data out" implementation
846        // with a null destination.
847        if (DEBUG) Log.i(TAG, "[ skipping file " + path + "]");
848        FullBackup.restoreFile(data, size, type, mode, mtime, null);
849    }
850
851    /**
852     * The application's restore operation has completed.  This method is called after
853     * all available data has been delivered to the application for restore (via either
854     * the {@link #onRestore(BackupDataInput, int, ParcelFileDescriptor) onRestore()} or
855     * {@link #onRestoreFile(ParcelFileDescriptor, long, File, int, long, long) onRestoreFile()}
856     * callbacks).  This provides the app with a stable end-of-restore opportunity to
857     * perform any appropriate post-processing on the data that was just delivered.
858     *
859     * @see #onRestore(BackupDataInput, int, ParcelFileDescriptor)
860     * @see #onRestoreFile(ParcelFileDescriptor, long, File, int, long, long)
861     */
862    public void onRestoreFinished() {
863    }
864
865    // ----- Core implementation -----
866
867    /** @hide */
868    public final IBinder onBind() {
869        return mBinder;
870    }
871
872    private final IBinder mBinder = new BackupServiceBinder().asBinder();
873
874    /** @hide */
875    public void attach(Context context) {
876        attachBaseContext(context);
877    }
878
879    // ----- IBackupService binder interface -----
880    private class BackupServiceBinder extends IBackupAgent.Stub {
881        private static final String TAG = "BackupServiceBinder";
882
883        @Override
884        public void doBackup(ParcelFileDescriptor oldState,
885                ParcelFileDescriptor data,
886                ParcelFileDescriptor newState,
887                int token, IBackupManager callbackBinder) throws RemoteException {
888            // Ensure that we're running with the app's normal permission level
889            long ident = Binder.clearCallingIdentity();
890
891            if (DEBUG) Log.v(TAG, "doBackup() invoked");
892            BackupDataOutput output = new BackupDataOutput(data.getFileDescriptor());
893
894            try {
895                BackupAgent.this.onBackup(oldState, output, newState);
896            } catch (IOException ex) {
897                Log.d(TAG, "onBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
898                throw new RuntimeException(ex);
899            } catch (RuntimeException ex) {
900                Log.d(TAG, "onBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
901                throw ex;
902            } finally {
903                // Ensure that any SharedPreferences writes have landed after the backup,
904                // in case the app code has side effects (since apps cannot provide this
905                // guarantee themselves).
906                waitForSharedPrefs();
907
908                Binder.restoreCallingIdentity(ident);
909                try {
910                    callbackBinder.opComplete(token, 0);
911                } catch (RemoteException e) {
912                    // we'll time out anyway, so we're safe
913                }
914            }
915        }
916
917        @Override
918        public void doRestore(ParcelFileDescriptor data, int appVersionCode,
919                ParcelFileDescriptor newState,
920                int token, IBackupManager callbackBinder) throws RemoteException {
921            // Ensure that we're running with the app's normal permission level
922            long ident = Binder.clearCallingIdentity();
923
924            if (DEBUG) Log.v(TAG, "doRestore() invoked");
925            BackupDataInput input = new BackupDataInput(data.getFileDescriptor());
926            try {
927                BackupAgent.this.onRestore(input, appVersionCode, newState);
928            } catch (IOException ex) {
929                Log.d(TAG, "onRestore (" + BackupAgent.this.getClass().getName() + ") threw", ex);
930                throw new RuntimeException(ex);
931            } catch (RuntimeException ex) {
932                Log.d(TAG, "onRestore (" + BackupAgent.this.getClass().getName() + ") threw", ex);
933                throw ex;
934            } finally {
935                // Ensure that any side-effect SharedPreferences writes have landed
936                waitForSharedPrefs();
937
938                Binder.restoreCallingIdentity(ident);
939                try {
940                    callbackBinder.opComplete(token, 0);
941                } catch (RemoteException e) {
942                    // we'll time out anyway, so we're safe
943                }
944            }
945        }
946
947        @Override
948        public void doFullBackup(ParcelFileDescriptor data,
949                int token, IBackupManager callbackBinder) {
950            // Ensure that we're running with the app's normal permission level
951            long ident = Binder.clearCallingIdentity();
952
953            if (DEBUG) Log.v(TAG, "doFullBackup() invoked");
954
955            // Ensure that any SharedPreferences writes have landed *before*
956            // we potentially try to back up the underlying files directly.
957            waitForSharedPrefs();
958
959            try {
960                BackupAgent.this.onFullBackup(new FullBackupDataOutput(data));
961            } catch (IOException ex) {
962                Log.d(TAG, "onFullBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
963                throw new RuntimeException(ex);
964            } catch (RuntimeException ex) {
965                Log.d(TAG, "onFullBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
966                throw ex;
967            } finally {
968                // ... and then again after, as in the doBackup() case
969                waitForSharedPrefs();
970
971                // Send the EOD marker indicating that there is no more data
972                // forthcoming from this agent.
973                try {
974                    FileOutputStream out = new FileOutputStream(data.getFileDescriptor());
975                    byte[] buf = new byte[4];
976                    out.write(buf);
977                } catch (IOException e) {
978                    Log.e(TAG, "Unable to finalize backup stream!");
979                }
980
981                Binder.restoreCallingIdentity(ident);
982                try {
983                    callbackBinder.opComplete(token, 0);
984                } catch (RemoteException e) {
985                    // we'll time out anyway, so we're safe
986                }
987            }
988        }
989
990        public void doMeasureFullBackup(int token, IBackupManager callbackBinder) {
991            // Ensure that we're running with the app's normal permission level
992            final long ident = Binder.clearCallingIdentity();
993            FullBackupDataOutput measureOutput = new FullBackupDataOutput();
994
995            waitForSharedPrefs();
996            try {
997                BackupAgent.this.onFullBackup(measureOutput);
998            } catch (IOException ex) {
999                Log.d(TAG, "onFullBackup[M] (" + BackupAgent.this.getClass().getName() + ") threw", ex);
1000                throw new RuntimeException(ex);
1001            } catch (RuntimeException ex) {
1002                Log.d(TAG, "onFullBackup[M] (" + BackupAgent.this.getClass().getName() + ") threw", ex);
1003                throw ex;
1004            } finally {
1005                Binder.restoreCallingIdentity(ident);
1006                try {
1007                    callbackBinder.opComplete(token, measureOutput.getSize());
1008                } catch (RemoteException e) {
1009                    // timeout, so we're safe
1010                }
1011            }
1012        }
1013
1014        @Override
1015        public void doRestoreFile(ParcelFileDescriptor data, long size,
1016                int type, String domain, String path, long mode, long mtime,
1017                int token, IBackupManager callbackBinder) throws RemoteException {
1018            long ident = Binder.clearCallingIdentity();
1019            try {
1020                BackupAgent.this.onRestoreFile(data, size, type, domain, path, mode, mtime);
1021            } catch (IOException e) {
1022                Log.d(TAG, "onRestoreFile (" + BackupAgent.this.getClass().getName() + ") threw", e);
1023                throw new RuntimeException(e);
1024            } finally {
1025                // Ensure that any side-effect SharedPreferences writes have landed
1026                waitForSharedPrefs();
1027
1028                Binder.restoreCallingIdentity(ident);
1029                try {
1030                    callbackBinder.opComplete(token, 0);
1031                } catch (RemoteException e) {
1032                    // we'll time out anyway, so we're safe
1033                }
1034            }
1035        }
1036
1037        @Override
1038        public void doRestoreFinished(int token, IBackupManager callbackBinder) {
1039            long ident = Binder.clearCallingIdentity();
1040            try {
1041                BackupAgent.this.onRestoreFinished();
1042            } catch (Exception e) {
1043                Log.d(TAG, "onRestoreFinished (" + BackupAgent.this.getClass().getName() + ") threw", e);
1044                throw e;
1045            } finally {
1046                // Ensure that any side-effect SharedPreferences writes have landed
1047                waitForSharedPrefs();
1048
1049                Binder.restoreCallingIdentity(ident);
1050                try {
1051                    callbackBinder.opComplete(token, 0);
1052                } catch (RemoteException e) {
1053                    // we'll time out anyway, so we're safe
1054                }
1055            }
1056        }
1057
1058        @Override
1059        public void fail(String message) {
1060            getHandler().post(new FailRunnable(message));
1061        }
1062
1063        @Override
1064        public void doQuotaExceeded(long backupDataBytes, long quotaBytes) {
1065            long ident = Binder.clearCallingIdentity();
1066            try {
1067                BackupAgent.this.onQuotaExceeded(backupDataBytes, quotaBytes);
1068            } catch (Exception e) {
1069                Log.d(TAG, "onQuotaExceeded(" + BackupAgent.this.getClass().getName() + ") threw",
1070                        e);
1071                throw e;
1072            } finally {
1073                waitForSharedPrefs();
1074                Binder.restoreCallingIdentity(ident);
1075            }
1076        }
1077    }
1078
1079    static class FailRunnable implements Runnable {
1080        private String mMessage;
1081
1082        FailRunnable(String message) {
1083            mMessage = message;
1084        }
1085
1086        @Override
1087        public void run() {
1088            throw new IllegalStateException(mMessage);
1089        }
1090    }
1091}
1092