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