OSDN Git Service

API: make "what's the quota?" an operation on the backup data sinks
[android-x86/frameworks-base.git] / core / java / android / app / backup / BackupAgent.java
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
17 package android.app.backup;
18
19 import android.app.IBackupAgent;
20 import android.app.QueuedWork;
21 import android.content.Context;
22 import android.content.ContextWrapper;
23 import android.content.pm.ApplicationInfo;
24 import android.os.Binder;
25 import android.os.Handler;
26 import android.os.IBinder;
27 import android.os.Looper;
28 import android.os.ParcelFileDescriptor;
29 import android.os.Process;
30 import android.os.RemoteException;
31 import android.system.ErrnoException;
32 import android.system.Os;
33 import android.system.OsConstants;
34 import android.system.StructStat;
35 import android.util.ArraySet;
36 import android.util.Log;
37
38 import libcore.io.IoUtils;
39
40 import org.xmlpull.v1.XmlPullParserException;
41
42 import java.io.File;
43 import java.io.FileOutputStream;
44 import java.io.IOException;
45 import java.util.Collection;
46 import java.util.LinkedList;
47 import java.util.Map;
48 import java.util.Set;
49 import 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  */
112 public 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 }