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