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