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 * 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 */ 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 */ 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 */ 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 */ 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 */ 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 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 */ 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 */ 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 */ 872 public void onRestoreFinished() { 873 } 874 875 // ----- Core implementation ----- 876 877 /** @hide */ 878 public final IBinder onBind() { 879 return mBinder; 880 } 881 882 private final IBinder mBinder = new BackupServiceBinder().asBinder(); 883 884 /** @hide */ 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 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 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 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 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 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 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 1069 public void fail(String message) { 1070 getHandler().post(new FailRunnable(message)); 1071 } 1072 1073 @Override 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 1092 FailRunnable(String message) { 1093 mMessage = message; 1094 } 1095 1096 @Override 1097 public void run() { 1098 throw new IllegalStateException(mMessage); 1099 } 1100 } 1101} 1102