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