Add Context.getNoBackupFilesDir()
This is an app-private filesystem space exactly like the one reported by Context.getFilesDir(), with one exception: files placed here are never backed up by the full-backup infrastructure. If an app attempts to back up any of its contents via the normal API it's immediately ignored with a logged warning. The restriction is also enforced on the restore side, because apps using support libraries might wind up creating full backup archives containing no_backup subdirs on pre-L devices (via adb backup, Helium, &c.). We check for this before passing the restore data to the app, and drop it if we detect the situation so that the app never sees the bits. Bug 16240573 Change-Id: I11216a391f1d32117ec7ce15aafc9cd93d0337de
This commit is contained in:
committed by
Christopher Tate
parent
4ad58a4738
commit
a7835b6b6b
@ -7061,6 +7061,7 @@ package android.content {
|
||||
method public abstract java.io.File getFileStreamPath(java.lang.String);
|
||||
method public abstract java.io.File getFilesDir();
|
||||
method public abstract android.os.Looper getMainLooper();
|
||||
method public abstract java.io.File getNoBackupFilesDir();
|
||||
method public abstract java.io.File getObbDir();
|
||||
method public abstract java.io.File[] getObbDirs();
|
||||
method public abstract java.lang.String getPackageCodePath();
|
||||
@ -7230,6 +7231,7 @@ package android.content {
|
||||
method public java.io.File getFileStreamPath(java.lang.String);
|
||||
method public java.io.File getFilesDir();
|
||||
method public android.os.Looper getMainLooper();
|
||||
method public java.io.File getNoBackupFilesDir();
|
||||
method public java.io.File getObbDir();
|
||||
method public java.io.File[] getObbDirs();
|
||||
method public java.lang.String getPackageCodePath();
|
||||
@ -29309,6 +29311,7 @@ package android.test.mock {
|
||||
method public java.io.File getFileStreamPath(java.lang.String);
|
||||
method public java.io.File getFilesDir();
|
||||
method public android.os.Looper getMainLooper();
|
||||
method public java.io.File getNoBackupFilesDir();
|
||||
method public java.io.File getObbDir();
|
||||
method public java.io.File[] getObbDirs();
|
||||
method public java.lang.String getPackageCodePath();
|
||||
|
@ -247,6 +247,8 @@ class ContextImpl extends Context {
|
||||
@GuardedBy("mSync")
|
||||
private File mFilesDir;
|
||||
@GuardedBy("mSync")
|
||||
private File mNoBackupFilesDir;
|
||||
@GuardedBy("mSync")
|
||||
private File mCacheDir;
|
||||
|
||||
@GuardedBy("mSync")
|
||||
@ -963,27 +965,42 @@ class ContextImpl extends Context {
|
||||
return f.delete();
|
||||
}
|
||||
|
||||
// Common-path handling of app data dir creation
|
||||
private static File createFilesDirLocked(File file) {
|
||||
if (!file.exists()) {
|
||||
if (!file.mkdirs()) {
|
||||
if (file.exists()) {
|
||||
// spurious failure; probably racing with another process for this app
|
||||
return file;
|
||||
}
|
||||
Log.w(TAG, "Unable to create files subdir " + file.getPath());
|
||||
return null;
|
||||
}
|
||||
FileUtils.setPermissions(
|
||||
file.getPath(),
|
||||
FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
|
||||
-1, -1);
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getFilesDir() {
|
||||
synchronized (mSync) {
|
||||
if (mFilesDir == null) {
|
||||
mFilesDir = new File(getDataDirFile(), "files");
|
||||
}
|
||||
if (!mFilesDir.exists()) {
|
||||
if(!mFilesDir.mkdirs()) {
|
||||
if (mFilesDir.exists()) {
|
||||
// spurious failure; probably racing with another process for this app
|
||||
return mFilesDir;
|
||||
}
|
||||
Log.w(TAG, "Unable to create files directory " + mFilesDir.getPath());
|
||||
return null;
|
||||
}
|
||||
FileUtils.setPermissions(
|
||||
mFilesDir.getPath(),
|
||||
FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
|
||||
-1, -1);
|
||||
return createFilesDirLocked(mFilesDir);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getNoBackupFilesDir() {
|
||||
synchronized (mSync) {
|
||||
if (mNoBackupFilesDir == null) {
|
||||
mNoBackupFilesDir = new File(getDataDirFile(), "no_backup");
|
||||
}
|
||||
return mFilesDir;
|
||||
return createFilesDirLocked(mNoBackupFilesDir);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1035,22 +1052,8 @@ class ContextImpl extends Context {
|
||||
if (mCacheDir == null) {
|
||||
mCacheDir = new File(getDataDirFile(), "cache");
|
||||
}
|
||||
if (!mCacheDir.exists()) {
|
||||
if(!mCacheDir.mkdirs()) {
|
||||
if (mCacheDir.exists()) {
|
||||
// spurious failure; probably racing with another process for this app
|
||||
return mCacheDir;
|
||||
}
|
||||
Log.w(TAG, "Unable to create cache directory " + mCacheDir.getAbsolutePath());
|
||||
return null;
|
||||
}
|
||||
FileUtils.setPermissions(
|
||||
mCacheDir.getPath(),
|
||||
FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
|
||||
-1, -1);
|
||||
}
|
||||
return createFilesDirLocked(mCacheDir);
|
||||
}
|
||||
return mCacheDir;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -247,12 +247,40 @@ public abstract class BackupAgent extends ContextWrapper {
|
||||
throws IOException;
|
||||
|
||||
/**
|
||||
* The default implementation backs up the entirety of the application's "owned"
|
||||
* file system trees to the output.
|
||||
* The application is having its entire file system contents backed up. {@code data}
|
||||
* points to the backup destination, and the app has the opportunity to choose which
|
||||
* files are to be stored. To commit a file as part of the backup, call the
|
||||
* {@link #fullBackupFile(File, FullBackupDataOutput)} helper method. After all file
|
||||
* data is written to the output, the agent returns from this method and the backup
|
||||
* operation concludes.
|
||||
*
|
||||
* <p>Certain parts of the app's data are never backed up even if the app explicitly
|
||||
* sends them to the output:
|
||||
*
|
||||
* <ul>
|
||||
* <li>The contents of the {@link #getCacheDir()} directory</li>
|
||||
* <li>The contents of the {@link #getNoBackupFilesDir()} directory</li>
|
||||
* <li>The contents of the app's shared library directory</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>The default implementation of this method backs up the entirety of the
|
||||
* application's "owned" file system trees to the output other than the few exceptions
|
||||
* listed above. Apps only need to override this method if they need to impose special
|
||||
* limitations on which files are being stored beyond the control that
|
||||
* {@link #getNoBackupFilesDir()} offers.
|
||||
*
|
||||
* @param data A structured wrapper pointing to the backup destination.
|
||||
* @throws IOException
|
||||
*
|
||||
* @see Context#getNoBackupFilesDir()
|
||||
* @see #fullBackupFile(File, FullBackupDataOutput)
|
||||
* @see #onRestoreFile(ParcelFileDescriptor, long, File, int, long, long)
|
||||
*/
|
||||
public void onFullBackup(FullBackupDataOutput data) throws IOException {
|
||||
ApplicationInfo appInfo = getApplicationInfo();
|
||||
|
||||
// Note that we don't need to think about the no_backup dir because it's outside
|
||||
// all of the ones we will be traversing
|
||||
String rootDir = new File(appInfo.dataDir).getCanonicalPath();
|
||||
String filesDir = getFilesDir().getCanonicalPath();
|
||||
String databaseDir = getDatabasePath("foo").getParentFile().getCanonicalPath();
|
||||
@ -311,6 +339,10 @@ public abstract class BackupAgent extends ContextWrapper {
|
||||
* to place it with the proper location and permissions on the device where the
|
||||
* data is restored.
|
||||
*
|
||||
* <p class="note">It is safe to explicitly back up files underneath your application's
|
||||
* {@link #getNoBackupFilesDir()} directory, and they will be restored to that
|
||||
* location correctly.
|
||||
*
|
||||
* @param file The file to be backed up. The file must exist and be readable by
|
||||
* the caller.
|
||||
* @param output The destination to which the backed-up file data will be sent.
|
||||
@ -319,6 +351,7 @@ public abstract class BackupAgent extends ContextWrapper {
|
||||
// Look up where all of our various well-defined dir trees live on this device
|
||||
String mainDir;
|
||||
String filesDir;
|
||||
String nbFilesDir;
|
||||
String dbDir;
|
||||
String spDir;
|
||||
String cacheDir;
|
||||
@ -331,6 +364,7 @@ public abstract class BackupAgent extends ContextWrapper {
|
||||
try {
|
||||
mainDir = new File(appInfo.dataDir).getCanonicalPath();
|
||||
filesDir = getFilesDir().getCanonicalPath();
|
||||
nbFilesDir = getNoBackupFilesDir().getCanonicalPath();
|
||||
dbDir = getDatabasePath("foo").getParentFile().getCanonicalPath();
|
||||
spDir = getSharedPrefsFile("foo").getParentFile().getCanonicalPath();
|
||||
cacheDir = getCacheDir().getCanonicalPath();
|
||||
@ -354,8 +388,10 @@ public abstract class BackupAgent extends ContextWrapper {
|
||||
return;
|
||||
}
|
||||
|
||||
if (filePath.startsWith(cacheDir) || filePath.startsWith(libDir)) {
|
||||
Log.w(TAG, "lib and cache files are not backed up");
|
||||
if (filePath.startsWith(cacheDir)
|
||||
|| filePath.startsWith(libDir)
|
||||
|| filePath.startsWith(nbFilesDir)) {
|
||||
Log.w(TAG, "lib, cache, and no_backup files are not backed up");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -508,6 +544,8 @@ public abstract class BackupAgent extends ContextWrapper {
|
||||
mode = -1; // < 0 is a token to skip attempting a chmod()
|
||||
}
|
||||
}
|
||||
} else if (domain.equals(FullBackup.NO_BACKUP_TREE_TOKEN)) {
|
||||
basePath = getNoBackupFilesDir().getCanonicalPath();
|
||||
} else {
|
||||
// Not a supported location
|
||||
Log.i(TAG, "Unrecognized domain " + domain);
|
||||
|
@ -40,6 +40,7 @@ public class FullBackup {
|
||||
public static final String OBB_TREE_TOKEN = "obb";
|
||||
public static final String ROOT_TREE_TOKEN = "r";
|
||||
public static final String DATA_TREE_TOKEN = "f";
|
||||
public static final String NO_BACKUP_TREE_TOKEN = "nb";
|
||||
public static final String DATABASE_TREE_TOKEN = "db";
|
||||
public static final String SHAREDPREFS_TREE_TOKEN = "sp";
|
||||
public static final String MANAGED_EXTERNAL_TREE_TOKEN = "ef";
|
||||
|
@ -646,6 +646,26 @@ public abstract class Context {
|
||||
*/
|
||||
public abstract File getFilesDir();
|
||||
|
||||
/**
|
||||
* Returns the absolute path to the directory on the filesystem similar to
|
||||
* {@link #getFilesDir()}. The difference is that files placed under this
|
||||
* directory will be excluded from automatic backup to remote storage. See
|
||||
* {@link android.app.backup.BackupAgent BackupAgent} for a full discussion
|
||||
* of the automatic backup mechanism in Android.
|
||||
*
|
||||
* <p>No permissions are required to read or write to the returned path, since this
|
||||
* path is internal storage.
|
||||
*
|
||||
* @return The path of the directory holding application files that will not be
|
||||
* automatically backed up to remote storage.
|
||||
*
|
||||
* @see #openFileOutput
|
||||
* @see #getFileStreamPath
|
||||
* @see #getDir
|
||||
* @see android.app.backup.BackupAgent
|
||||
*/
|
||||
public abstract File getNoBackupFilesDir();
|
||||
|
||||
/**
|
||||
* Returns the absolute path to the directory on the primary external filesystem
|
||||
* (that is somewhere on {@link android.os.Environment#getExternalStorageDirectory()
|
||||
|
@ -200,7 +200,12 @@ public class ContextWrapper extends Context {
|
||||
public File getFilesDir() {
|
||||
return mBase.getFilesDir();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public File getNoBackupFilesDir() {
|
||||
return mBase.getNoBackupFilesDir();
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getExternalFilesDir(String type) {
|
||||
return mBase.getExternalFilesDir(type);
|
||||
|
@ -3922,6 +3922,11 @@ public class BackupManagerService extends IBackupManager.Stub {
|
||||
break;
|
||||
}
|
||||
|
||||
// Is it a *file* we need to drop?
|
||||
if (!isRestorableFile(info)) {
|
||||
okay = false;
|
||||
}
|
||||
|
||||
// If the policy is satisfied, go ahead and set up to pipe the
|
||||
// data to the agent.
|
||||
if (DEBUG && okay && mAgent != null) {
|
||||
@ -4082,9 +4087,9 @@ public class BackupManagerService extends IBackupManager.Stub {
|
||||
}
|
||||
}
|
||||
|
||||
// Problems setting up the agent communication, or an already-
|
||||
// ignored package: skip to the next tar stream entry by
|
||||
// reading and discarding this file.
|
||||
// Problems setting up the agent communication, an explicitly
|
||||
// dropped file, or an already-ignored package: skip to the
|
||||
// next stream entry by reading and discarding this file.
|
||||
if (!okay) {
|
||||
if (DEBUG) Slog.d(TAG, "[discarding file content]");
|
||||
long bytesToConsume = (info.size + 511) & ~511;
|
||||
@ -4691,6 +4696,31 @@ public class BackupManagerService extends IBackupManager.Stub {
|
||||
return info;
|
||||
}
|
||||
|
||||
private boolean isRestorableFile(FileMetadata info) {
|
||||
if (FullBackup.CACHE_TREE_TOKEN.equals(info.domain)) {
|
||||
if (MORE_DEBUG) {
|
||||
Slog.i(TAG, "Dropping cache file path " + info.path);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (FullBackup.ROOT_TREE_TOKEN.equals(info.domain)) {
|
||||
// It's possible this is "no-backup" dir contents in an archive stream
|
||||
// produced on a device running a version of the OS that predates that
|
||||
// API. Respect the no-backup intention and don't let the data get to
|
||||
// the app.
|
||||
if (info.path.startsWith("no_backup/")) {
|
||||
if (MORE_DEBUG) {
|
||||
Slog.i(TAG, "Dropping no_backup file path " + info.path);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise we think this file is good to go
|
||||
return true;
|
||||
}
|
||||
|
||||
private void HEXLOG(byte[] block) {
|
||||
int offset = 0;
|
||||
int todo = block.length;
|
||||
|
@ -174,6 +174,11 @@ public class MockContext extends Context {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getNoBackupFilesDir() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getExternalFilesDir(String type) {
|
||||
throw new UnsupportedOperationException();
|
||||
|
Reference in New Issue
Block a user