Add some tests for TarBackupReader

... and PerformAdbRestoreTask.

Involves some refactoring, splitting readAppManifest() into two methods.

Also a bit of cleanup: make private field actually private and use
static imports for some constants.

Bug: 38090803
Bug: 37619463
Test: runtest -p com.android.server.backup frameworks-services
Change-Id: Ic30a6c5a515da1efb67caaae6eb75f4313797d5c
This commit is contained in:
Artem Iglikov
2017-05-08 17:17:13 +01:00
parent 3494f15fc1
commit 13fffd267c
8 changed files with 798 additions and 568 deletions

View File

@@ -34,6 +34,8 @@ public class FileMetadata {
public long mode; // e.g. 0666 (actually int)
public long mtime; // last mod time, UTC time_t (actually int)
public long size; // bytes of content
public int version; // App version.
public boolean hasApk; // Whether backup file contains apk.
@Override
public String toString() {

View File

@@ -16,6 +16,16 @@
package com.android.server.backup.restore;
import static com.android.server.backup.RefactoredBackupManagerService.BACKUP_MANIFEST_FILENAME;
import static com.android.server.backup.RefactoredBackupManagerService.BACKUP_METADATA_FILENAME;
import static com.android.server.backup.RefactoredBackupManagerService.DEBUG;
import static com.android.server.backup.RefactoredBackupManagerService.MORE_DEBUG;
import static com.android.server.backup.RefactoredBackupManagerService.OP_TYPE_RESTORE_WAIT;
import static com.android.server.backup.RefactoredBackupManagerService.SHARED_BACKUP_AGENT_PACKAGE;
import static com.android.server.backup.RefactoredBackupManagerService.TAG;
import static com.android.server.backup.RefactoredBackupManagerService.TIMEOUT_RESTORE_INTERVAL;
import static com.android.server.backup.RefactoredBackupManagerService
.TIMEOUT_SHARED_BACKUP_INTERVAL;
import static com.android.server.backup.internal.BackupHandler.MSG_RESTORE_OPERATION_TIMEOUT;
import android.app.ApplicationThreadConstants;
@@ -52,58 +62,61 @@ import java.util.HashSet;
*/
public class FullRestoreEngine extends RestoreEngine {
private RefactoredBackupManagerService backupManagerService;
private final RefactoredBackupManagerService mBackupManagerService;
// Task in charge of monitoring timeouts
BackupRestoreTask mMonitorTask;
private final BackupRestoreTask mMonitorTask;
private final RestoreInstallObserver mInstallObserver = new RestoreInstallObserver();
private final RestoreDeleteObserver mDeleteObserver = new RestoreDeleteObserver();
// Dedicated observer, if any
IFullBackupRestoreObserver mObserver;
private IFullBackupRestoreObserver mObserver;
IBackupManagerMonitor mMonitor;
final IBackupManagerMonitor mMonitor;
// Where we're delivering the file data as we go
IBackupAgent mAgent;
private IBackupAgent mAgent;
// Are we permitted to only deliver a specific package's metadata?
PackageInfo mOnlyPackage;
final PackageInfo mOnlyPackage;
boolean mAllowApks;
boolean mAllowObbs;
final boolean mAllowApks;
private final boolean mAllowObbs;
// Which package are we currently handling data for?
String mAgentPackage;
private String mAgentPackage;
// Info for working with the target app process
ApplicationInfo mTargetApp;
private ApplicationInfo mTargetApp;
// Machinery for restoring OBBs
FullBackupObbConnection mObbConnection = null;
private FullBackupObbConnection mObbConnection = null;
// possible handling states for a given package in the restore dataset
final HashMap<String, RestorePolicy> mPackagePolicies
private final HashMap<String, RestorePolicy> mPackagePolicies
= new HashMap<>();
// installer package names for each encountered app, derived from the manifests
final HashMap<String, String> mPackageInstallers = new HashMap<>();
private final HashMap<String, String> mPackageInstallers = new HashMap<>();
// Signatures for a given package found in its manifest file
final HashMap<String, Signature[]> mManifestSignatures
private final HashMap<String, Signature[]> mManifestSignatures
= new HashMap<>();
// Packages we've already wiped data on when restoring their first file
final HashSet<String> mClearedPackages = new HashSet<>();
private final HashSet<String> mClearedPackages = new HashSet<>();
// How much data have we moved?
long mBytes;
private long mBytes;
// Working buffer
byte[] mBuffer;
final byte[] mBuffer;
// Pipes for moving data
ParcelFileDescriptor[] mPipes = null;
private ParcelFileDescriptor[] mPipes = null;
// Widget blob to be restored out-of-band
byte[] mWidgetData = null;
private byte[] mWidgetData = null;
final int mEphemeralOpToken;
@@ -111,7 +124,7 @@ public class FullRestoreEngine extends RestoreEngine {
BackupRestoreTask monitorTask, IFullBackupRestoreObserver observer,
IBackupManagerMonitor monitor, PackageInfo onlyPackage, boolean allowApks,
boolean allowObbs, int ephemeralOpToken) {
this.backupManagerService = backupManagerService;
mBackupManagerService = backupManagerService;
mEphemeralOpToken = ephemeralOpToken;
mMonitorTask = monitorTask;
mObserver = observer;
@@ -134,7 +147,7 @@ public class FullRestoreEngine extends RestoreEngine {
public boolean restoreOneFile(InputStream instream, boolean mustKillAgent, byte[] buffer,
PackageInfo onlyPackage, boolean allowApks, int token, IBackupManagerMonitor monitor) {
if (!isRunning()) {
Slog.w(RefactoredBackupManagerService.TAG, "Restore engine used after halting");
Slog.w(TAG, "Restore engine used after halting");
return false;
}
@@ -150,12 +163,12 @@ public class FullRestoreEngine extends RestoreEngine {
FileMetadata info;
try {
if (RefactoredBackupManagerService.MORE_DEBUG) {
Slog.v(RefactoredBackupManagerService.TAG, "Reading tar header for restoring file");
if (MORE_DEBUG) {
Slog.v(TAG, "Reading tar header for restoring file");
}
info = tarBackupReader.readTarHeaders();
if (info != null) {
if (RefactoredBackupManagerService.MORE_DEBUG) {
if (MORE_DEBUG) {
info.dump();
}
@@ -165,9 +178,7 @@ public class FullRestoreEngine extends RestoreEngine {
// one app's data but see a different app's on the wire
if (onlyPackage != null) {
if (!pkg.equals(onlyPackage.packageName)) {
Slog.w(RefactoredBackupManagerService.TAG,
"Expected data for " + onlyPackage
+ " but saw " + pkg);
Slog.w(TAG, "Expected data for " + onlyPackage + " but saw " + pkg);
setResult(RestoreEngine.TRANSPORT_FAILURE);
setRunning(false);
return false;
@@ -183,9 +194,8 @@ public class FullRestoreEngine extends RestoreEngine {
// Clean up the previous agent relationship if necessary,
// and let the observer know we're considering a new app.
if (mAgent != null) {
if (RefactoredBackupManagerService.DEBUG) {
Slog.d(RefactoredBackupManagerService.TAG,
"Saw new package; finalizing old one");
if (DEBUG) {
Slog.d(TAG, "Saw new package; finalizing old one");
}
// Now we're really done
tearDownPipes();
@@ -195,19 +205,20 @@ public class FullRestoreEngine extends RestoreEngine {
}
}
if (info.path.equals(RefactoredBackupManagerService.BACKUP_MANIFEST_FILENAME)) {
RestorePolicy appManifest = tarBackupReader.readAppManifest(
backupManagerService.getPackageManager(), allowApks,
mManifestSignatures, info);
mPackagePolicies.put(pkg, appManifest);
if (info.path.equals(BACKUP_MANIFEST_FILENAME)) {
Signature[] signatures = tarBackupReader.readAppManifestAndReturnSignatures(
info);
RestorePolicy restorePolicy = tarBackupReader.chooseRestorePolicy(
mBackupManagerService.getPackageManager(), allowApks, info, signatures);
mManifestSignatures.put(info.packageName, signatures);
mPackagePolicies.put(pkg, restorePolicy);
mPackageInstallers.put(pkg, info.installerPackageName);
// We've read only the manifest content itself at this point,
// so consume the footer before looping around to the next
// input file
tarBackupReader.skipTarPadding(info.size);
mObserver = FullBackupRestoreObserverUtils.sendOnRestorePackage(mObserver, pkg);
} else if (info.path.equals(
RefactoredBackupManagerService.BACKUP_METADATA_FILENAME)) {
} else if (info.path.equals(BACKUP_METADATA_FILENAME)) {
// Metadata blobs!
tarBackupReader.readMetadata(info);
@@ -234,18 +245,17 @@ public class FullRestoreEngine extends RestoreEngine {
// If we're in accept-if-apk state, then the first file we
// see MUST be the apk.
if (info.domain.equals(FullBackup.APK_TREE_TOKEN)) {
if (RefactoredBackupManagerService.DEBUG) {
Slog.d(RefactoredBackupManagerService.TAG,
"APK file; installing");
if (DEBUG) {
Slog.d(TAG, "APK file; installing");
}
// Try to install the app.
String installerName = mPackageInstallers.get(pkg);
boolean isSuccessfullyInstalled = RestoreUtils.installApk(
instream, backupManagerService.getPackageManager(),
instream, mBackupManagerService.getPackageManager(),
mInstallObserver, mDeleteObserver, mManifestSignatures,
mPackagePolicies, info, installerName,
bytesReadListener, backupManagerService.getDataDir()
);
bytesReadListener, mBackupManagerService.getDataDir()
);
// good to go; promote to ACCEPT
mPackagePolicies.put(pkg, isSuccessfullyInstalled
? RestorePolicy.ACCEPT
@@ -265,9 +275,8 @@ public class FullRestoreEngine extends RestoreEngine {
case ACCEPT:
if (info.domain.equals(FullBackup.APK_TREE_TOKEN)) {
if (RefactoredBackupManagerService.DEBUG) {
Slog.d(RefactoredBackupManagerService.TAG,
"apk present but ACCEPT");
if (DEBUG) {
Slog.d(TAG, "apk present but ACCEPT");
}
// we can take the data without the apk, so we
// *want* to do so. skip the apk by declaring this
@@ -281,8 +290,7 @@ public class FullRestoreEngine extends RestoreEngine {
// Something has gone dreadfully wrong when determining
// the restore policy from the manifest. Ignore the
// rest of this package's data.
Slog.e(RefactoredBackupManagerService.TAG,
"Invalid policy from manifest");
Slog.e(TAG, "Invalid policy from manifest");
okay = false;
mPackagePolicies.put(pkg, RestorePolicy.IGNORE);
break;
@@ -295,19 +303,17 @@ public class FullRestoreEngine extends RestoreEngine {
// If the policy is satisfied, go ahead and set up to pipe the
// data to the agent.
if (RefactoredBackupManagerService.MORE_DEBUG && okay && mAgent != null) {
Slog.i(RefactoredBackupManagerService.TAG,
"Reusing existing agent instance");
if (MORE_DEBUG && okay && mAgent != null) {
Slog.i(TAG, "Reusing existing agent instance");
}
if (okay && mAgent == null) {
if (RefactoredBackupManagerService.MORE_DEBUG) {
Slog.d(RefactoredBackupManagerService.TAG,
"Need to launch agent for " + pkg);
if (MORE_DEBUG) {
Slog.d(TAG, "Need to launch agent for " + pkg);
}
try {
mTargetApp =
backupManagerService.getPackageManager().getApplicationInfo(
mBackupManagerService.getPackageManager().getApplicationInfo(
pkg, 0);
// If we haven't sent any data to this app yet, we probably
@@ -317,29 +323,28 @@ public class FullRestoreEngine extends RestoreEngine {
// responsible for coherently managing a full
// restore.
if (mTargetApp.backupAgentName == null) {
if (RefactoredBackupManagerService.DEBUG) {
Slog.d(RefactoredBackupManagerService.TAG,
if (DEBUG) {
Slog.d(TAG,
"Clearing app data preparatory to full restore");
}
backupManagerService.clearApplicationDataSynchronous(pkg);
mBackupManagerService.clearApplicationDataSynchronous(pkg);
} else {
if (RefactoredBackupManagerService.MORE_DEBUG) {
Slog.d(RefactoredBackupManagerService.TAG, "backup agent ("
if (MORE_DEBUG) {
Slog.d(TAG, "backup agent ("
+ mTargetApp.backupAgentName + ") => no clear");
}
}
mClearedPackages.add(pkg);
} else {
if (RefactoredBackupManagerService.MORE_DEBUG) {
Slog.d(RefactoredBackupManagerService.TAG,
"We've initialized this app already; no clear "
+ "required");
if (MORE_DEBUG) {
Slog.d(TAG, "We've initialized this app already; no clear "
+ "required");
}
}
// All set; now set up the IPC and launch the agent
setUpPipes();
mAgent = backupManagerService.bindToAgentSynchronous(mTargetApp,
mAgent = mBackupManagerService.bindToAgentSynchronous(mTargetApp,
ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL);
mAgentPackage = pkg;
} catch (IOException e) {
@@ -349,9 +354,7 @@ public class FullRestoreEngine extends RestoreEngine {
}
if (mAgent == null) {
Slog.e(
RefactoredBackupManagerService.TAG,
"Unable to create agent for " + pkg);
Slog.e(TAG, "Unable to create agent for " + pkg);
okay = false;
tearDownPipes();
mPackagePolicies.put(pkg, RestorePolicy.IGNORE);
@@ -361,7 +364,7 @@ public class FullRestoreEngine extends RestoreEngine {
// Sanity check: make sure we never give data to the wrong app. This
// should never happen but a little paranoia here won't go amiss.
if (okay && !pkg.equals(mAgentPackage)) {
Slog.e(RefactoredBackupManagerService.TAG, "Restoring data for " + pkg
Slog.e(TAG, "Restoring data for " + pkg
+ " but agent is for " + mAgentPackage);
okay = false;
}
@@ -373,84 +376,74 @@ public class FullRestoreEngine extends RestoreEngine {
if (okay) {
boolean agentSuccess = true;
long toCopy = info.size;
final boolean isSharedStorage = pkg.equals(
RefactoredBackupManagerService.SHARED_BACKUP_AGENT_PACKAGE);
final boolean isSharedStorage = pkg.equals(SHARED_BACKUP_AGENT_PACKAGE);
final long timeout = isSharedStorage ?
RefactoredBackupManagerService.TIMEOUT_SHARED_BACKUP_INTERVAL :
RefactoredBackupManagerService.TIMEOUT_RESTORE_INTERVAL;
TIMEOUT_SHARED_BACKUP_INTERVAL :
TIMEOUT_RESTORE_INTERVAL;
try {
backupManagerService.prepareOperationTimeout(token,
mBackupManagerService.prepareOperationTimeout(token,
timeout,
mMonitorTask,
RefactoredBackupManagerService.OP_TYPE_RESTORE_WAIT);
OP_TYPE_RESTORE_WAIT);
if (FullBackup.OBB_TREE_TOKEN.equals(info.domain)) {
if (RefactoredBackupManagerService.DEBUG) {
Slog.d(RefactoredBackupManagerService.TAG,
"Restoring OBB file for " + pkg
+ " : " + info.path);
if (DEBUG) {
Slog.d(TAG, "Restoring OBB file for " + pkg
+ " : " + info.path);
}
mObbConnection.restoreObbFile(pkg, mPipes[0],
info.size, info.type, info.path, info.mode,
info.mtime, token,
backupManagerService.getBackupManagerBinder());
mBackupManagerService.getBackupManagerBinder());
} else if (FullBackup.KEY_VALUE_DATA_TOKEN.equals(info.domain)) {
// This is only possible during adb restore.
// TODO: Refactor to clearly separate the flows.
if (RefactoredBackupManagerService.DEBUG) {
Slog.d(RefactoredBackupManagerService.TAG,
"Restoring key-value file for " + pkg
+ " : " + info.path);
if (DEBUG) {
Slog.d(TAG, "Restoring key-value file for " + pkg
+ " : " + info.path);
}
KeyValueAdbRestoreEngine restoreEngine =
new KeyValueAdbRestoreEngine(
backupManagerService,
backupManagerService.getDataDir(), info, mPipes[0],
mBackupManagerService,
mBackupManagerService.getDataDir(), info, mPipes[0],
mAgent, token);
new Thread(restoreEngine, "restore-key-value-runner").start();
} else {
if (RefactoredBackupManagerService.MORE_DEBUG) {
Slog.d(RefactoredBackupManagerService.TAG,
"Invoking agent to restore file "
+ info.path);
if (MORE_DEBUG) {
Slog.d(TAG, "Invoking agent to restore file " + info.path);
}
// fire up the app's agent listening on the socket. If
// the agent is running in the system process we can't
// just invoke it asynchronously, so we provide a thread
// for it here.
if (mTargetApp.processName.equals("system")) {
Slog.d(RefactoredBackupManagerService.TAG,
"system process agent - spinning a thread");
Slog.d(TAG, "system process agent - spinning a thread");
RestoreFileRunnable runner = new RestoreFileRunnable(
backupManagerService, mAgent, info, mPipes[0], token);
mBackupManagerService, mAgent, info, mPipes[0], token);
new Thread(runner, "restore-sys-runner").start();
} else {
mAgent.doRestoreFile(mPipes[0], info.size, info.type,
info.domain, info.path, info.mode, info.mtime,
token, backupManagerService.getBackupManagerBinder());
token, mBackupManagerService.getBackupManagerBinder());
}
}
} catch (IOException e) {
// couldn't dup the socket for a process-local restore
Slog.d(RefactoredBackupManagerService.TAG,
"Couldn't establish restore");
Slog.d(TAG, "Couldn't establish restore");
agentSuccess = false;
okay = false;
} catch (RemoteException e) {
// whoops, remote entity went away. We'll eat the content
// ourselves, then, and not copy it over.
Slog.e(RefactoredBackupManagerService.TAG,
"Agent crashed during full restore");
Slog.e(TAG, "Agent crashed during full restore");
agentSuccess = false;
okay = false;
}
// Copy over the data if the agent is still good
if (okay) {
if (RefactoredBackupManagerService.MORE_DEBUG) {
Slog.v(RefactoredBackupManagerService.TAG,
" copying to restore agent: "
+ toCopy + " bytes");
if (MORE_DEBUG) {
Slog.v(TAG, " copying to restore agent: " + toCopy + " bytes");
}
boolean pipeOkay = true;
FileOutputStream pipe = new FileOutputStream(
@@ -473,9 +466,8 @@ public class FullRestoreEngine extends RestoreEngine {
try {
pipe.write(buffer, 0, nRead);
} catch (IOException e) {
Slog.e(RefactoredBackupManagerService.TAG,
"Failed to write to restore pipe: "
+ e.getMessage());
Slog.e(TAG, "Failed to write to restore pipe: "
+ e.getMessage());
pipeOkay = false;
}
}
@@ -487,15 +479,14 @@ public class FullRestoreEngine extends RestoreEngine {
// and now that we've sent it all, wait for the remote
// side to acknowledge receipt
agentSuccess = backupManagerService.waitUntilOperationComplete(token);
agentSuccess = mBackupManagerService.waitUntilOperationComplete(token);
}
// okay, if the remote end failed at any point, deal with
// it by ignoring the rest of the restore on it
if (!agentSuccess) {
Slog.w(RefactoredBackupManagerService.TAG,
"Agent failure restoring " + pkg + "; ending restore");
backupManagerService.getBackupHandler().removeMessages(
Slog.w(TAG, "Agent failure restoring " + pkg + "; ending restore");
mBackupManagerService.getBackupHandler().removeMessages(
MSG_RESTORE_OPERATION_TIMEOUT);
tearDownPipes();
tearDownAgent(mTargetApp);
@@ -516,8 +507,8 @@ public class FullRestoreEngine extends RestoreEngine {
// dropped file, or an already-ignored package: skip to the
// next stream entry by reading and discarding this file.
if (!okay) {
if (RefactoredBackupManagerService.MORE_DEBUG) {
Slog.d(RefactoredBackupManagerService.TAG, "[discarding file content]");
if (MORE_DEBUG) {
Slog.d(TAG, "[discarding file content]");
}
long bytesToConsume = (info.size + 511) & ~511;
while (bytesToConsume > 0) {
@@ -536,9 +527,8 @@ public class FullRestoreEngine extends RestoreEngine {
}
}
} catch (IOException e) {
if (RefactoredBackupManagerService.DEBUG) {
Slog.w(RefactoredBackupManagerService.TAG,
"io exception on restore socket read: " + e.getMessage());
if (DEBUG) {
Slog.w(TAG, "io exception on restore socket read: " + e.getMessage());
}
setResult(RestoreEngine.TRANSPORT_FAILURE);
info = null;
@@ -546,9 +536,8 @@ public class FullRestoreEngine extends RestoreEngine {
// If we got here we're either running smoothly or we've finished
if (info == null) {
if (RefactoredBackupManagerService.MORE_DEBUG) {
Slog.i(RefactoredBackupManagerService.TAG,
"No [more] data for this package; tearing down");
if (MORE_DEBUG) {
Slog.i(TAG, "No [more] data for this package; tearing down");
}
tearDownPipes();
setRunning(false);
@@ -559,11 +548,11 @@ public class FullRestoreEngine extends RestoreEngine {
return (info != null);
}
void setUpPipes() throws IOException {
private void setUpPipes() throws IOException {
mPipes = ParcelFileDescriptor.createPipe();
}
void tearDownPipes() {
private void tearDownPipes() {
// Teardown might arise from the inline restore processing or from the asynchronous
// timeout mechanism, and these might race. Make sure we don't try to close and
// null out the pipes twice.
@@ -575,16 +564,16 @@ public class FullRestoreEngine extends RestoreEngine {
mPipes[1].close();
mPipes[1] = null;
} catch (IOException e) {
Slog.w(RefactoredBackupManagerService.TAG, "Couldn't close agent pipes", e);
Slog.w(TAG, "Couldn't close agent pipes", e);
}
mPipes = null;
}
}
}
void tearDownAgent(ApplicationInfo app) {
private void tearDownAgent(ApplicationInfo app) {
if (mAgent != null) {
backupManagerService.tearDownAgentAndKill(app);
mBackupManagerService.tearDownAgentAndKill(app);
mAgent = null;
}
}
@@ -595,13 +584,10 @@ public class FullRestoreEngine extends RestoreEngine {
setRunning(false);
}
final RestoreInstallObserver mInstallObserver = new RestoreInstallObserver();
final RestoreDeleteObserver mDeleteObserver = new RestoreDeleteObserver();
private static boolean isRestorableFile(FileMetadata info) {
if (FullBackup.CACHE_TREE_TOKEN.equals(info.domain)) {
if (RefactoredBackupManagerService.MORE_DEBUG) {
Slog.i(RefactoredBackupManagerService.TAG, "Dropping cache file path " + info.path);
if (MORE_DEBUG) {
Slog.i(TAG, "Dropping cache file path " + info.path);
}
return false;
}
@@ -612,9 +598,8 @@ public class FullRestoreEngine extends RestoreEngine {
// API. Respect the no-backup intention and don't let the data get to
// the app.
if (info.path.startsWith("no_backup/")) {
if (RefactoredBackupManagerService.MORE_DEBUG) {
Slog.i(RefactoredBackupManagerService.TAG,
"Dropping no_backup file path " + info.path);
if (MORE_DEBUG) {
Slog.i(TAG, "Dropping no_backup file path " + info.path);
}
return false;
}
@@ -626,8 +611,8 @@ public class FullRestoreEngine extends RestoreEngine {
private static boolean isCanonicalFilePath(String path) {
if (path.contains("..") || path.contains("//")) {
if (RefactoredBackupManagerService.MORE_DEBUG) {
Slog.w(RefactoredBackupManagerService.TAG, "Dropping invalid path " + path);
if (MORE_DEBUG) {
Slog.w(TAG, "Dropping invalid path " + path);
}
return false;
}
@@ -641,8 +626,7 @@ public class FullRestoreEngine extends RestoreEngine {
// TODO: use a more user-friendly name string
mObserver.onRestorePackage(name);
} catch (RemoteException e) {
Slog.w(RefactoredBackupManagerService.TAG,
"full restore observer went away: restorePackage");
Slog.w(TAG, "full restore observer went away: restorePackage");
mObserver = null;
}
}

View File

@@ -16,6 +16,20 @@
package com.android.server.backup.restore;
import static com.android.server.backup.RefactoredBackupManagerService.BACKUP_FILE_HEADER_MAGIC;
import static com.android.server.backup.RefactoredBackupManagerService.BACKUP_FILE_VERSION;
import static com.android.server.backup.RefactoredBackupManagerService.BACKUP_MANIFEST_FILENAME;
import static com.android.server.backup.RefactoredBackupManagerService.BACKUP_METADATA_FILENAME;
import static com.android.server.backup.RefactoredBackupManagerService.DEBUG;
import static com.android.server.backup.RefactoredBackupManagerService.MORE_DEBUG;
import static com.android.server.backup.RefactoredBackupManagerService.OP_TYPE_RESTORE_WAIT;
import static com.android.server.backup.RefactoredBackupManagerService.PBKDF_CURRENT;
import static com.android.server.backup.RefactoredBackupManagerService.PBKDF_FALLBACK;
import static com.android.server.backup.RefactoredBackupManagerService.SETTINGS_PACKAGE;
import static com.android.server.backup.RefactoredBackupManagerService.SHARED_BACKUP_AGENT_PACKAGE;
import static com.android.server.backup.RefactoredBackupManagerService.TAG;
import static com.android.server.backup.RefactoredBackupManagerService.TIMEOUT_FULL_BACKUP_INTERVAL;
import static com.android.server.backup.RefactoredBackupManagerService.TIMEOUT_RESTORE_INTERVAL;
import static com.android.server.backup.internal.BackupHandler.MSG_RESTORE_OPERATION_TIMEOUT;
import android.app.ApplicationThreadConstants;
@@ -32,6 +46,7 @@ import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.backup.FileMetadata;
import com.android.server.backup.KeyValueAdbRestoreEngine;
import com.android.server.backup.PackageManagerBackupAgent;
@@ -43,7 +58,6 @@ import com.android.server.backup.utils.PasswordUtils;
import com.android.server.backup.utils.RestoreUtils;
import com.android.server.backup.utils.TarBackupReader;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
@@ -68,38 +82,44 @@ import javax.crypto.spec.SecretKeySpec;
public class PerformAdbRestoreTask implements Runnable {
private RefactoredBackupManagerService backupManagerService;
ParcelFileDescriptor mInputFile;
String mCurrentPassword;
String mDecryptPassword;
IFullBackupRestoreObserver mObserver;
AtomicBoolean mLatchObject;
IBackupAgent mAgent;
PackageManagerBackupAgent mPackageManagerBackupAgent;
String mAgentPackage;
ApplicationInfo mTargetApp;
FullBackupObbConnection mObbConnection = null;
ParcelFileDescriptor[] mPipes = null;
byte[] mWidgetData = null;
private final RefactoredBackupManagerService mBackupManagerService;
private final ParcelFileDescriptor mInputFile;
private final String mCurrentPassword;
private final String mDecryptPassword;
private final AtomicBoolean mLatchObject;
private final PackageManagerBackupAgent mPackageManagerBackupAgent;
private final RestoreInstallObserver mInstallObserver = new RestoreInstallObserver();
private final RestoreDeleteObserver mDeleteObserver = new RestoreDeleteObserver();
long mBytes;
private IFullBackupRestoreObserver mObserver;
private IBackupAgent mAgent;
private String mAgentPackage;
private ApplicationInfo mTargetApp;
private FullBackupObbConnection mObbConnection = null;
private ParcelFileDescriptor[] mPipes = null;
private byte[] mWidgetData = null;
private long mBytes;
// Runner that can be placed on a separate thread to do in-process invocation
// of the "restore finished" API asynchronously. Used by adb restore.
class RestoreFinishedRunnable implements Runnable {
private static class RestoreFinishedRunnable implements Runnable {
final IBackupAgent mAgent;
final int mToken;
private final IBackupAgent mAgent;
private final int mToken;
private final RefactoredBackupManagerService mBackupManagerService;
RestoreFinishedRunnable(IBackupAgent agent, int token) {
RestoreFinishedRunnable(IBackupAgent agent, int token,
RefactoredBackupManagerService backupManagerService) {
mAgent = agent;
mToken = token;
mBackupManagerService = backupManagerService;
}
@Override
public void run() {
try {
mAgent.doRestoreFinished(mToken, backupManagerService.getBackupManagerBinder());
mAgent.doRestoreFinished(mToken, mBackupManagerService.getBackupManagerBinder());
} catch (RemoteException e) {
// never happens; this is used only for local binder calls
}
@@ -107,23 +127,23 @@ public class PerformAdbRestoreTask implements Runnable {
}
// possible handling states for a given package in the restore dataset
final HashMap<String, RestorePolicy> mPackagePolicies
private final HashMap<String, RestorePolicy> mPackagePolicies
= new HashMap<>();
// installer package names for each encountered app, derived from the manifests
final HashMap<String, String> mPackageInstallers = new HashMap<>();
private final HashMap<String, String> mPackageInstallers = new HashMap<>();
// Signatures for a given package found in its manifest file
final HashMap<String, Signature[]> mManifestSignatures
private final HashMap<String, Signature[]> mManifestSignatures
= new HashMap<>();
// Packages we've already wiped data on when restoring their first file
final HashSet<String> mClearedPackages = new HashSet<>();
private final HashSet<String> mClearedPackages = new HashSet<>();
public PerformAdbRestoreTask(RefactoredBackupManagerService backupManagerService,
ParcelFileDescriptor fd, String curPassword, String decryptPassword,
IFullBackupRestoreObserver observer, AtomicBoolean latch) {
this.backupManagerService = backupManagerService;
this.mBackupManagerService = backupManagerService;
mInputFile = fd;
mCurrentPassword = curPassword;
mDecryptPassword = decryptPassword;
@@ -139,115 +159,66 @@ public class PerformAdbRestoreTask implements Runnable {
// Which packages we've already wiped data on. We prepopulate this
// with a whitelist of packages known to be unclearable.
mClearedPackages.add("android");
mClearedPackages.add(RefactoredBackupManagerService.SETTINGS_PACKAGE);
mClearedPackages.add(SETTINGS_PACKAGE);
}
@Override
public void run() {
Slog.i(RefactoredBackupManagerService.TAG, "--- Performing full-dataset restore ---");
Slog.i(TAG, "--- Performing full-dataset restore ---");
mObbConnection.establish();
mObserver = FullBackupRestoreObserverUtils.sendStartRestore(mObserver);
// Are we able to restore shared-storage data?
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
mPackagePolicies.put(RefactoredBackupManagerService.SHARED_BACKUP_AGENT_PACKAGE,
mPackagePolicies.put(SHARED_BACKUP_AGENT_PACKAGE,
RestorePolicy.ACCEPT);
}
FileInputStream rawInStream = null;
DataInputStream rawDataIn = null;
try {
if (!backupManagerService.backupPasswordMatches(mCurrentPassword)) {
if (RefactoredBackupManagerService.DEBUG) {
Slog.w(RefactoredBackupManagerService.TAG,
"Backup password mismatch; aborting");
if (!mBackupManagerService.backupPasswordMatches(mCurrentPassword)) {
if (DEBUG) {
Slog.w(TAG, "Backup password mismatch; aborting");
}
return;
}
mBytes = 0;
byte[] buffer = new byte[32 * 1024];
rawInStream = new FileInputStream(mInputFile.getFileDescriptor());
rawDataIn = new DataInputStream(rawInStream);
// First, parse out the unencrypted/uncompressed header
boolean compressed = false;
InputStream preCompressStream = rawInStream;
final InputStream in;
boolean okay = false;
final int headerLen = RefactoredBackupManagerService.BACKUP_FILE_HEADER_MAGIC.length();
byte[] streamHeader = new byte[headerLen];
rawDataIn.readFully(streamHeader);
byte[] magicBytes = RefactoredBackupManagerService.BACKUP_FILE_HEADER_MAGIC.getBytes(
"UTF-8");
if (Arrays.equals(magicBytes, streamHeader)) {
// okay, header looks good. now parse out the rest of the fields.
String s = readHeaderLine(rawInStream);
final int archiveVersion = Integer.parseInt(s);
if (archiveVersion <= RefactoredBackupManagerService.BACKUP_FILE_VERSION) {
// okay, it's a version we recognize. if it's version 1, we may need
// to try two different PBKDF2 regimes to compare checksums.
final boolean pbkdf2Fallback = (archiveVersion == 1);
s = readHeaderLine(rawInStream);
compressed = (Integer.parseInt(s) != 0);
s = readHeaderLine(rawInStream);
if (s.equals("none")) {
// no more header to parse; we're good to go
okay = true;
} else if (mDecryptPassword != null && mDecryptPassword.length() > 0) {
preCompressStream = decodeAesHeaderAndInitialize(s, pbkdf2Fallback,
rawInStream);
if (preCompressStream != null) {
okay = true;
}
} else {
Slog.w(RefactoredBackupManagerService.TAG,
"Archive is encrypted but no password given");
}
} else {
Slog.w(RefactoredBackupManagerService.TAG, "Wrong header version: " + s);
}
} else {
Slog.w(RefactoredBackupManagerService.TAG, "Didn't read the right header magic");
}
if (!okay) {
Slog.w(RefactoredBackupManagerService.TAG, "Invalid restore data; aborting.");
InputStream tarInputStream = parseBackupFileHeaderAndReturnTarStream(rawInStream,
mDecryptPassword);
if (tarInputStream == null) {
// There was an error reading the backup file, which is already handled and logged.
// Just abort.
return;
}
// okay, use the right stream layer based on compression
in = (compressed) ? new InflaterInputStream(preCompressStream) : preCompressStream;
byte[] buffer = new byte[32 * 1024];
boolean didRestore;
do {
didRestore = restoreOneFile(in, false /* mustKillAgent */, buffer,
didRestore = restoreOneFile(tarInputStream, false /* mustKillAgent */, buffer,
null /* onlyPackage */, true /* allowApks */,
backupManagerService.generateRandomIntegerToken(), null /* monitor */);
mBackupManagerService.generateRandomIntegerToken(), null /* monitor */);
} while (didRestore);
if (RefactoredBackupManagerService.MORE_DEBUG) {
Slog.v(RefactoredBackupManagerService.TAG,
"Done consuming input tarfile, total bytes=" + mBytes);
if (MORE_DEBUG) {
Slog.v(TAG, "Done consuming input tarfile, total bytes=" + mBytes);
}
} catch (IOException e) {
Slog.e(RefactoredBackupManagerService.TAG, "Unable to read restore input");
Slog.e(TAG, "Unable to read restore input");
} finally {
tearDownPipes();
tearDownAgent(mTargetApp, true);
try {
if (rawDataIn != null) {
rawDataIn.close();
}
if (rawInStream != null) {
rawInStream.close();
}
mInputFile.close();
} catch (IOException e) {
Slog.w(RefactoredBackupManagerService.TAG, "Close of restore data pipe threw", e);
Slog.w(TAG, "Close of restore data pipe threw", e);
/* nothing we can do about this */
}
synchronized (mLatchObject) {
@@ -256,12 +227,79 @@ public class PerformAdbRestoreTask implements Runnable {
}
mObbConnection.tearDown();
mObserver = FullBackupRestoreObserverUtils.sendEndRestore(mObserver);
Slog.d(RefactoredBackupManagerService.TAG, "Full restore pass complete.");
backupManagerService.getWakelock().release();
Slog.d(TAG, "Full restore pass complete.");
mBackupManagerService.getWakelock().release();
}
}
String readHeaderLine(InputStream in) throws IOException {
private static void readFullyOrThrow(InputStream in, byte[] buffer) throws IOException {
int offset = 0;
while (offset < buffer.length) {
int bytesRead = in.read(buffer, offset, buffer.length - offset);
if (bytesRead <= 0) {
throw new IOException("Couldn't fully read data");
}
offset += bytesRead;
}
}
@VisibleForTesting
public static InputStream parseBackupFileHeaderAndReturnTarStream(
InputStream rawInputStream,
String decryptPassword)
throws IOException {
// First, parse out the unencrypted/uncompressed header
boolean compressed = false;
InputStream preCompressStream = rawInputStream;
boolean okay = false;
final int headerLen = BACKUP_FILE_HEADER_MAGIC.length();
byte[] streamHeader = new byte[headerLen];
readFullyOrThrow(rawInputStream, streamHeader);
byte[] magicBytes = BACKUP_FILE_HEADER_MAGIC.getBytes(
"UTF-8");
if (Arrays.equals(magicBytes, streamHeader)) {
// okay, header looks good. now parse out the rest of the fields.
String s = readHeaderLine(rawInputStream);
final int archiveVersion = Integer.parseInt(s);
if (archiveVersion <= BACKUP_FILE_VERSION) {
// okay, it's a version we recognize. if it's version 1, we may need
// to try two different PBKDF2 regimes to compare checksums.
final boolean pbkdf2Fallback = (archiveVersion == 1);
s = readHeaderLine(rawInputStream);
compressed = (Integer.parseInt(s) != 0);
s = readHeaderLine(rawInputStream);
if (s.equals("none")) {
// no more header to parse; we're good to go
okay = true;
} else if (decryptPassword != null && decryptPassword.length() > 0) {
preCompressStream = decodeAesHeaderAndInitialize(
decryptPassword, s, pbkdf2Fallback,
rawInputStream);
if (preCompressStream != null) {
okay = true;
}
} else {
Slog.w(TAG, "Archive is encrypted but no password given");
}
} else {
Slog.w(TAG, "Wrong header version: " + s);
}
} else {
Slog.w(TAG, "Didn't read the right header magic");
}
if (!okay) {
Slog.w(TAG, "Invalid restore data; aborting.");
return null;
}
// okay, use the right stream layer based on compression
return compressed ? new InflaterInputStream(preCompressStream) : preCompressStream;
}
private static String readHeaderLine(InputStream in) throws IOException {
int c;
StringBuilder buffer = new StringBuilder(80);
while ((c = in.read()) >= 0) {
@@ -273,7 +311,8 @@ public class PerformAdbRestoreTask implements Runnable {
return buffer.toString();
}
InputStream attemptMasterKeyDecryption(String algorithm, byte[] userSalt, byte[] ckSalt,
private static InputStream attemptMasterKeyDecryption(String decryptPassword, String algorithm,
byte[] userSalt, byte[] ckSalt,
int rounds, String userIvHex, String masterKeyBlobHex, InputStream rawInStream,
boolean doLog) {
InputStream result = null;
@@ -281,7 +320,7 @@ public class PerformAdbRestoreTask implements Runnable {
try {
Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKey userKey = PasswordUtils
.buildPasswordKey(algorithm, mDecryptPassword, userSalt,
.buildPasswordKey(algorithm, decryptPassword, userSalt,
rounds);
byte[] IV = PasswordUtils.hexToByteArray(userIvHex);
IvParameterSpec ivSpec = new IvParameterSpec(IV);
@@ -317,11 +356,11 @@ public class PerformAdbRestoreTask implements Runnable {
// Only if all of the above worked properly will 'result' be assigned
result = new CipherInputStream(rawInStream, c);
} else if (doLog) {
Slog.w(RefactoredBackupManagerService.TAG, "Incorrect password");
Slog.w(TAG, "Incorrect password");
}
} catch (InvalidAlgorithmParameterException e) {
if (doLog) {
Slog.e(RefactoredBackupManagerService.TAG, "Needed parameter spec unavailable!", e);
Slog.e(TAG, "Needed parameter spec unavailable!", e);
}
} catch (BadPaddingException e) {
// This case frequently occurs when the wrong password is used to decrypt
@@ -329,31 +368,32 @@ public class PerformAdbRestoreTask implements Runnable {
// used in the checksum failure log in order to avoid providing additional
// information to an attacker.
if (doLog) {
Slog.w(RefactoredBackupManagerService.TAG, "Incorrect password");
Slog.w(TAG, "Incorrect password");
}
} catch (IllegalBlockSizeException e) {
if (doLog) {
Slog.w(RefactoredBackupManagerService.TAG, "Invalid block size in master key");
Slog.w(TAG, "Invalid block size in master key");
}
} catch (NoSuchAlgorithmException e) {
if (doLog) {
Slog.e(RefactoredBackupManagerService.TAG,
"Needed decryption algorithm unavailable!");
Slog.e(TAG, "Needed decryption algorithm unavailable!");
}
} catch (NoSuchPaddingException e) {
if (doLog) {
Slog.e(RefactoredBackupManagerService.TAG, "Needed padding mechanism unavailable!");
Slog.e(TAG, "Needed padding mechanism unavailable!");
}
} catch (InvalidKeyException e) {
if (doLog) {
Slog.w(RefactoredBackupManagerService.TAG, "Illegal password; aborting");
Slog.w(TAG, "Illegal password; aborting");
}
}
return result;
}
InputStream decodeAesHeaderAndInitialize(String encryptionName, boolean pbkdf2Fallback,
private static InputStream decodeAesHeaderAndInitialize(String decryptPassword,
String encryptionName,
boolean pbkdf2Fallback,
InputStream rawInStream) {
InputStream result = null;
try {
@@ -371,22 +411,21 @@ public class PerformAdbRestoreTask implements Runnable {
String masterKeyBlobHex = readHeaderLine(rawInStream); // 9
// decrypt the master key blob
result = attemptMasterKeyDecryption(RefactoredBackupManagerService.PBKDF_CURRENT,
result = attemptMasterKeyDecryption(decryptPassword, PBKDF_CURRENT,
userSalt, ckSalt,
rounds, userIvHex, masterKeyBlobHex, rawInStream, false);
if (result == null && pbkdf2Fallback) {
result = attemptMasterKeyDecryption(
RefactoredBackupManagerService.PBKDF_FALLBACK, userSalt, ckSalt,
decryptPassword, PBKDF_FALLBACK, userSalt, ckSalt,
rounds, userIvHex, masterKeyBlobHex, rawInStream, true);
}
} else {
Slog.w(RefactoredBackupManagerService.TAG,
"Unsupported encryption method: " + encryptionName);
Slog.w(TAG, "Unsupported encryption method: " + encryptionName);
}
} catch (NumberFormatException e) {
Slog.w(RefactoredBackupManagerService.TAG, "Can't parse restore data header");
Slog.w(TAG, "Can't parse restore data header");
} catch (IOException e) {
Slog.w(RefactoredBackupManagerService.TAG, "Can't read input header");
Slog.w(TAG, "Can't read input header");
}
return result;
@@ -406,7 +445,7 @@ public class PerformAdbRestoreTask implements Runnable {
try {
info = tarBackupReader.readTarHeaders();
if (info != null) {
if (RefactoredBackupManagerService.MORE_DEBUG) {
if (MORE_DEBUG) {
info.dump();
}
@@ -421,9 +460,8 @@ public class PerformAdbRestoreTask implements Runnable {
// Clean up the previous agent relationship if necessary,
// and let the observer know we're considering a new app.
if (mAgent != null) {
if (RefactoredBackupManagerService.DEBUG) {
Slog.d(RefactoredBackupManagerService.TAG,
"Saw new package; finalizing old one");
if (DEBUG) {
Slog.d(TAG, "Saw new package; finalizing old one");
}
// Now we're really done
tearDownPipes();
@@ -433,19 +471,21 @@ public class PerformAdbRestoreTask implements Runnable {
}
}
if (info.path.equals(RefactoredBackupManagerService.BACKUP_MANIFEST_FILENAME)) {
RestorePolicy appManifest = tarBackupReader.readAppManifest(
backupManagerService.getPackageManager(), allowApks,
mManifestSignatures, info);
mPackagePolicies.put(pkg, appManifest);
if (info.path.equals(BACKUP_MANIFEST_FILENAME)) {
Signature[] signatures = tarBackupReader.readAppManifestAndReturnSignatures(
info);
RestorePolicy restorePolicy = tarBackupReader.chooseRestorePolicy(
mBackupManagerService.getPackageManager(), allowApks,
info, signatures);
mManifestSignatures.put(info.packageName, signatures);
mPackagePolicies.put(pkg, restorePolicy);
mPackageInstallers.put(pkg, info.installerPackageName);
// We've read only the manifest content itself at this point,
// so consume the footer before looping around to the next
// input file
tarBackupReader.skipTarPadding(info.size);
mObserver = FullBackupRestoreObserverUtils.sendOnRestorePackage(mObserver, pkg);
} else if (info.path.equals(
RefactoredBackupManagerService.BACKUP_METADATA_FILENAME)) {
} else if (info.path.equals(BACKUP_METADATA_FILENAME)) {
// Metadata blobs!
tarBackupReader.readMetadata(info);
@@ -472,18 +512,17 @@ public class PerformAdbRestoreTask implements Runnable {
// If we're in accept-if-apk state, then the first file we
// see MUST be the apk.
if (info.domain.equals(FullBackup.APK_TREE_TOKEN)) {
if (RefactoredBackupManagerService.DEBUG) {
Slog.d(RefactoredBackupManagerService.TAG,
"APK file; installing");
if (DEBUG) {
Slog.d(TAG, "APK file; installing");
}
// Try to install the app.
String installerName = mPackageInstallers.get(pkg);
boolean isSuccessfullyInstalled = RestoreUtils.installApk(
instream, backupManagerService.getPackageManager(),
instream, mBackupManagerService.getPackageManager(),
mInstallObserver, mDeleteObserver, mManifestSignatures,
mPackagePolicies, info, installerName,
bytesReadListener, backupManagerService.getDataDir()
);
bytesReadListener, mBackupManagerService.getDataDir()
);
// good to go; promote to ACCEPT
mPackagePolicies.put(pkg, isSuccessfullyInstalled
? RestorePolicy.ACCEPT
@@ -503,9 +542,8 @@ public class PerformAdbRestoreTask implements Runnable {
case ACCEPT:
if (info.domain.equals(FullBackup.APK_TREE_TOKEN)) {
if (RefactoredBackupManagerService.DEBUG) {
Slog.d(RefactoredBackupManagerService.TAG,
"apk present but ACCEPT");
if (DEBUG) {
Slog.d(TAG, "apk present but ACCEPT");
}
// we can take the data without the apk, so we
// *want* to do so. skip the apk by declaring this
@@ -519,8 +557,7 @@ public class PerformAdbRestoreTask implements Runnable {
// Something has gone dreadfully wrong when determining
// the restore policy from the manifest. Ignore the
// rest of this package's data.
Slog.e(RefactoredBackupManagerService.TAG,
"Invalid policy from manifest");
Slog.e(TAG, "Invalid policy from manifest");
okay = false;
mPackagePolicies.put(pkg, RestorePolicy.IGNORE);
break;
@@ -533,19 +570,17 @@ public class PerformAdbRestoreTask implements Runnable {
// If the policy is satisfied, go ahead and set up to pipe the
// data to the agent.
if (RefactoredBackupManagerService.DEBUG && okay && mAgent != null) {
Slog.i(RefactoredBackupManagerService.TAG,
"Reusing existing agent instance");
if (DEBUG && okay && mAgent != null) {
Slog.i(TAG, "Reusing existing agent instance");
}
if (okay && mAgent == null) {
if (RefactoredBackupManagerService.DEBUG) {
Slog.d(RefactoredBackupManagerService.TAG,
"Need to launch agent for " + pkg);
if (DEBUG) {
Slog.d(TAG, "Need to launch agent for " + pkg);
}
try {
mTargetApp =
backupManagerService.getPackageManager().getApplicationInfo(
mBackupManagerService.getPackageManager().getApplicationInfo(
pkg, 0);
// If we haven't sent any data to this app yet, we probably
@@ -555,29 +590,28 @@ public class PerformAdbRestoreTask implements Runnable {
// responsible for coherently managing a full
// restore.
if (mTargetApp.backupAgentName == null) {
if (RefactoredBackupManagerService.DEBUG) {
Slog.d(RefactoredBackupManagerService.TAG,
if (DEBUG) {
Slog.d(TAG,
"Clearing app data preparatory to full restore");
}
backupManagerService.clearApplicationDataSynchronous(pkg);
mBackupManagerService.clearApplicationDataSynchronous(pkg);
} else {
if (RefactoredBackupManagerService.DEBUG) {
Slog.d(RefactoredBackupManagerService.TAG, "backup agent ("
if (DEBUG) {
Slog.d(TAG, "backup agent ("
+ mTargetApp.backupAgentName + ") => no clear");
}
}
mClearedPackages.add(pkg);
} else {
if (RefactoredBackupManagerService.DEBUG) {
Slog.d(RefactoredBackupManagerService.TAG,
"We've initialized this app already; no clear "
+ "required");
if (DEBUG) {
Slog.d(TAG, "We've initialized this app already; no clear "
+ "required");
}
}
// All set; now set up the IPC and launch the agent
setUpPipes();
mAgent = backupManagerService.bindToAgentSynchronous(mTargetApp,
mAgent = mBackupManagerService.bindToAgentSynchronous(mTargetApp,
ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL);
mAgentPackage = pkg;
} catch (IOException e) {
@@ -587,9 +621,7 @@ public class PerformAdbRestoreTask implements Runnable {
}
if (mAgent == null) {
Slog.e(
RefactoredBackupManagerService.TAG,
"Unable to create agent for " + pkg);
Slog.e(TAG, "Unable to create agent for " + pkg);
okay = false;
tearDownPipes();
mPackagePolicies.put(pkg, RestorePolicy.IGNORE);
@@ -599,7 +631,7 @@ public class PerformAdbRestoreTask implements Runnable {
// Sanity check: make sure we never give data to the wrong app. This
// should never happen but a little paranoia here won't go amiss.
if (okay && !pkg.equals(mAgentPackage)) {
Slog.e(RefactoredBackupManagerService.TAG, "Restoring data for " + pkg
Slog.e(TAG, "Restoring data for " + pkg
+ " but agent is for " + mAgentPackage);
okay = false;
}
@@ -612,67 +644,60 @@ public class PerformAdbRestoreTask implements Runnable {
boolean agentSuccess = true;
long toCopy = info.size;
try {
backupManagerService
mBackupManagerService
.prepareOperationTimeout(token,
RefactoredBackupManagerService.TIMEOUT_RESTORE_INTERVAL,
TIMEOUT_RESTORE_INTERVAL,
null,
RefactoredBackupManagerService.OP_TYPE_RESTORE_WAIT);
OP_TYPE_RESTORE_WAIT);
if (FullBackup.OBB_TREE_TOKEN.equals(info.domain)) {
if (RefactoredBackupManagerService.DEBUG) {
Slog.d(RefactoredBackupManagerService.TAG,
"Restoring OBB file for " + pkg
+ " : " + info.path);
if (DEBUG) {
Slog.d(TAG, "Restoring OBB file for " + pkg
+ " : " + info.path);
}
mObbConnection.restoreObbFile(pkg, mPipes[0],
info.size, info.type, info.path, info.mode,
info.mtime, token,
backupManagerService.getBackupManagerBinder());
mBackupManagerService.getBackupManagerBinder());
} else if (FullBackup.KEY_VALUE_DATA_TOKEN.equals(info.domain)) {
if (RefactoredBackupManagerService.DEBUG) {
Slog.d(RefactoredBackupManagerService.TAG,
"Restoring key-value file for " + pkg
+ " : " + info.path);
if (DEBUG) {
Slog.d(TAG, "Restoring key-value file for " + pkg
+ " : " + info.path);
}
KeyValueAdbRestoreEngine restoreEngine =
new KeyValueAdbRestoreEngine(
backupManagerService,
backupManagerService.getDataDir(), info, mPipes[0],
mBackupManagerService,
mBackupManagerService.getDataDir(), info, mPipes[0],
mAgent, token);
new Thread(restoreEngine, "restore-key-value-runner").start();
} else {
if (RefactoredBackupManagerService.DEBUG) {
Slog.d(RefactoredBackupManagerService.TAG,
"Invoking agent to restore file "
+ info.path);
if (DEBUG) {
Slog.d(TAG, "Invoking agent to restore file " + info.path);
}
// fire up the app's agent listening on the socket. If
// the agent is running in the system process we can't
// just invoke it asynchronously, so we provide a thread
// for it here.
if (mTargetApp.processName.equals("system")) {
Slog.d(RefactoredBackupManagerService.TAG,
"system process agent - spinning a thread");
Slog.d(TAG, "system process agent - spinning a thread");
RestoreFileRunnable runner = new RestoreFileRunnable(
backupManagerService, mAgent, info, mPipes[0], token);
mBackupManagerService, mAgent, info, mPipes[0], token);
new Thread(runner, "restore-sys-runner").start();
} else {
mAgent.doRestoreFile(mPipes[0], info.size, info.type,
info.domain, info.path, info.mode, info.mtime,
token, backupManagerService.getBackupManagerBinder());
token, mBackupManagerService.getBackupManagerBinder());
}
}
} catch (IOException e) {
// couldn't dup the socket for a process-local restore
Slog.d(RefactoredBackupManagerService.TAG,
"Couldn't establish restore");
Slog.d(TAG, "Couldn't establish restore");
agentSuccess = false;
okay = false;
} catch (RemoteException e) {
// whoops, remote entity went away. We'll eat the content
// ourselves, then, and not copy it over.
Slog.e(RefactoredBackupManagerService.TAG,
"Agent crashed during full restore");
Slog.e(TAG, "Agent crashed during full restore");
agentSuccess = false;
okay = false;
}
@@ -700,8 +725,7 @@ public class PerformAdbRestoreTask implements Runnable {
try {
pipe.write(buffer, 0, nRead);
} catch (IOException e) {
Slog.e(RefactoredBackupManagerService.TAG,
"Failed to write to restore pipe", e);
Slog.e(TAG, "Failed to write to restore pipe", e);
pipeOkay = false;
}
}
@@ -713,17 +737,16 @@ public class PerformAdbRestoreTask implements Runnable {
// and now that we've sent it all, wait for the remote
// side to acknowledge receipt
agentSuccess = backupManagerService.waitUntilOperationComplete(token);
agentSuccess = mBackupManagerService.waitUntilOperationComplete(token);
}
// okay, if the remote end failed at any point, deal with
// it by ignoring the rest of the restore on it
if (!agentSuccess) {
if (RefactoredBackupManagerService.DEBUG) {
Slog.d(RefactoredBackupManagerService.TAG,
"Agent failure restoring " + pkg + "; now ignoring");
if (DEBUG) {
Slog.d(TAG, "Agent failure restoring " + pkg + "; now ignoring");
}
backupManagerService.getBackupHandler().removeMessages(
mBackupManagerService.getBackupHandler().removeMessages(
MSG_RESTORE_OPERATION_TIMEOUT);
tearDownPipes();
tearDownAgent(mTargetApp, false);
@@ -735,8 +758,8 @@ public class PerformAdbRestoreTask implements Runnable {
// ignored package: skip to the next tar stream entry by
// reading and discarding this file.
if (!okay) {
if (RefactoredBackupManagerService.DEBUG) {
Slog.d(RefactoredBackupManagerService.TAG, "[discarding file content]");
if (DEBUG) {
Slog.d(TAG, "[discarding file content]");
}
long bytesToConsume = (info.size + 511) & ~511;
while (bytesToConsume > 0) {
@@ -755,9 +778,8 @@ public class PerformAdbRestoreTask implements Runnable {
}
}
} catch (IOException e) {
if (RefactoredBackupManagerService.DEBUG) {
Slog.w(RefactoredBackupManagerService.TAG, "io exception on restore socket read",
e);
if (DEBUG) {
Slog.w(TAG, "io exception on restore socket read", e);
}
// treat as EOF
info = null;
@@ -768,8 +790,8 @@ public class PerformAdbRestoreTask implements Runnable {
private static boolean isCanonicalFilePath(String path) {
if (path.contains("..") || path.contains("//")) {
if (RefactoredBackupManagerService.MORE_DEBUG) {
Slog.w(RefactoredBackupManagerService.TAG, "Dropping invalid path " + path);
if (MORE_DEBUG) {
Slog.w(TAG, "Dropping invalid path " + path);
}
return false;
}
@@ -777,11 +799,11 @@ public class PerformAdbRestoreTask implements Runnable {
return true;
}
void setUpPipes() throws IOException {
private void setUpPipes() throws IOException {
mPipes = ParcelFileDescriptor.createPipe();
}
void tearDownPipes() {
private void tearDownPipes() {
if (mPipes != null) {
try {
mPipes[0].close();
@@ -789,49 +811,46 @@ public class PerformAdbRestoreTask implements Runnable {
mPipes[1].close();
mPipes[1] = null;
} catch (IOException e) {
Slog.w(RefactoredBackupManagerService.TAG, "Couldn't close agent pipes", e);
Slog.w(TAG, "Couldn't close agent pipes", e);
}
mPipes = null;
}
}
void tearDownAgent(ApplicationInfo app, boolean doRestoreFinished) {
private void tearDownAgent(ApplicationInfo app, boolean doRestoreFinished) {
if (mAgent != null) {
try {
// In the adb restore case, we do restore-finished here
if (doRestoreFinished) {
final int token = backupManagerService.generateRandomIntegerToken();
final int token = mBackupManagerService.generateRandomIntegerToken();
final AdbRestoreFinishedLatch latch = new AdbRestoreFinishedLatch(
backupManagerService, token);
backupManagerService
mBackupManagerService, token);
mBackupManagerService
.prepareOperationTimeout(token,
RefactoredBackupManagerService.TIMEOUT_FULL_BACKUP_INTERVAL,
TIMEOUT_FULL_BACKUP_INTERVAL,
latch,
RefactoredBackupManagerService.OP_TYPE_RESTORE_WAIT);
OP_TYPE_RESTORE_WAIT);
if (mTargetApp.processName.equals("system")) {
if (RefactoredBackupManagerService.MORE_DEBUG) {
Slog.d(RefactoredBackupManagerService.TAG,
"system agent - restoreFinished on thread");
if (MORE_DEBUG) {
Slog.d(TAG, "system agent - restoreFinished on thread");
}
Runnable runner = new RestoreFinishedRunnable(mAgent, token);
Runnable runner = new RestoreFinishedRunnable(mAgent, token,
mBackupManagerService);
new Thread(runner, "restore-sys-finished-runner").start();
} else {
mAgent.doRestoreFinished(token,
backupManagerService.getBackupManagerBinder());
mBackupManagerService.getBackupManagerBinder());
}
latch.await();
}
backupManagerService.tearDownAgentAndKill(app);
mBackupManagerService.tearDownAgentAndKill(app);
} catch (RemoteException e) {
Slog.d(RefactoredBackupManagerService.TAG, "Lost app trying to shut down");
Slog.d(TAG, "Lost app trying to shut down");
}
mAgent = null;
}
}
final RestoreInstallObserver mInstallObserver = new RestoreInstallObserver();
final RestoreDeleteObserver mDeleteObserver = new RestoreDeleteObserver();
}

View File

@@ -34,6 +34,10 @@ import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_SYSTEM_APP_NO
import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_VERSIONS_MATCH;
import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_VERSION_OF_BACKUP_OLDER;
import static com.android.server.backup.RefactoredBackupManagerService.DEBUG;
import static com.android.server.backup.RefactoredBackupManagerService.MORE_DEBUG;
import static com.android.server.backup.RefactoredBackupManagerService.TAG;
import android.app.backup.BackupAgent;
import android.app.backup.BackupManagerMonitor;
import android.app.backup.FullBackup;
@@ -54,7 +58,6 @@ import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
/**
* Utility methods to read backup tar file.
@@ -150,23 +153,20 @@ public class TarBackupReader {
case '5': {
info.type = BackupAgent.TYPE_DIRECTORY;
if (info.size != 0) {
Slog.w(RefactoredBackupManagerService.TAG,
"Directory entry with nonzero size in header");
Slog.w(TAG, "Directory entry with nonzero size in header");
info.size = 0;
}
break;
}
case 0: {
// presume EOF
if (RefactoredBackupManagerService.MORE_DEBUG) {
Slog.w(RefactoredBackupManagerService.TAG,
"Saw type=0 in tar header block, info=" + info);
if (MORE_DEBUG) {
Slog.w(TAG, "Saw type=0 in tar header block, info=" + info);
}
return null;
}
default: {
Slog.e(RefactoredBackupManagerService.TAG,
"Unknown tar entity type: " + typeChar);
Slog.e(TAG, "Unknown tar entity type: " + typeChar);
throw new IOException("Unknown entity type " + typeChar);
}
}
@@ -180,9 +180,8 @@ public class TarBackupReader {
info.path = info.path.substring(FullBackup.SHARED_PREFIX.length());
info.packageName = RefactoredBackupManagerService.SHARED_BACKUP_AGENT_PACKAGE;
info.domain = FullBackup.SHARED_STORAGE_TOKEN;
if (RefactoredBackupManagerService.DEBUG) {
Slog.i(RefactoredBackupManagerService.TAG,
"File in shared storage: " + info.path);
if (DEBUG) {
Slog.i(TAG, "File in shared storage: " + info.path);
}
} else if (FullBackup.APPS_PREFIX.regionMatches(0,
info.path, 0, FullBackup.APPS_PREFIX.length())) {
@@ -214,10 +213,9 @@ public class TarBackupReader {
}
}
} catch (IOException e) {
if (RefactoredBackupManagerService.DEBUG) {
Slog.e(RefactoredBackupManagerService.TAG,
"Parse error in header: " + e.getMessage());
if (RefactoredBackupManagerService.MORE_DEBUG) {
if (DEBUG) {
Slog.e(TAG, "Parse error in header: " + e.getMessage());
if (MORE_DEBUG) {
hexLog(block);
}
}
@@ -242,41 +240,35 @@ public class TarBackupReader {
if (size <= 0) {
throw new IllegalArgumentException("size must be > 0");
}
if (RefactoredBackupManagerService.MORE_DEBUG) {
Slog.i(RefactoredBackupManagerService.TAG, " ... readExactly(" + size + ") called");
if (MORE_DEBUG) {
Slog.i(TAG, " ... readExactly(" + size + ") called");
}
int soFar = 0;
while (soFar < size) {
int nRead = in.read(buffer, offset + soFar, size - soFar);
if (nRead <= 0) {
if (RefactoredBackupManagerService.MORE_DEBUG) {
Slog.w(RefactoredBackupManagerService.TAG,
"- wanted exactly " + size + " but got only " + soFar);
if (MORE_DEBUG) {
Slog.w(TAG, "- wanted exactly " + size + " but got only " + soFar);
}
break;
}
soFar += nRead;
if (RefactoredBackupManagerService.MORE_DEBUG) {
Slog.v(RefactoredBackupManagerService.TAG,
" + got " + nRead + "; now wanting " + (size - soFar));
if (MORE_DEBUG) {
Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soFar));
}
}
return soFar;
}
/**
* Reads app manifest and returns a policy constant.
* Reads app manifest, filling version and hasApk fields in the metadata, and returns array of
* signatures.
*
* @param packageManager - PackageManager instance.
* @param allowApks - allow restore set to include apks.
* @param manifestSignatures - parsed signatures will be put here.
* @param info - file metadata.
* @return a policy constant.
* @return array of signatures or null, in case of an error.
* @throws IOException in case of an error.
*/
public RestorePolicy readAppManifest(PackageManager packageManager,
boolean allowApks, HashMap<String, Signature[]> manifestSignatures,
FileMetadata info)
public Signature[] readAppManifestAndReturnSignatures(FileMetadata info)
throws IOException {
// Fail on suspiciously large manifest files
if (info.size > 64 * 1024) {
@@ -284,9 +276,9 @@ public class TarBackupReader {
}
byte[] buffer = new byte[(int) info.size];
if (RefactoredBackupManagerService.MORE_DEBUG) {
Slog.i(RefactoredBackupManagerService.TAG,
" readAppManifest() looking for " + info.size + " bytes");
if (MORE_DEBUG) {
Slog.i(TAG,
" readAppManifestAndReturnSignatures() looking for " + info.size + " bytes");
}
if (readExactly(mInputStream, buffer, 0, (int) info.size) == info.size) {
mBytesReadListener.onBytesRead(info.size);
@@ -294,7 +286,6 @@ public class TarBackupReader {
throw new IOException("Unexpected EOF in manifest");
}
RestorePolicy policy = RestorePolicy.IGNORE;
String[] str = new String[1];
int offset = 0;
@@ -307,7 +298,7 @@ public class TarBackupReader {
// TODO: handle <original-package>
if (manifestPackage.equals(info.packageName)) {
offset = extractLine(buffer, offset, str);
version = Integer.parseInt(str[0]); // app version
info.version = Integer.parseInt(str[0]); // app version
offset = extractLine(buffer, offset, str);
// This is the platform version, which we don't use, but we parse it
// as a safety against corruption in the manifest.
@@ -315,7 +306,7 @@ public class TarBackupReader {
offset = extractLine(buffer, offset, str);
info.installerPackageName = (str[0].length() > 0) ? str[0] : null;
offset = extractLine(buffer, offset, str);
boolean hasApk = str[0].equals("1");
info.hasApk = str[0].equals("1");
offset = extractLine(buffer, offset, str);
int numSigs = Integer.parseInt(str[0]);
if (numSigs > 0) {
@@ -324,162 +315,9 @@ public class TarBackupReader {
offset = extractLine(buffer, offset, str);
sigs[i] = new Signature(str[0]);
}
manifestSignatures.put(info.packageName, sigs);
// Okay, got the manifest info we need...
try {
PackageInfo pkgInfo = packageManager.getPackageInfo(
info.packageName, PackageManager.GET_SIGNATURES);
// Fall through to IGNORE if the app explicitly disallows backup
final int flags = pkgInfo.applicationInfo.flags;
if ((flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0) {
// Restore system-uid-space packages only if they have
// defined a custom backup agent
if ((pkgInfo.applicationInfo.uid
>= Process.FIRST_APPLICATION_UID)
|| (pkgInfo.applicationInfo.backupAgentName != null)) {
// Verify signatures against any installed version; if they
// don't match, then we fall though and ignore the data. The
// signatureMatch() method explicitly ignores the signature
// check for packages installed on the system partition, because
// such packages are signed with the platform cert instead of
// the app developer's cert, so they're different on every
// device.
if (AppBackupUtils.signaturesMatch(sigs,
pkgInfo)) {
if ((pkgInfo.applicationInfo.flags
& ApplicationInfo.FLAG_RESTORE_ANY_VERSION) != 0) {
Slog.i(RefactoredBackupManagerService.TAG,
"Package has restoreAnyVersion; taking data");
mMonitor = BackupManagerMonitorUtils.monitorEvent(
mMonitor,
LOG_EVENT_ID_RESTORE_ANY_VERSION,
pkgInfo,
LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
null);
policy = RestorePolicy.ACCEPT;
} else if (pkgInfo.versionCode >= version) {
Slog.i(RefactoredBackupManagerService.TAG,
"Sig + version match; taking data");
policy = RestorePolicy.ACCEPT;
mMonitor = BackupManagerMonitorUtils.monitorEvent(
mMonitor,
LOG_EVENT_ID_VERSIONS_MATCH,
pkgInfo,
LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
null);
} else {
// The data is from a newer version of the app than
// is presently installed. That means we can only
// use it if the matching apk is also supplied.
if (allowApks) {
Slog.i(RefactoredBackupManagerService.TAG,
"Data version " + version
+ " is newer than installed "
+ "version "
+ pkgInfo.versionCode
+ " - requiring apk");
policy = RestorePolicy.ACCEPT_IF_APK;
} else {
Slog.i(RefactoredBackupManagerService.TAG,
"Data requires newer version "
+ version + "; ignoring");
mMonitor = BackupManagerMonitorUtils
.monitorEvent(mMonitor,
LOG_EVENT_ID_VERSION_OF_BACKUP_OLDER,
pkgInfo,
LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
BackupManagerMonitorUtils
.putMonitoringExtra(
null,
EXTRA_LOG_OLD_VERSION,
version));
policy = RestorePolicy.IGNORE;
}
}
} else {
Slog.w(RefactoredBackupManagerService.TAG,
"Restore manifest signatures do not match "
+ "installed application for "
+ info.packageName);
mMonitor = BackupManagerMonitorUtils.monitorEvent(
mMonitor,
LOG_EVENT_ID_FULL_RESTORE_SIGNATURE_MISMATCH,
pkgInfo,
LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
null);
}
} else {
Slog.w(RefactoredBackupManagerService.TAG,
"Package " + info.packageName
+ " is system level with no agent");
mMonitor = BackupManagerMonitorUtils.monitorEvent(
mMonitor,
LOG_EVENT_ID_SYSTEM_APP_NO_AGENT,
pkgInfo,
LOG_EVENT_CATEGORY_AGENT,
null);
}
} else {
if (RefactoredBackupManagerService.DEBUG) {
Slog.i(RefactoredBackupManagerService.TAG,
"Restore manifest from "
+ info.packageName + " but allowBackup=false");
}
mMonitor = BackupManagerMonitorUtils.monitorEvent(
mMonitor,
LOG_EVENT_ID_FULL_RESTORE_ALLOW_BACKUP_FALSE,
pkgInfo,
LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
null);
}
} catch (PackageManager.NameNotFoundException e) {
// Okay, the target app isn't installed. We can process
// the restore properly only if the dataset provides the
// apk file and we can successfully install it.
if (allowApks) {
if (RefactoredBackupManagerService.DEBUG) {
Slog.i(RefactoredBackupManagerService.TAG,
"Package " + info.packageName
+ " not installed; requiring apk in dataset");
}
policy = RestorePolicy.ACCEPT_IF_APK;
} else {
policy = RestorePolicy.IGNORE;
}
Bundle monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(
null,
EXTRA_LOG_EVENT_PACKAGE_NAME, info.packageName);
monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(
monitoringExtras,
EXTRA_LOG_POLICY_ALLOW_APKS, allowApks);
mMonitor = BackupManagerMonitorUtils.monitorEvent(
mMonitor,
LOG_EVENT_ID_APK_NOT_INSTALLED,
null,
LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
monitoringExtras);
}
if (policy == RestorePolicy.ACCEPT_IF_APK && !hasApk) {
Slog.i(RefactoredBackupManagerService.TAG,
"Cannot restore package " + info.packageName
+ " without the matching .apk");
mMonitor = BackupManagerMonitorUtils.monitorEvent(
mMonitor,
LOG_EVENT_ID_CANNOT_RESTORE_WITHOUT_APK,
null,
LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
BackupManagerMonitorUtils.putMonitoringExtra(null,
EXTRA_LOG_EVENT_PACKAGE_NAME, info.packageName));
}
return sigs;
} else {
Slog.i(RefactoredBackupManagerService.TAG,
"Missing signature on backed-up package "
+ info.packageName);
Slog.i(TAG, "Missing signature on backed-up package " + info.packageName);
mMonitor = BackupManagerMonitorUtils.monitorEvent(
mMonitor,
LOG_EVENT_ID_MISSING_SIGNATURE,
@@ -489,9 +327,8 @@ public class TarBackupReader {
EXTRA_LOG_EVENT_PACKAGE_NAME, info.packageName));
}
} else {
Slog.i(RefactoredBackupManagerService.TAG,
"Expected package " + info.packageName
+ " but restore manifest claims " + manifestPackage);
Slog.i(TAG, "Expected package " + info.packageName
+ " but restore manifest claims " + manifestPackage);
Bundle monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(null,
EXTRA_LOG_EVENT_PACKAGE_NAME, info.packageName);
monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(
@@ -505,9 +342,8 @@ public class TarBackupReader {
monitoringExtras);
}
} else {
Slog.i(RefactoredBackupManagerService.TAG,
"Unknown restore manifest version " + version
+ " for package " + info.packageName);
Slog.i(TAG, "Unknown restore manifest version " + version
+ " for package " + info.packageName);
Bundle monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(null,
EXTRA_LOG_EVENT_PACKAGE_NAME, info.packageName);
monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(monitoringExtras,
@@ -521,8 +357,7 @@ public class TarBackupReader {
}
} catch (NumberFormatException e) {
Slog.w(RefactoredBackupManagerService.TAG,
"Corrupt restore manifest for package " + info.packageName);
Slog.w(TAG, "Corrupt restore manifest for package " + info.packageName);
mMonitor = BackupManagerMonitorUtils.monitorEvent(
mMonitor,
BackupManagerMonitor.LOG_EVENT_ID_CORRUPT_MANIFEST,
@@ -531,7 +366,166 @@ public class TarBackupReader {
BackupManagerMonitorUtils.putMonitoringExtra(null, EXTRA_LOG_EVENT_PACKAGE_NAME,
info.packageName));
} catch (IllegalArgumentException e) {
Slog.w(RefactoredBackupManagerService.TAG, e.getMessage());
Slog.w(TAG, e.getMessage());
}
return null;
}
/**
* Chooses restore policy.
*
* @param packageManager - PackageManager instance.
* @param allowApks - allow restore set to include apks.
* @param info - file metadata.
* @param signatures - array of signatures parsed from backup file.
* @return a restore policy constant.
*/
public RestorePolicy chooseRestorePolicy(PackageManager packageManager,
boolean allowApks, FileMetadata info, Signature[] signatures) {
if (signatures == null) {
return RestorePolicy.IGNORE;
}
RestorePolicy policy = RestorePolicy.IGNORE;
// Okay, got the manifest info we need...
try {
PackageInfo pkgInfo = packageManager.getPackageInfo(
info.packageName, PackageManager.GET_SIGNATURES);
// Fall through to IGNORE if the app explicitly disallows backup
final int flags = pkgInfo.applicationInfo.flags;
if ((flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0) {
// Restore system-uid-space packages only if they have
// defined a custom backup agent
if ((pkgInfo.applicationInfo.uid
>= Process.FIRST_APPLICATION_UID)
|| (pkgInfo.applicationInfo.backupAgentName != null)) {
// Verify signatures against any installed version; if they
// don't match, then we fall though and ignore the data. The
// signatureMatch() method explicitly ignores the signature
// check for packages installed on the system partition, because
// such packages are signed with the platform cert instead of
// the app developer's cert, so they're different on every
// device.
if (AppBackupUtils.signaturesMatch(signatures, pkgInfo)) {
if ((pkgInfo.applicationInfo.flags
& ApplicationInfo.FLAG_RESTORE_ANY_VERSION) != 0) {
Slog.i(TAG, "Package has restoreAnyVersion; taking data");
mMonitor = BackupManagerMonitorUtils.monitorEvent(
mMonitor,
LOG_EVENT_ID_RESTORE_ANY_VERSION,
pkgInfo,
LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
null);
policy = RestorePolicy.ACCEPT;
} else if (pkgInfo.versionCode >= info.version) {
Slog.i(TAG, "Sig + version match; taking data");
policy = RestorePolicy.ACCEPT;
mMonitor = BackupManagerMonitorUtils.monitorEvent(
mMonitor,
LOG_EVENT_ID_VERSIONS_MATCH,
pkgInfo,
LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
null);
} else {
// The data is from a newer version of the app than
// is presently installed. That means we can only
// use it if the matching apk is also supplied.
if (allowApks) {
Slog.i(TAG, "Data version " + info.version
+ " is newer than installed "
+ "version "
+ pkgInfo.versionCode
+ " - requiring apk");
policy = RestorePolicy.ACCEPT_IF_APK;
} else {
Slog.i(TAG, "Data requires newer version "
+ info.version + "; ignoring");
mMonitor = BackupManagerMonitorUtils
.monitorEvent(mMonitor,
LOG_EVENT_ID_VERSION_OF_BACKUP_OLDER,
pkgInfo,
LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
BackupManagerMonitorUtils
.putMonitoringExtra(
null,
EXTRA_LOG_OLD_VERSION,
info.version));
policy = RestorePolicy.IGNORE;
}
}
} else {
Slog.w(TAG, "Restore manifest signatures do not match "
+ "installed application for "
+ info.packageName);
mMonitor = BackupManagerMonitorUtils.monitorEvent(
mMonitor,
LOG_EVENT_ID_FULL_RESTORE_SIGNATURE_MISMATCH,
pkgInfo,
LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
null);
}
} else {
Slog.w(TAG, "Package " + info.packageName
+ " is system level with no agent");
mMonitor = BackupManagerMonitorUtils.monitorEvent(
mMonitor,
LOG_EVENT_ID_SYSTEM_APP_NO_AGENT,
pkgInfo,
LOG_EVENT_CATEGORY_AGENT,
null);
}
} else {
if (DEBUG) {
Slog.i(TAG,
"Restore manifest from " + info.packageName + " but allowBackup=false");
}
mMonitor = BackupManagerMonitorUtils.monitorEvent(
mMonitor,
LOG_EVENT_ID_FULL_RESTORE_ALLOW_BACKUP_FALSE,
pkgInfo,
LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
null);
}
} catch (PackageManager.NameNotFoundException e) {
// Okay, the target app isn't installed. We can process
// the restore properly only if the dataset provides the
// apk file and we can successfully install it.
if (allowApks) {
if (DEBUG) {
Slog.i(TAG, "Package " + info.packageName
+ " not installed; requiring apk in dataset");
}
policy = RestorePolicy.ACCEPT_IF_APK;
} else {
policy = RestorePolicy.IGNORE;
}
Bundle monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(
null,
EXTRA_LOG_EVENT_PACKAGE_NAME, info.packageName);
monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(
monitoringExtras,
EXTRA_LOG_POLICY_ALLOW_APKS, allowApks);
mMonitor = BackupManagerMonitorUtils.monitorEvent(
mMonitor,
LOG_EVENT_ID_APK_NOT_INSTALLED,
null,
LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
monitoringExtras);
}
if (policy == RestorePolicy.ACCEPT_IF_APK && !info.hasApk) {
Slog.i(TAG, "Cannot restore package " + info.packageName
+ " without the matching .apk");
mMonitor = BackupManagerMonitorUtils.monitorEvent(
mMonitor,
LOG_EVENT_ID_CANNOT_RESTORE_WITHOUT_APK,
null,
LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
BackupManagerMonitorUtils.putMonitoringExtra(null,
EXTRA_LOG_EVENT_PACKAGE_NAME, info.packageName));
}
return policy;
@@ -543,9 +537,8 @@ public class TarBackupReader {
long partial = (size + 512) % 512;
if (partial > 0) {
final int needed = 512 - (int) partial;
if (RefactoredBackupManagerService.MORE_DEBUG) {
Slog.i(RefactoredBackupManagerService.TAG,
"Skipping tar padding: " + needed + " bytes");
if (MORE_DEBUG) {
Slog.i(TAG, "Skipping tar padding: " + needed + " bytes");
}
byte[] buffer = new byte[needed];
if (readExactly(mInputStream, buffer, 0, needed) == needed) {
@@ -588,24 +581,21 @@ public class TarBackupReader {
int token = in.readInt();
int size = in.readInt();
if (size > 64 * 1024) {
throw new IOException("Datum "
+ Integer.toHexString(token)
throw new IOException("Datum " + Integer.toHexString(token)
+ " too big; corrupt? size=" + info.size);
}
switch (token) {
case RefactoredBackupManagerService.BACKUP_WIDGET_METADATA_TOKEN: {
if (RefactoredBackupManagerService.MORE_DEBUG) {
Slog.i(RefactoredBackupManagerService.TAG,
"Got widget metadata for " + info.packageName);
if (MORE_DEBUG) {
Slog.i(TAG, "Got widget metadata for " + info.packageName);
}
mWidgetData = new byte[size];
in.read(mWidgetData);
break;
}
default: {
if (RefactoredBackupManagerService.DEBUG) {
Slog.i(RefactoredBackupManagerService.TAG, "Ignoring metadata blob "
+ Integer.toHexString(token)
if (DEBUG) {
Slog.i(TAG, "Ignoring metadata blob " + Integer.toHexString(token)
+ " for " + info.packageName);
}
in.skipBytes(size);
@@ -614,9 +604,9 @@ public class TarBackupReader {
}
}
} else {
Slog.w(RefactoredBackupManagerService.TAG,
"Metadata mismatch: package " + info.packageName
+ " but widget data for " + pkg);
Slog.w(TAG,
"Metadata mismatch: package " + info.packageName + " but widget data for "
+ pkg);
Bundle monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(null,
EXTRA_LOG_EVENT_PACKAGE_NAME, info.packageName);
@@ -630,7 +620,7 @@ public class TarBackupReader {
monitoringExtras);
}
} else {
Slog.w(RefactoredBackupManagerService.TAG, "Unsupported metadata version " + version);
Slog.w(TAG, "Unsupported metadata version " + version);
Bundle monitoringExtras = BackupManagerMonitorUtils
.putMonitoringExtra(null, EXTRA_LOG_EVENT_PACKAGE_NAME,
@@ -692,7 +682,7 @@ public class TarBackupReader {
throws IOException {
// We should never see a pax extended header larger than this
if (info.size > 32 * 1024) {
Slog.w(RefactoredBackupManagerService.TAG,
Slog.w(TAG,
"Suspiciously large pax header size " + info.size
+ " - aborting");
throw new IOException("Sanity failure: pax header size " + info.size);
@@ -740,8 +730,8 @@ public class TarBackupReader {
} else if ("size".equals(keyStr)) {
info.size = Long.parseLong(valStr);
} else {
if (RefactoredBackupManagerService.DEBUG) {
Slog.i(RefactoredBackupManagerService.TAG, "Unhandled pax key: " + key);
if (DEBUG) {
Slog.i(TAG, "Unhandled pax key: " + key);
}
}

View File

@@ -0,0 +1,95 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package com.android.server.backup.restore;
import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
import android.platform.test.annotations.Presubmit;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import com.android.frameworks.servicestests.R;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.MockitoAnnotations;
import java.io.InputStream;
@SmallTest
@Presubmit
@RunWith(AndroidJUnit4.class)
public class PerformAdbRestoreTaskTest {
private Context mContext;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mContext = InstrumentationRegistry.getContext();
}
@Test
public void parseBackupFileAndReturnTarStream_backupNotEncrypted_returnsNonNull()
throws Exception {
InputStream inputStream = mContext.getResources().openRawResource(
R.raw.backup_telephony_no_password);
InputStream tarInputStream = PerformAdbRestoreTask.parseBackupFileHeaderAndReturnTarStream(
inputStream, null);
assertThat(tarInputStream).isNotNull();
}
@Test
public void
parseBackupFileAndReturnTarStream_backupEncryptedAndPasswordProvided_returnsNonNull()
throws Exception {
InputStream inputStream = mContext.getResources().openRawResource(
R.raw.backup_telephony_with_password);
InputStream tarInputStream = PerformAdbRestoreTask.parseBackupFileHeaderAndReturnTarStream(
inputStream, "123");
assertThat(tarInputStream).isNotNull();
}
@Test
public void
parseBackupFileAndReturnTarStream_backupEncryptedAndPasswordNotProvided_returnsNull()
throws Exception {
InputStream inputStream = mContext.getResources().openRawResource(
R.raw.backup_telephony_with_password);
InputStream tarInputStream = PerformAdbRestoreTask.parseBackupFileHeaderAndReturnTarStream(
inputStream, null);
assertThat(tarInputStream).isNull();
}
@Test
public void
parseBackupFileAndReturnTarStream_backupEncryptedAndIncorrectPassword_returnsNull()
throws Exception {
InputStream inputStream = mContext.getResources().openRawResource(
R.raw.backup_telephony_with_password);
InputStream tarInputStream = PerformAdbRestoreTask.parseBackupFileHeaderAndReturnTarStream(
inputStream, "1234");
assertThat(tarInputStream).isNull();
}
}

View File

@@ -0,0 +1,140 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package com.android.server.backup.utils;
import static com.google.common.truth.Truth.assertThat;
import android.app.backup.IBackupManagerMonitor;
import android.content.Context;
import android.content.pm.Signature;
import android.platform.test.annotations.Presubmit;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import com.android.frameworks.servicestests.R;
import com.android.server.backup.FileMetadata;
import com.android.server.backup.restore.PerformAdbRestoreTask;
import com.google.common.hash.Hashing;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.io.InputStream;
@SmallTest
@Presubmit
@RunWith(AndroidJUnit4.class)
public class TarBackupReaderTest {
private static final String TELEPHONY_PACKAGE_NAME = "com.android.providers.telephony";
private static final String TELEPHONY_PACKAGE_SIGNATURE_SHA256 =
"301aa3cb081134501c45f1422abc66c24224fd5ded5fdc8f17e697176fd866aa";
private static final int TELEPHONY_PACKAGE_VERSION = 25;
@Mock
private BytesReadListener mBytesReadListenerMock;
@Mock
private IBackupManagerMonitor mBackupManagerMonitorMock;
private Context mContext;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mContext = InstrumentationRegistry.getContext();
}
@Test
public void readTarHeaders_backupEncrypted_correctlyParsesFileMetadata() throws Exception {
InputStream inputStream = mContext.getResources().openRawResource(
R.raw.backup_telephony_with_password);
InputStream tarInputStream = PerformAdbRestoreTask.parseBackupFileHeaderAndReturnTarStream(
inputStream, "123");
TarBackupReader tarBackupReader = new TarBackupReader(tarInputStream,
mBytesReadListenerMock, mBackupManagerMonitorMock);
FileMetadata fileMetadata = tarBackupReader.readTarHeaders();
assertThat(fileMetadata.packageName).isEqualTo(TELEPHONY_PACKAGE_NAME);
assertThat(fileMetadata.mode).isEqualTo(0600);
assertThat(fileMetadata.size).isEqualTo(2438);
assertThat(fileMetadata.domain).isEqualTo(null);
assertThat(fileMetadata.path).isEqualTo("_manifest");
}
@Test
public void readTarHeaders_backupNotEncrypted_correctlyParsesFileMetadata() throws Exception {
InputStream inputStream = mContext.getResources().openRawResource(
R.raw.backup_telephony_no_password);
InputStream tarInputStream = PerformAdbRestoreTask.parseBackupFileHeaderAndReturnTarStream(
inputStream, null);
TarBackupReader tarBackupReader = new TarBackupReader(tarInputStream,
mBytesReadListenerMock, mBackupManagerMonitorMock);
FileMetadata fileMetadata = tarBackupReader.readTarHeaders();
assertThat(fileMetadata.packageName).isEqualTo(TELEPHONY_PACKAGE_NAME);
assertThat(fileMetadata.mode).isEqualTo(0600);
assertThat(fileMetadata.size).isEqualTo(2438);
assertThat(fileMetadata.domain).isEqualTo(null);
assertThat(fileMetadata.path).isEqualTo("_manifest");
}
@Test
public void readAppManifest_backupEncrypted_correctlyParsesAppManifest() throws Exception {
InputStream inputStream = mContext.getResources().openRawResource(
R.raw.backup_telephony_with_password);
InputStream tarInputStream = PerformAdbRestoreTask.parseBackupFileHeaderAndReturnTarStream(
inputStream, "123");
TarBackupReader tarBackupReader = new TarBackupReader(tarInputStream,
mBytesReadListenerMock, mBackupManagerMonitorMock);
FileMetadata fileMetadata = tarBackupReader.readTarHeaders();
Signature[] signatures = tarBackupReader.readAppManifestAndReturnSignatures(fileMetadata);
assertThat(fileMetadata.version).isEqualTo(TELEPHONY_PACKAGE_VERSION);
assertThat(fileMetadata.hasApk).isFalse();
assertThat(signatures).isNotNull();
assertThat(signatures).hasLength(1);
String signatureSha256 = Hashing.sha256().hashBytes(signatures[0].toByteArray()).toString();
assertThat(signatureSha256).isEqualTo(TELEPHONY_PACKAGE_SIGNATURE_SHA256);
}
@Test
public void readAppManifest_backupNotEncrypted_correctlyParsesAppManifest() throws Exception {
InputStream inputStream = mContext.getResources().openRawResource(
R.raw.backup_telephony_no_password);
InputStream tarInputStream = PerformAdbRestoreTask.parseBackupFileHeaderAndReturnTarStream(
inputStream, null);
TarBackupReader tarBackupReader = new TarBackupReader(tarInputStream,
mBytesReadListenerMock, mBackupManagerMonitorMock);
FileMetadata fileMetadata = tarBackupReader.readTarHeaders();
Signature[] signatures = tarBackupReader.readAppManifestAndReturnSignatures(fileMetadata);
assertThat(fileMetadata.version).isEqualTo(TELEPHONY_PACKAGE_VERSION);
assertThat(fileMetadata.hasApk).isFalse();
assertThat(signatures).isNotNull();
assertThat(signatures).hasLength(1);
String signatureSha256 = Hashing.sha256().hashBytes(signatures[0].toByteArray()).toString();
assertThat(signatureSha256).isEqualTo(TELEPHONY_PACKAGE_SIGNATURE_SHA256);
}
}