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"><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 */ 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