blob: 42e6147203847f8842f27b17afc705602dcee20e [file] [log] [blame]
Christopher Tate487529a2009-04-29 14:03:25 -07001/*
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
Christopher Tate45281862010-03-05 15:46:30 -080017package android.app.backup;
Christopher Tate487529a2009-04-29 14:03:25 -070018
Christopher Tate181fafa2009-05-14 11:12:14 -070019import android.app.IBackupAgent;
Christopher Tatef85f5b22013-04-18 16:57:43 -070020import android.app.QueuedWork;
Christopher Tate181fafa2009-05-14 11:12:14 -070021import android.content.Context;
22import android.content.ContextWrapper;
Christopher Tate79ec80d2011-06-24 14:58:49 -070023import android.content.pm.ApplicationInfo;
Christopher Tate19024922010-01-22 16:39:53 -080024import android.os.Binder;
Christopher Tatef85f5b22013-04-18 16:57:43 -070025import android.os.Handler;
Christopher Tate487529a2009-04-29 14:03:25 -070026import android.os.IBinder;
Christopher Tatef85f5b22013-04-18 16:57:43 -070027import android.os.Looper;
Christopher Tate22b87872009-05-04 16:41:53 -070028import android.os.ParcelFileDescriptor;
Christopher Tate5cb5c332013-02-21 14:32:12 -080029import android.os.Process;
Christopher Tate487529a2009-04-29 14:03:25 -070030import android.os.RemoteException;
Elliott Hughes34385d32014-04-28 11:11:32 -070031import android.system.ErrnoException;
32import android.system.Os;
33import android.system.OsConstants;
34import android.system.StructStat;
Matthew Williams303650c2015-04-17 18:22:51 -070035import android.util.ArraySet;
Christopher Tate487529a2009-04-29 14:03:25 -070036import android.util.Log;
37
Christopher Tate91bb0e52016-09-30 17:52:19 -070038import libcore.io.IoUtils;
39
Jeff Sharkey8a372a02016-03-16 16:25:45 -060040import org.xmlpull.v1.XmlPullParserException;
41
Christopher Tate79ec80d2011-06-24 14:58:49 -070042import java.io.File;
Christopher Tate7926a692011-07-11 11:31:57 -070043import java.io.FileOutputStream;
Joe Onorato83248c42009-06-17 17:55:20 -070044import java.io.IOException;
Matthew Williams303650c2015-04-17 18:22:51 -070045import java.util.Collection;
Christopher Tate79ec80d2011-06-24 14:58:49 -070046import java.util.LinkedList;
Matthew Williams303650c2015-04-17 18:22:51 -070047import java.util.Map;
48import java.util.Set;
Christopher Tatef85f5b22013-04-18 16:57:43 -070049import java.util.concurrent.CountDownLatch;
Christopher Tate79ec80d2011-06-24 14:58:49 -070050
Christopher Tate487529a2009-04-29 14:03:25 -070051/**
Scott Maind17da432010-04-29 21:42:58 -070052 * Provides the central interface between an
Christopher Tate4e14a822010-04-08 12:54:23 -070053 * 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
Scott Maind17da432010-04-29 21:42:58 -070056 * {@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
Joe Fernandez61fd1e82011-10-26 13:39:11 -070059 * the <code>
60 * <a href="{@docRoot}guide/topics/manifest/application-element.html">&lt;application&gt;</a></code>
Scott Maind17da432010-04-29 21:42:58 -070061 * tag's {@code android:backupAgent} attribute.
Joe Fernandez61fd1e82011-10-26 13:39:11 -070062 *
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 *
Scott Maind17da432010-04-29 21:42:58 -070068 * <h3>Basic Operation</h3>
Kenny Root5a20ea12010-02-23 18:49:11 -080069 * <p>
Christopher Tate4e14a822010-04-08 12:54:23 -070070 * 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.
Scott Maind17da432010-04-29 21:42:58 -070073 * 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
Christopher Tate4e14a822010-04-08 12:54:23 -070075 * backup pass to be performed at an opportune time.
76 * <p>
Scott Maind17da432010-04-29 21:42:58 -070077 * Restore operations are typically performed only when applications are first
Christopher Tate4e14a822010-04-08 12:54:23 -070078 * installed on a device. At that time, the operating system checks to see whether
Scott Maind17da432010-04-29 21:42:58 -070079 * 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
Christopher Tate4e14a822010-04-08 12:54:23 -070081 * process.
82 * <p>
Scott Maind17da432010-04-29 21:42:58 -070083 * 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
Christopher Tate4e14a822010-04-08 12:54:23 -070087 * 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>
Scott Maind17da432010-04-29 21:42:58 -070093 * 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
Christopher Tate4e14a822010-04-08 12:54:23 -070096 * 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
Christopher Tate487529a2009-04-29 14:03:25 -0700111 */
Christopher Tate181fafa2009-05-14 11:12:14 -0700112public abstract class BackupAgent extends ContextWrapper {
Joe Onorato83248c42009-06-17 17:55:20 -0700113 private static final String TAG = "BackupAgent";
Alan Jeonf0e32ee2014-12-13 22:44:53 +0900114 private static final boolean DEBUG = false;
Joe Onorato83248c42009-06-17 17:55:20 -0700115
Christopher Tate79ec80d2011-06-24 14:58:49 -0700116 /** @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
Christopher Tatef85f5b22013-04-18 16:57:43 -0700134 Handler mHandler = null;
135
Christopher Tatecba59412014-04-01 10:38:29 -0700136 Handler getHandler() {
137 if (mHandler == null) {
138 mHandler = new Handler(Looper.getMainLooper());
139 }
140 return mHandler;
141 }
142
Christopher Tatef85f5b22013-04-18 16:57:43 -0700143 class SharedPrefsSynchronizer implements Runnable {
144 public final CountDownLatch mLatch = new CountDownLatch(1);
145
146 @Override
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
154 private void waitForSharedPrefs() {
Christopher Tatecba59412014-04-01 10:38:29 -0700155 Handler h = getHandler();
Christopher Tatef85f5b22013-04-18 16:57:43 -0700156 final SharedPrefsSynchronizer s = new SharedPrefsSynchronizer();
Christopher Tatecba59412014-04-01 10:38:29 -0700157 h.postAtFrontOfQueue(s);
Christopher Tatef85f5b22013-04-18 16:57:43 -0700158 try {
159 s.mLatch.await();
160 } catch (InterruptedException e) { /* ignored */ }
161 }
162
163
Christopher Tate181fafa2009-05-14 11:12:14 -0700164 public BackupAgent() {
165 super(null);
166 }
Christopher Tate487529a2009-04-29 14:03:25 -0700167
Christopher Tate4e14a822010-04-08 12:54:23 -0700168 /**
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>
Christopher Tate4e14a822010-04-08 12:54:23 -0700173 */
Christopher Tate181fafa2009-05-14 11:12:14 -0700174 public void onCreate() {
175 }
176
Christopher Tate4e14a822010-04-08 12:54:23 -0700177 /**
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 */
Christopher Tate181fafa2009-05-14 11:12:14 -0700183 public void onDestroy() {
184 }
Christopher Tate487529a2009-04-29 14:03:25 -0700185
186 /**
Kenny Root5a20ea12010-02-23 18:49:11 -0800187 * 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>.
Christopher Tate4e14a822010-04-08 12:54:23 -0700195 * <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.
Elliott Hughes34385d32014-04-28 11:11:32 -0700202 *
Kenny Root5a20ea12010-02-23 18:49:11 -0800203 * @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
Christopher Tate4e14a822010-04-08 12:54:23 -0700208 * file descriptor pointing to the backup data destination.
Kenny Root5a20ea12010-02-23 18:49:11 -0800209 * 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
Christopher Tate4e14a822010-04-08 12:54:23 -0700213 * state here after writing the requested data to the <code>data</code>
214 * output stream.
Christopher Tate487529a2009-04-29 14:03:25 -0700215 */
Joe Onorato290bb012009-05-13 18:57:29 -0400216 public abstract void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
Christopher Tate2e40d112014-07-15 12:37:38 -0700217 ParcelFileDescriptor newState) throws IOException;
Kenny Root5a20ea12010-02-23 18:49:11 -0800218
Christopher Tate487529a2009-04-29 14:03:25 -0700219 /**
Kenny Root5a20ea12010-02-23 18:49:11 -0800220 * The application is being restored from backup and should replace any
221 * existing data with the contents of the backup. The backup data is
Christopher Tate4e14a822010-04-08 12:54:23 -0700222 * provided through the <code>data</code> parameter. Once
Kenny Root5a20ea12010-02-23 18:49:11 -0800223 * 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.
Elliott Hughes34385d32014-04-28 11:11:32 -0700233 *
Kenny Root5a20ea12010-02-23 18:49:11 -0800234 * @param data A structured wrapper around an open, read-only
Christopher Tate4e14a822010-04-08 12:54:23 -0700235 * 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.
Scott Mainb83a2832010-04-29 13:26:53 -0700238 * @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
Christopher Tate4e14a822010-04-08 12:54:23 -0700242 * makes it possible for an application's agent to distinguish among any
Kenny Root5a20ea12010-02-23 18:49:11 -0800243 * 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
Christopher Tate4e14a822010-04-08 12:54:23 -0700247 * state here after restoring its data from the <code>data</code> stream.
Christopher Tate4a627c72011-04-01 14:43:32 -0700248 * When a full-backup dataset is being restored, this will be <code>null</code>.
Christopher Tate487529a2009-04-29 14:03:25 -0700249 */
Christopher Tate5cbbf562009-06-22 16:44:51 -0700250 public abstract void onRestore(BackupDataInput data, int appVersionCode,
Christopher Tate2e40d112014-07-15 12:37:38 -0700251 ParcelFileDescriptor newState) throws IOException;
Christopher Tate487529a2009-04-29 14:03:25 -0700252
Christopher Tate4a627c72011-04-01 14:43:32 -0700253 /**
Christopher Tatea7835b62014-07-11 17:25:57 -0700254 * 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>
Christopher Tatea8a739f2015-03-05 18:31:38 -0800266 * <li>The contents of the {@link #getCodeCacheDir()} directory</li>
Christopher Tatea7835b62014-07-11 17:25:57 -0700267 * <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.
Matthew Williams303650c2015-04-17 18:22:51 -0700276 * Alternatively they can provide an xml resource to specify what data to include or exclude.
277 *
Christopher Tatea7835b62014-07-11 17:25:57 -0700278 *
279 * @param data A structured wrapper pointing to the backup destination.
280 * @throws IOException
281 *
282 * @see Context#getNoBackupFilesDir()
Matthew Williams303650c2015-04-17 18:22:51 -0700283 * @see ApplicationInfo#fullBackupContent
Christopher Tatea7835b62014-07-11 17:25:57 -0700284 * @see #fullBackupFile(File, FullBackupDataOutput)
285 * @see #onRestoreFile(ParcelFileDescriptor, long, File, int, long, long)
Christopher Tate75a99702011-05-18 16:28:19 -0700286 */
Christopher Tate79ec80d2011-06-24 14:58:49 -0700287 public void onFullBackup(FullBackupDataOutput data) throws IOException {
Matthew Williams303650c2015-04-17 18:22:51 -0700288 FullBackup.BackupScheme backupScheme = FullBackup.getBackupScheme(this);
289 if (!backupScheme.isFullBackupContentEnabled()) {
290 return;
291 }
Christopher Tate79ec80d2011-06-24 14:58:49 -0700292
Matthew Williams303650c2015-04-17 18:22:51 -0700293 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
Jeff Sharkey2c1ba9a2016-02-17 15:29:38 -0700311 // System apps have control over where their default storage context
312 // is pointed, so we're always explicit when building paths.
Jeff Sharkey8a372a02016-03-16 16:25:45 -0600313 final Context ceContext = createCredentialProtectedStorageContext();
Jeff Sharkey2c1ba9a2016-02-17 15:29:38 -0700314 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
Jeff Sharkey8a372a02016-03-16 16:25:45 -0600324 final Context deContext = createDeviceProtectedStorageContext();
Jeff Sharkey2c1ba9a2016-02-17 15:29:38 -0700325 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)
Christopher Tate2efd2db2011-07-19 16:32:49 -0700336 ? new File(appInfo.nativeLibraryDir).getCanonicalPath()
Christopher Tate79ec80d2011-06-24 14:58:49 -0700337 : null;
338
Matthew Williams303650c2015-04-17 18:22:51 -0700339 // 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>();
Christopher Tate79ec80d2011-06-24 14:58:49 -0700343
Matthew Williams303650c2015-04-17 18:22:51 -0700344 // Add the directories we always exclude.
Jeff Sharkey2c1ba9a2016-02-17 15:29:38 -0700345 traversalExcludeSet.add(filesDir);
346 traversalExcludeSet.add(noBackupDir);
347 traversalExcludeSet.add(databaseDir);
348 traversalExcludeSet.add(sharedPrefsDir);
Matthew Williams303650c2015-04-17 18:22:51 -0700349 traversalExcludeSet.add(cacheDir);
350 traversalExcludeSet.add(codeCacheDir);
Jeff Sharkey2c1ba9a2016-02-17 15:29:38 -0700351
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
Christopher Tate79ec80d2011-06-24 14:58:49 -0700359 if (libDir != null) {
Matthew Williams303650c2015-04-17 18:22:51 -0700360 traversalExcludeSet.add(libDir);
Christopher Tate79ec80d2011-06-24 14:58:49 -0700361 }
Christopher Tate79ec80d2011-06-24 14:58:49 -0700362
Matthew Williams303650c2015-04-17 18:22:51 -0700363 // Root dir first.
364 applyXmlFiltersAndDoFullBackupForDomain(
365 packageName, FullBackup.ROOT_TREE_TOKEN, manifestIncludeMap,
366 manifestExcludeSet, traversalExcludeSet, data);
367 traversalExcludeSet.add(rootDir);
Christopher Tate79ec80d2011-06-24 14:58:49 -0700368
Jeff Sharkey2c1ba9a2016-02-17 15:29:38 -0700369 applyXmlFiltersAndDoFullBackupForDomain(
370 packageName, FullBackup.DEVICE_ROOT_TREE_TOKEN, manifestIncludeMap,
371 manifestExcludeSet, traversalExcludeSet, data);
372 traversalExcludeSet.add(deviceRootDir);
373
Matthew Williams303650c2015-04-17 18:22:51 -0700374 // Data dir next.
375 traversalExcludeSet.remove(filesDir);
376 applyXmlFiltersAndDoFullBackupForDomain(
Jeff Sharkey2c1ba9a2016-02-17 15:29:38 -0700377 packageName, FullBackup.FILES_TREE_TOKEN, manifestIncludeMap,
Matthew Williams303650c2015-04-17 18:22:51 -0700378 manifestExcludeSet, traversalExcludeSet, data);
379 traversalExcludeSet.add(filesDir);
380
Jeff Sharkey2c1ba9a2016-02-17 15:29:38 -0700381 traversalExcludeSet.remove(deviceFilesDir);
382 applyXmlFiltersAndDoFullBackupForDomain(
383 packageName, FullBackup.DEVICE_FILES_TREE_TOKEN, manifestIncludeMap,
384 manifestExcludeSet, traversalExcludeSet, data);
385 traversalExcludeSet.add(deviceFilesDir);
386
Matthew Williams303650c2015-04-17 18:22:51 -0700387 // Database directory.
388 traversalExcludeSet.remove(databaseDir);
389 applyXmlFiltersAndDoFullBackupForDomain(
390 packageName, FullBackup.DATABASE_TREE_TOKEN, manifestIncludeMap,
391 manifestExcludeSet, traversalExcludeSet, data);
392 traversalExcludeSet.add(databaseDir);
393
Jeff Sharkey2c1ba9a2016-02-17 15:29:38 -0700394 traversalExcludeSet.remove(deviceDatabaseDir);
395 applyXmlFiltersAndDoFullBackupForDomain(
396 packageName, FullBackup.DEVICE_DATABASE_TREE_TOKEN, manifestIncludeMap,
397 manifestExcludeSet, traversalExcludeSet, data);
398 traversalExcludeSet.add(deviceDatabaseDir);
399
Matthew Williams303650c2015-04-17 18:22:51 -0700400 // SharedPrefs.
401 traversalExcludeSet.remove(sharedPrefsDir);
402 applyXmlFiltersAndDoFullBackupForDomain(
403 packageName, FullBackup.SHAREDPREFS_TREE_TOKEN, manifestIncludeMap,
404 manifestExcludeSet, traversalExcludeSet, data);
405 traversalExcludeSet.add(sharedPrefsDir);
Christopher Tate416c39e2013-02-14 16:55:46 -0800406
Jeff Sharkey2c1ba9a2016-02-17 15:29:38 -0700407 traversalExcludeSet.remove(deviceSharedPrefsDir);
408 applyXmlFiltersAndDoFullBackupForDomain(
409 packageName, FullBackup.DEVICE_SHAREDPREFS_TREE_TOKEN, manifestIncludeMap,
410 manifestExcludeSet, traversalExcludeSet, data);
411 traversalExcludeSet.add(deviceSharedPrefsDir);
412
Christopher Tate416c39e2013-02-14 16:55:46 -0800413 // 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
Christopher Tate5cb5c332013-02-21 14:32:12 -0800416 // 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) {
Matthew Williams303650c2015-04-17 18:22:51 -0700423 applyXmlFiltersAndDoFullBackupForDomain(
424 packageName, FullBackup.MANAGED_EXTERNAL_TREE_TOKEN, manifestIncludeMap,
425 manifestExcludeSet, traversalExcludeSet, data);
426 }
427
428 }
429 }
430
431 /**
Christopher Tated43879c2016-04-04 12:54:43 -0700432 * 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.
Sergey Poromov872d3b62016-01-12 15:48:08 +0100437 *
Christopher Tated43879c2016-04-04 12:54:43 -0700438 * <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.
Sergey Poromov872d3b62016-01-12 15:48:08 +0100457 */
458 public void onQuotaExceeded(long backupDataBytes, long quotaBytes) {
459 }
460
461 /**
Matthew Williams303650c2015-04-17 18:22:51 -0700462 * 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 */
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);
Christopher Tate5cb5c332013-02-21 14:32:12 -0800483 }
484 }
Christopher Tate75a99702011-05-18 16:28:19 -0700485 }
486
487 /**
Christopher Tate79ec80d2011-06-24 14:58:49 -0700488 * 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.
Christopher Tate79ec80d2011-06-24 14:58:49 -0700492 *
Christopher Tatec5be8f82016-04-25 14:41:50 -0700493 * <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.
Elliot Waite54de7742017-01-11 15:30:35 -0800496 * See {@link #onFullBackup(FullBackupDataOutput)} for details on what directories
Christopher Tatec5be8f82016-04-25 14:41:50 -0700497 * are excluded from backups.
Christopher Tatea7835b62014-07-11 17:25:57 -0700498 *
Christopher Tate79ec80d2011-06-24 14:58:49 -0700499 * @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.
Christopher Tate4a627c72011-04-01 14:43:32 -0700502 */
Christopher Tate79ec80d2011-06-24 14:58:49 -0700503 public final void fullBackupFile(File file, FullBackupDataOutput output) {
504 // Look up where all of our various well-defined dir trees live on this device
Jeff Sharkey2c1ba9a2016-02-17 15:29:38 -0700505 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
Christopher Tate5cb5c332013-02-21 14:32:12 -0800521 String efDir = null;
Christopher Tate2efd2db2011-07-19 16:32:49 -0700522 String filePath;
Christopher Tate79ec80d2011-06-24 14:58:49 -0700523
524 ApplicationInfo appInfo = getApplicationInfo();
525
Christopher Tate2efd2db2011-07-19 16:32:49 -0700526 try {
Jeff Sharkey2c1ba9a2016-02-17 15:29:38 -0700527 // System apps have control over where their default storage context
528 // is pointed, so we're always explicit when building paths.
Jeff Sharkey8a372a02016-03-16 16:25:45 -0600529 final Context ceContext = createCredentialProtectedStorageContext();
Jeff Sharkey2c1ba9a2016-02-17 15:29:38 -0700530 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
Jeff Sharkey8a372a02016-03-16 16:25:45 -0600538 final Context deContext = createDeviceProtectedStorageContext();
Jeff Sharkey2c1ba9a2016-02-17 15:29:38 -0700539 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
Christopher Tate2efd2db2011-07-19 16:32:49 -0700548 libDir = (appInfo.nativeLibraryDir == null)
549 ? null
550 : new File(appInfo.nativeLibraryDir).getCanonicalPath();
Christopher Tate5cb5c332013-02-21 14:32:12 -0800551
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 }
Christopher Tate79ec80d2011-06-24 14:58:49 -0700559
Christopher Tate2efd2db2011-07-19 16:32:49 -0700560 // Now figure out which well-defined tree the file is placed in, working from
Christopher Tatea8a739f2015-03-05 18:31:38 -0800561 // most to least specific. We also specifically exclude the lib, cache,
562 // and code_cache dirs.
Christopher Tate2efd2db2011-07-19 16:32:49 -0700563 filePath = file.getCanonicalPath();
564 } catch (IOException e) {
565 Log.w(TAG, "Unable to obtain canonical paths");
566 return;
567 }
Christopher Tate79ec80d2011-06-24 14:58:49 -0700568
Christopher Tatea7835b62014-07-11 17:25:57 -0700569 if (filePath.startsWith(cacheDir)
Christopher Tatea8a739f2015-03-05 18:31:38 -0800570 || filePath.startsWith(codeCacheDir)
Jeff Sharkey2c1ba9a2016-02-17 15:29:38 -0700571 || filePath.startsWith(nbFilesDir)
572 || filePath.startsWith(deviceCacheDir)
573 || filePath.startsWith(deviceCodeCacheDir)
574 || filePath.startsWith(deviceNbFilesDir)
575 || filePath.startsWith(libDir)) {
Christopher Tatea8a739f2015-03-05 18:31:38 -0800576 Log.w(TAG, "lib, cache, code_cache, and no_backup files are not backed up");
Christopher Tate79ec80d2011-06-24 14:58:49 -0700577 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)) {
Jeff Sharkey2c1ba9a2016-02-17 15:29:38 -0700589 domain = FullBackup.FILES_TREE_TOKEN;
Christopher Tate79ec80d2011-06-24 14:58:49 -0700590 rootpath = filesDir;
Jeff Sharkey2c1ba9a2016-02-17 15:29:38 -0700591 } else if (filePath.startsWith(rootDir)) {
Christopher Tate79ec80d2011-06-24 14:58:49 -0700592 domain = FullBackup.ROOT_TREE_TOKEN;
Jeff Sharkey2c1ba9a2016-02-17 15:29:38 -0700593 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;
Christopher Tate5cb5c332013-02-21 14:32:12 -0800606 } else if ((efDir != null) && filePath.startsWith(efDir)) {
Christopher Tate416c39e2013-02-14 16:55:46 -0800607 domain = FullBackup.MANAGED_EXTERNAL_TREE_TOKEN;
608 rootpath = efDir;
Christopher Tate79ec80d2011-06-24 14:58:49 -0700609 } 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
Christopher Tate11ae7682015-03-24 18:48:10 -0700615 // 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
Christopher Tate79ec80d2011-06-24 14:58:49 -0700618 + " rootpath=" + rootpath);
Matthew Williams303650c2015-04-17 18:22:51 -0700619
Christopher Tate11ae7682015-03-24 18:48:10 -0700620 FullBackup.backupToTar(getPackageName(), domain, null, rootpath, filePath, output);
Christopher Tate79ec80d2011-06-24 14:58:49 -0700621 }
622
623 /**
624 * Scan the dir tree (if it actually exists) and process each entry we find. If the
Matthew Williams303650c2015-04-17 18:22:51 -0700625 * 'excludes' parameters are non-null, they are consulted each time a new file system entity
Christopher Tate79ec80d2011-06-24 14:58:49 -0700626 * is visited to see whether that entity (and its subtree, if appropriate) should be
627 * omitted from the backup process.
628 *
Matthew Williams303650c2015-04-17 18:22:51 -0700629 * @param systemExcludes An optional list of excludes.
Christopher Tate79ec80d2011-06-24 14:58:49 -0700630 * @hide
631 */
Matthew Williams303650c2015-04-17 18:22:51 -0700632 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);
Christopher Tate79ec80d2011-06-24 14:58:49 -0700644 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);
Christopher Tate2efd2db2011-07-19 16:32:49 -0700650 String filePath;
Christopher Tate79ec80d2011-06-24 14:58:49 -0700651 try {
Christopher Tateda2018e2016-10-13 12:12:29 -0700652 // Ignore things that aren't "real" files or dirs
Christopher Tate85192a12015-09-24 10:21:54 -0700653 StructStat stat = Os.lstat(file.getPath());
Christopher Tateda2018e2016-10-13 12:12:29 -0700654 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);
Christopher Tate85192a12015-09-24 10:21:54 -0700657 continue;
658 }
659
660 // For all other verification, look at the canonicalized path
Christopher Tate2efd2db2011-07-19 16:32:49 -0700661 filePath = file.getCanonicalPath();
662
663 // prune this subtree?
Matthew Williams303650c2015-04-17 18:22:51 -0700664 if (manifestExcludes != null && manifestExcludes.contains(filePath)) {
665 continue;
666 }
667 if (systemExcludes != null && systemExcludes.contains(filePath)) {
Christopher Tate2efd2db2011-07-19 16:32:49 -0700668 continue;
669 }
670
671 // If it's a directory, enqueue its contents for scanning.
Christopher Tate85192a12015-09-24 10:21:54 -0700672 if (OsConstants.S_ISDIR(stat.st_mode)) {
Christopher Tate79ec80d2011-06-24 14:58:49 -0700673 File[] contents = file.listFiles();
674 if (contents != null) {
675 for (File entry : contents) {
676 scanQueue.add(0, entry);
677 }
678 }
679 }
Christopher Tate2efd2db2011-07-19 16:32:49 -0700680 } catch (IOException e) {
681 if (DEBUG) Log.w(TAG, "Error canonicalizing path of " + file);
Matthew Williams303650c2015-04-17 18:22:51 -0700682 if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
683 Log.v(FullBackup.TAG_XML_PARSER, "Error canonicalizing path of " + file);
684 }
Christopher Tate2efd2db2011-07-19 16:32:49 -0700685 continue;
Christopher Tate79ec80d2011-06-24 14:58:49 -0700686 } catch (ErrnoException e) {
687 if (DEBUG) Log.w(TAG, "Error scanning file " + file + " : " + e);
Matthew Williams303650c2015-04-17 18:22:51 -0700688 if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
689 Log.v(FullBackup.TAG_XML_PARSER, "Error scanning file " + file + " : " + e);
690 }
Christopher Tate79ec80d2011-06-24 14:58:49 -0700691 continue;
692 }
693
Christopher Tate11ae7682015-03-24 18:48:10 -0700694 // Finally, back this file up (or measure it) before proceeding
Matthew Williams303650c2015-04-17 18:22:51 -0700695 FullBackup.backupToTar(packageName, domain, null, domainPath, filePath, output);
Christopher Tate79ec80d2011-06-24 14:58:49 -0700696 }
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 */
726 public void onRestoreFile(ParcelFileDescriptor data, long size,
727 File destination, int type, long mode, long mtime)
728 throws IOException {
Matthew Williamsb9ebed52015-08-05 18:27:44 -0700729
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
735 private boolean isFileEligibleForRestore(File destination) throws IOException {
Matthew Williams303650c2015-04-17 18:22:51 -0700736 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 }
Matthew Williamsb9ebed52015-08-05 18:27:44 -0700743 return false;
Matthew Williams303650c2015-04-17 18:22:51 -0700744 }
Matthew Williamsb9ebed52015-08-05 18:27:44 -0700745
Matthew Williams303650c2015-04-17 18:22:51 -0700746 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 }
Matthew Williamsb9ebed52015-08-05 18:27:44 -0700759 return false;
Matthew Williams303650c2015-04-17 18:22:51 -0700760 }
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 }
Matthew Williamsb9ebed52015-08-05 18:27:44 -0700769 return false;
Matthew Williams303650c2015-04-17 18:22:51 -0700770 }
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 }
Matthew Williamsb9ebed52015-08-05 18:27:44 -0700789 return false;
Matthew Williams303650c2015-04-17 18:22:51 -0700790 }
791 }
Matthew Williamsb9ebed52015-08-05 18:27:44 -0700792 return true;
Christopher Tate79ec80d2011-06-24 14:58:49 -0700793 }
794
795 /**
Matthew Williams303650c2015-04-17 18:22:51 -0700796 * @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 */
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 /**
Christopher Tate79ec80d2011-06-24 14:58:49 -0700822 * Only specialized platform agents should overload this entry point to support
823 * restores to crazy non-app locations.
824 * @hide
825 */
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
Matthew Williams303650c2015-04-17 18:22:51 -0700835 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()
Christopher Tate79ec80d2011-06-24 14:58:49 -0700838 }
839
840 // Now that we've figured out where the data goes, send it on its way
841 if (basePath != null) {
Christopher Tate73237652013-03-25 10:06:34 -0700842 // Canonicalize the nominal path and verify that it lies within the stated domain
Christopher Tate79ec80d2011-06-24 14:58:49 -0700843 File outFile = new File(basePath, path);
Christopher Tate73237652013-03-25 10:06:34 -0700844 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 }
Christopher Tate79ec80d2011-06-24 14:58:49 -0700855 }
Christopher Tate73237652013-03-25 10:06:34 -0700856
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);
Christopher Tate4a627c72011-04-01 14:43:32 -0700862 }
Christopher Tate487529a2009-04-29 14:03:25 -0700863
Christopher Tate2e40d112014-07-15 12:37:38 -0700864 /**
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 */
875 public void onRestoreFinished() {
876 }
877
Christopher Tate487529a2009-04-29 14:03:25 -0700878 // ----- Core implementation -----
Christopher Tate44a27902010-01-27 17:15:49 -0800879
880 /** @hide */
881 public final IBinder onBind() {
Christopher Tate181fafa2009-05-14 11:12:14 -0700882 return mBinder;
Christopher Tate487529a2009-04-29 14:03:25 -0700883 }
884
885 private final IBinder mBinder = new BackupServiceBinder().asBinder();
886
Christopher Tate181fafa2009-05-14 11:12:14 -0700887 /** @hide */
888 public void attach(Context context) {
889 attachBaseContext(context);
890 }
891
Christopher Tate487529a2009-04-29 14:03:25 -0700892 // ----- IBackupService binder interface -----
Christopher Tate181fafa2009-05-14 11:12:14 -0700893 private class BackupServiceBinder extends IBackupAgent.Stub {
894 private static final String TAG = "BackupServiceBinder";
895
Christopher Tate75a99702011-05-18 16:28:19 -0700896 @Override
Christopher Tate22b87872009-05-04 16:41:53 -0700897 public void doBackup(ParcelFileDescriptor oldState,
898 ParcelFileDescriptor data,
Christopher Tate44a27902010-01-27 17:15:49 -0800899 ParcelFileDescriptor newState,
Shreyas Basargeb6e73c92017-01-31 20:13:43 +0000900 long quotaBytes, int token, IBackupManager callbackBinder) throws RemoteException {
Christopher Tate19024922010-01-22 16:39:53 -0800901 // Ensure that we're running with the app's normal permission level
Christopher Tate44a27902010-01-27 17:15:49 -0800902 long ident = Binder.clearCallingIdentity();
Christopher Tate19024922010-01-22 16:39:53 -0800903
Christopher Tate436344a2009-09-30 16:17:37 -0700904 if (DEBUG) Log.v(TAG, "doBackup() invoked");
Christopher Tateee87b962017-04-26 17:07:27 -0700905 BackupDataOutput output = new BackupDataOutput(data.getFileDescriptor(), quotaBytes);
Christopher Tate4a627c72011-04-01 14:43:32 -0700906
Joe Onorato290bb012009-05-13 18:57:29 -0400907 try {
Christopher Tate181fafa2009-05-14 11:12:14 -0700908 BackupAgent.this.onBackup(oldState, output, newState);
Joe Onorato4ababd92009-06-25 18:29:18 -0400909 } catch (IOException ex) {
910 Log.d(TAG, "onBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
911 throw new RuntimeException(ex);
Joe Onorato290bb012009-05-13 18:57:29 -0400912 } catch (RuntimeException ex) {
Joe Onorato83248c42009-06-17 17:55:20 -0700913 Log.d(TAG, "onBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
Joe Onorato290bb012009-05-13 18:57:29 -0400914 throw ex;
Christopher Tate19024922010-01-22 16:39:53 -0800915 } finally {
Christopher Tatef85f5b22013-04-18 16:57:43 -0700916 // 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
Christopher Tate44a27902010-01-27 17:15:49 -0800921 Binder.restoreCallingIdentity(ident);
922 try {
Christopher Tate11ae7682015-03-24 18:48:10 -0700923 callbackBinder.opComplete(token, 0);
Christopher Tate44a27902010-01-27 17:15:49 -0800924 } catch (RemoteException e) {
925 // we'll time out anyway, so we're safe
926 }
Christopher Tate91bb0e52016-09-30 17:52:19 -0700927
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 }
Joe Onorato290bb012009-05-13 18:57:29 -0400934 }
Christopher Tate487529a2009-04-29 14:03:25 -0700935 }
936
Christopher Tate75a99702011-05-18 16:28:19 -0700937 @Override
Christopher Tate5cbbf562009-06-22 16:44:51 -0700938 public void doRestore(ParcelFileDescriptor data, int appVersionCode,
Christopher Tate44a27902010-01-27 17:15:49 -0800939 ParcelFileDescriptor newState,
940 int token, IBackupManager callbackBinder) throws RemoteException {
Christopher Tate19024922010-01-22 16:39:53 -0800941 // Ensure that we're running with the app's normal permission level
Christopher Tate44a27902010-01-27 17:15:49 -0800942 long ident = Binder.clearCallingIdentity();
Christopher Tate19024922010-01-22 16:39:53 -0800943
Christopher Tate436344a2009-09-30 16:17:37 -0700944 if (DEBUG) Log.v(TAG, "doRestore() invoked");
Christopher Tatefe2368c2017-05-17 15:42:35 -0700945
946 // Ensure that any side-effect SharedPreferences writes have landed *before*
947 // we may be about to rewrite the file out from underneath
948 waitForSharedPrefs();
949
Joe Onorato83248c42009-06-17 17:55:20 -0700950 BackupDataInput input = new BackupDataInput(data.getFileDescriptor());
951 try {
Christopher Tate5cbbf562009-06-22 16:44:51 -0700952 BackupAgent.this.onRestore(input, appVersionCode, newState);
Joe Onorato83248c42009-06-17 17:55:20 -0700953 } catch (IOException ex) {
954 Log.d(TAG, "onRestore (" + BackupAgent.this.getClass().getName() + ") threw", ex);
955 throw new RuntimeException(ex);
956 } catch (RuntimeException ex) {
957 Log.d(TAG, "onRestore (" + BackupAgent.this.getClass().getName() + ") threw", ex);
958 throw ex;
Christopher Tate19024922010-01-22 16:39:53 -0800959 } finally {
Christopher Tatefe2368c2017-05-17 15:42:35 -0700960 // And bring live SharedPreferences instances up to date
961 reloadSharedPreferences();
Christopher Tatef85f5b22013-04-18 16:57:43 -0700962
Christopher Tate44a27902010-01-27 17:15:49 -0800963 Binder.restoreCallingIdentity(ident);
964 try {
Christopher Tate11ae7682015-03-24 18:48:10 -0700965 callbackBinder.opComplete(token, 0);
Christopher Tate44a27902010-01-27 17:15:49 -0800966 } catch (RemoteException e) {
967 // we'll time out anyway, so we're safe
968 }
Christopher Tate91bb0e52016-09-30 17:52:19 -0700969
970 if (Binder.getCallingPid() != Process.myPid()) {
971 IoUtils.closeQuietly(data);
972 IoUtils.closeQuietly(newState);
973 }
Joe Onorato83248c42009-06-17 17:55:20 -0700974 }
Christopher Tate487529a2009-04-29 14:03:25 -0700975 }
Christopher Tate75a99702011-05-18 16:28:19 -0700976
977 @Override
Christopher Tate79ec80d2011-06-24 14:58:49 -0700978 public void doFullBackup(ParcelFileDescriptor data,
Shreyas Basargeb6e73c92017-01-31 20:13:43 +0000979 long quotaBytes, int token, IBackupManager callbackBinder) {
Christopher Tate79ec80d2011-06-24 14:58:49 -0700980 // Ensure that we're running with the app's normal permission level
981 long ident = Binder.clearCallingIdentity();
982
983 if (DEBUG) Log.v(TAG, "doFullBackup() invoked");
Christopher Tate79ec80d2011-06-24 14:58:49 -0700984
Christopher Tatef85f5b22013-04-18 16:57:43 -0700985 // Ensure that any SharedPreferences writes have landed *before*
986 // we potentially try to back up the underlying files directly.
987 waitForSharedPrefs();
988
Christopher Tate79ec80d2011-06-24 14:58:49 -0700989 try {
Christopher Tateee87b962017-04-26 17:07:27 -0700990 BackupAgent.this.onFullBackup(new FullBackupDataOutput(data, quotaBytes));
Christopher Tate79ec80d2011-06-24 14:58:49 -0700991 } catch (IOException ex) {
Christopher Tate11ae7682015-03-24 18:48:10 -0700992 Log.d(TAG, "onFullBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
Christopher Tate79ec80d2011-06-24 14:58:49 -0700993 throw new RuntimeException(ex);
994 } catch (RuntimeException ex) {
Christopher Tate11ae7682015-03-24 18:48:10 -0700995 Log.d(TAG, "onFullBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
Christopher Tate79ec80d2011-06-24 14:58:49 -0700996 throw ex;
997 } finally {
Christopher Tatef85f5b22013-04-18 16:57:43 -0700998 // ... and then again after, as in the doBackup() case
999 waitForSharedPrefs();
1000
Christopher Tate7926a692011-07-11 11:31:57 -07001001 // Send the EOD marker indicating that there is no more data
1002 // forthcoming from this agent.
1003 try {
1004 FileOutputStream out = new FileOutputStream(data.getFileDescriptor());
1005 byte[] buf = new byte[4];
1006 out.write(buf);
1007 } catch (IOException e) {
1008 Log.e(TAG, "Unable to finalize backup stream!");
1009 }
1010
Christopher Tate79ec80d2011-06-24 14:58:49 -07001011 Binder.restoreCallingIdentity(ident);
1012 try {
Christopher Tate11ae7682015-03-24 18:48:10 -07001013 callbackBinder.opComplete(token, 0);
Christopher Tate79ec80d2011-06-24 14:58:49 -07001014 } catch (RemoteException e) {
1015 // we'll time out anyway, so we're safe
1016 }
Christopher Tate91bb0e52016-09-30 17:52:19 -07001017
1018 if (Binder.getCallingPid() != Process.myPid()) {
1019 IoUtils.closeQuietly(data);
1020 }
Christopher Tate79ec80d2011-06-24 14:58:49 -07001021 }
1022 }
1023
Shreyas Basargeb6e73c92017-01-31 20:13:43 +00001024 public void doMeasureFullBackup(long quotaBytes, int token, IBackupManager callbackBinder) {
Christopher Tate11ae7682015-03-24 18:48:10 -07001025 // Ensure that we're running with the app's normal permission level
1026 final long ident = Binder.clearCallingIdentity();
Christopher Tateee87b962017-04-26 17:07:27 -07001027 FullBackupDataOutput measureOutput = new FullBackupDataOutput(quotaBytes);
Shreyas Basargeb6e73c92017-01-31 20:13:43 +00001028
Christopher Tate11ae7682015-03-24 18:48:10 -07001029 waitForSharedPrefs();
1030 try {
1031 BackupAgent.this.onFullBackup(measureOutput);
1032 } catch (IOException ex) {
1033 Log.d(TAG, "onFullBackup[M] (" + BackupAgent.this.getClass().getName() + ") threw", ex);
1034 throw new RuntimeException(ex);
1035 } catch (RuntimeException ex) {
1036 Log.d(TAG, "onFullBackup[M] (" + BackupAgent.this.getClass().getName() + ") threw", ex);
1037 throw ex;
1038 } finally {
1039 Binder.restoreCallingIdentity(ident);
1040 try {
1041 callbackBinder.opComplete(token, measureOutput.getSize());
1042 } catch (RemoteException e) {
1043 // timeout, so we're safe
1044 }
1045 }
1046 }
1047
Christopher Tate79ec80d2011-06-24 14:58:49 -07001048 @Override
Christopher Tate75a99702011-05-18 16:28:19 -07001049 public void doRestoreFile(ParcelFileDescriptor data, long size,
1050 int type, String domain, String path, long mode, long mtime,
1051 int token, IBackupManager callbackBinder) throws RemoteException {
1052 long ident = Binder.clearCallingIdentity();
1053 try {
Christopher Tate75a99702011-05-18 16:28:19 -07001054 BackupAgent.this.onRestoreFile(data, size, type, domain, path, mode, mtime);
1055 } catch (IOException e) {
Christopher Tate11ae7682015-03-24 18:48:10 -07001056 Log.d(TAG, "onRestoreFile (" + BackupAgent.this.getClass().getName() + ") threw", e);
Christopher Tate75a99702011-05-18 16:28:19 -07001057 throw new RuntimeException(e);
1058 } finally {
Christopher Tatef85f5b22013-04-18 16:57:43 -07001059 // Ensure that any side-effect SharedPreferences writes have landed
1060 waitForSharedPrefs();
Christopher Tatefe2368c2017-05-17 15:42:35 -07001061 // And bring live SharedPreferences instances up to date
1062 reloadSharedPreferences();
Christopher Tatef85f5b22013-04-18 16:57:43 -07001063
Christopher Tate75a99702011-05-18 16:28:19 -07001064 Binder.restoreCallingIdentity(ident);
1065 try {
Christopher Tate11ae7682015-03-24 18:48:10 -07001066 callbackBinder.opComplete(token, 0);
Christopher Tate75a99702011-05-18 16:28:19 -07001067 } catch (RemoteException e) {
1068 // we'll time out anyway, so we're safe
1069 }
Christopher Tate91bb0e52016-09-30 17:52:19 -07001070
1071 if (Binder.getCallingPid() != Process.myPid()) {
1072 IoUtils.closeQuietly(data);
1073 }
Christopher Tate75a99702011-05-18 16:28:19 -07001074 }
1075 }
Christopher Tatecba59412014-04-01 10:38:29 -07001076
1077 @Override
Christopher Tate2e40d112014-07-15 12:37:38 -07001078 public void doRestoreFinished(int token, IBackupManager callbackBinder) {
1079 long ident = Binder.clearCallingIdentity();
1080 try {
1081 BackupAgent.this.onRestoreFinished();
Christopher Tate11ae7682015-03-24 18:48:10 -07001082 } catch (Exception e) {
1083 Log.d(TAG, "onRestoreFinished (" + BackupAgent.this.getClass().getName() + ") threw", e);
1084 throw e;
Christopher Tate2e40d112014-07-15 12:37:38 -07001085 } finally {
1086 // Ensure that any side-effect SharedPreferences writes have landed
1087 waitForSharedPrefs();
1088
1089 Binder.restoreCallingIdentity(ident);
1090 try {
Christopher Tate11ae7682015-03-24 18:48:10 -07001091 callbackBinder.opComplete(token, 0);
Christopher Tate2e40d112014-07-15 12:37:38 -07001092 } catch (RemoteException e) {
1093 // we'll time out anyway, so we're safe
1094 }
1095 }
1096 }
1097
1098 @Override
Christopher Tatecba59412014-04-01 10:38:29 -07001099 public void fail(String message) {
1100 getHandler().post(new FailRunnable(message));
1101 }
Sergey Poromov872d3b62016-01-12 15:48:08 +01001102
1103 @Override
1104 public void doQuotaExceeded(long backupDataBytes, long quotaBytes) {
1105 long ident = Binder.clearCallingIdentity();
1106 try {
1107 BackupAgent.this.onQuotaExceeded(backupDataBytes, quotaBytes);
1108 } catch (Exception e) {
1109 Log.d(TAG, "onQuotaExceeded(" + BackupAgent.this.getClass().getName() + ") threw",
1110 e);
1111 throw e;
1112 } finally {
1113 waitForSharedPrefs();
1114 Binder.restoreCallingIdentity(ident);
1115 }
1116 }
Christopher Tatecba59412014-04-01 10:38:29 -07001117 }
1118
1119 static class FailRunnable implements Runnable {
1120 private String mMessage;
1121
1122 FailRunnable(String message) {
1123 mMessage = message;
1124 }
1125
1126 @Override
1127 public void run() {
1128 throw new IllegalStateException(mMessage);
1129 }
Christopher Tate487529a2009-04-29 14:03:25 -07001130 }
1131}