blob: 3bf77e8bd23dcd4d6a8371e04553054568721e16 [file] [log] [blame]
/*
* 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.fullbackup;
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.BACKUP_METADATA_VERSION;
import static com.android.server.backup.RefactoredBackupManagerService.BACKUP_WIDGET_METADATA_TOKEN;
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_BACKUP_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_FULL_BACKUP_INTERVAL;
import static com.android.server.backup.RefactoredBackupManagerService
.TIMEOUT_SHARED_BACKUP_INTERVAL;
import android.app.ApplicationThreadConstants;
import android.app.IBackupAgent;
import android.app.backup.BackupTransport;
import android.app.backup.FullBackup;
import android.app.backup.FullBackupDataOutput;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.os.Environment.UserEnvironment;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
import android.util.Slog;
import android.util.StringBuilderPrinter;
import com.android.server.AppWidgetBackupBridge;
import com.android.server.backup.BackupRestoreTask;
import com.android.server.backup.RefactoredBackupManagerService;
import com.android.server.backup.utils.FullBackupUtils;
import java.io.BufferedOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
/**
* Core logic for performing one package's full backup, gathering the tarball from the
* application and emitting it to the designated OutputStream.
*/
public class FullBackupEngine {
private RefactoredBackupManagerService backupManagerService;
OutputStream mOutput;
FullBackupPreflight mPreflightHook;
BackupRestoreTask mTimeoutMonitor;
IBackupAgent mAgent;
File mFilesDir;
File mManifestFile;
File mMetadataFile;
boolean mIncludeApks;
PackageInfo mPkg;
private final long mQuota;
private final int mOpToken;
class FullBackupRunner implements Runnable {
PackageInfo mPackage;
byte[] mWidgetData;
IBackupAgent mAgent;
ParcelFileDescriptor mPipe;
int mToken;
boolean mSendApk;
boolean mWriteManifest;
FullBackupRunner(PackageInfo pack, IBackupAgent agent, ParcelFileDescriptor pipe,
int token, boolean sendApk, boolean writeManifest, byte[] widgetData)
throws IOException {
mPackage = pack;
mWidgetData = widgetData;
mAgent = agent;
mPipe = ParcelFileDescriptor.dup(pipe.getFileDescriptor());
mToken = token;
mSendApk = sendApk;
mWriteManifest = writeManifest;
}
@Override
public void run() {
try {
FullBackupDataOutput output = new FullBackupDataOutput(mPipe);
if (mWriteManifest) {
final boolean writeWidgetData = mWidgetData != null;
if (MORE_DEBUG) {
Slog.d(TAG, "Writing manifest for " + mPackage.packageName);
}
FullBackupUtils
.writeAppManifest(mPackage, backupManagerService.getPackageManager(),
mManifestFile, mSendApk,
writeWidgetData);
FullBackup.backupToTar(mPackage.packageName, null, null,
mFilesDir.getAbsolutePath(),
mManifestFile.getAbsolutePath(),
output);
mManifestFile.delete();
// We only need to write a metadata file if we have widget data to stash
if (writeWidgetData) {
writeMetadata(mPackage, mMetadataFile, mWidgetData);
FullBackup.backupToTar(mPackage.packageName, null, null,
mFilesDir.getAbsolutePath(),
mMetadataFile.getAbsolutePath(),
output);
mMetadataFile.delete();
}
}
if (mSendApk) {
writeApkToBackup(mPackage, output);
}
final boolean isSharedStorage =
mPackage.packageName.equals(SHARED_BACKUP_AGENT_PACKAGE);
final long timeout = isSharedStorage ?
TIMEOUT_SHARED_BACKUP_INTERVAL :
TIMEOUT_FULL_BACKUP_INTERVAL;
if (DEBUG) {
Slog.d(TAG, "Calling doFullBackup() on " + mPackage.packageName);
}
backupManagerService
.prepareOperationTimeout(mToken,
timeout,
mTimeoutMonitor /* in parent class */,
OP_TYPE_BACKUP_WAIT);
mAgent.doFullBackup(mPipe, mQuota, mToken,
backupManagerService.getBackupManagerBinder());
} catch (IOException e) {
Slog.e(TAG, "Error running full backup for " + mPackage.packageName);
} catch (RemoteException e) {
Slog.e(TAG, "Remote agent vanished during full backup of " + mPackage.packageName);
} finally {
try {
mPipe.close();
} catch (IOException e) {
}
}
}
}
public FullBackupEngine(RefactoredBackupManagerService backupManagerService,
OutputStream output,
FullBackupPreflight preflightHook, PackageInfo pkg,
boolean alsoApks, BackupRestoreTask timeoutMonitor, long quota, int opToken) {
this.backupManagerService = backupManagerService;
mOutput = output;
mPreflightHook = preflightHook;
mPkg = pkg;
mIncludeApks = alsoApks;
mTimeoutMonitor = timeoutMonitor;
mFilesDir = new File("/data/system");
mManifestFile = new File(mFilesDir, BACKUP_MANIFEST_FILENAME);
mMetadataFile = new File(mFilesDir, BACKUP_METADATA_FILENAME);
mQuota = quota;
mOpToken = opToken;
}
public int preflightCheck() throws RemoteException {
if (mPreflightHook == null) {
if (MORE_DEBUG) {
Slog.v(TAG, "No preflight check");
}
return BackupTransport.TRANSPORT_OK;
}
if (initializeAgent()) {
int result = mPreflightHook.preflightFullBackup(mPkg, mAgent);
if (MORE_DEBUG) {
Slog.v(TAG, "preflight returned " + result);
}
return result;
} else {
Slog.w(TAG, "Unable to bind to full agent for " + mPkg.packageName);
return BackupTransport.AGENT_ERROR;
}
}
public int backupOnePackage() throws RemoteException {
int result = BackupTransport.AGENT_ERROR;
if (initializeAgent()) {
ParcelFileDescriptor[] pipes = null;
try {
pipes = ParcelFileDescriptor.createPipe();
ApplicationInfo app = mPkg.applicationInfo;
final boolean isSharedStorage =
mPkg.packageName.equals(SHARED_BACKUP_AGENT_PACKAGE);
final boolean sendApk = mIncludeApks
&& !isSharedStorage
&& ((app.privateFlags & ApplicationInfo.PRIVATE_FLAG_FORWARD_LOCK) == 0)
&& ((app.flags & ApplicationInfo.FLAG_SYSTEM) == 0 ||
(app.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0);
// TODO: http://b/22388012
byte[] widgetBlob = AppWidgetBackupBridge.getWidgetState(mPkg.packageName,
UserHandle.USER_SYSTEM);
FullBackupRunner runner = new FullBackupRunner(mPkg, mAgent, pipes[1],
mOpToken, sendApk, !isSharedStorage, widgetBlob);
pipes[1].close(); // the runner has dup'd it
pipes[1] = null;
Thread t = new Thread(runner, "app-data-runner");
t.start();
// Now pull data from the app and stuff it into the output
FullBackupUtils.routeSocketDataToOutput(pipes[0], mOutput);
if (!backupManagerService.waitUntilOperationComplete(mOpToken)) {
Slog.e(TAG, "Full backup failed on package " + mPkg.packageName);
} else {
if (MORE_DEBUG) {
Slog.d(TAG, "Full package backup success: " + mPkg.packageName);
}
result = BackupTransport.TRANSPORT_OK;
}
} catch (IOException e) {
Slog.e(TAG, "Error backing up " + mPkg.packageName + ": " + e.getMessage());
result = BackupTransport.AGENT_ERROR;
} finally {
try {
// flush after every package
mOutput.flush();
if (pipes != null) {
if (pipes[0] != null) {
pipes[0].close();
}
if (pipes[1] != null) {
pipes[1].close();
}
}
} catch (IOException e) {
Slog.w(TAG, "Error bringing down backup stack");
result = BackupTransport.TRANSPORT_ERROR;
}
}
} else {
Slog.w(TAG, "Unable to bind to full agent for " + mPkg.packageName);
}
tearDown();
return result;
}
public void sendQuotaExceeded(final long backupDataBytes, final long quotaBytes) {
if (initializeAgent()) {
try {
mAgent.doQuotaExceeded(backupDataBytes, quotaBytes);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception while telling agent about quota exceeded");
}
}
}
private boolean initializeAgent() {
if (mAgent == null) {
if (MORE_DEBUG) {
Slog.d(TAG, "Binding to full backup agent : " + mPkg.packageName);
}
mAgent = backupManagerService.bindToAgentSynchronous(mPkg.applicationInfo,
ApplicationThreadConstants.BACKUP_MODE_FULL);
}
return mAgent != null;
}
private void writeApkToBackup(PackageInfo pkg, FullBackupDataOutput output) {
// Forward-locked apps, system-bundled .apks, etc are filtered out before we get here
// TODO: handle backing up split APKs
final String appSourceDir = pkg.applicationInfo.getBaseCodePath();
final String apkDir = new File(appSourceDir).getParent();
FullBackup.backupToTar(pkg.packageName, FullBackup.APK_TREE_TOKEN, null,
apkDir, appSourceDir, output);
// TODO: migrate this to SharedStorageBackup, since AID_SYSTEM
// doesn't have access to external storage.
// Save associated .obb content if it exists and we did save the apk
// check for .obb and save those too
// TODO: http://b/22388012
final UserEnvironment userEnv = new UserEnvironment(UserHandle.USER_SYSTEM);
final File obbDir = userEnv.buildExternalStorageAppObbDirs(pkg.packageName)[0];
if (obbDir != null) {
if (MORE_DEBUG) {
Log.i(TAG, "obb dir: " + obbDir.getAbsolutePath());
}
File[] obbFiles = obbDir.listFiles();
if (obbFiles != null) {
final String obbDirName = obbDir.getAbsolutePath();
for (File obb : obbFiles) {
FullBackup.backupToTar(pkg.packageName, FullBackup.OBB_TREE_TOKEN, null,
obbDirName, obb.getAbsolutePath(), output);
}
}
}
}
// Widget metadata format. All header entries are strings ending in LF:
//
// Version 1 header:
// BACKUP_METADATA_VERSION, currently "1"
// package name
//
// File data (all integers are binary in network byte order)
// *N: 4 : integer token identifying which metadata blob
// 4 : integer size of this blob = N
// N : raw bytes of this metadata blob
//
// Currently understood blobs (always in network byte order):
//
// widgets : metadata token = 0x01FFED01 (BACKUP_WIDGET_METADATA_TOKEN)
//
// Unrecognized blobs are *ignored*, not errors.
private void writeMetadata(PackageInfo pkg, File destination, byte[] widgetData)
throws IOException {
StringBuilder b = new StringBuilder(512);
StringBuilderPrinter printer = new StringBuilderPrinter(b);
printer.println(Integer.toString(BACKUP_METADATA_VERSION));
printer.println(pkg.packageName);
FileOutputStream fout = new FileOutputStream(destination);
BufferedOutputStream bout = new BufferedOutputStream(fout);
DataOutputStream out = new DataOutputStream(bout);
bout.write(b.toString().getBytes()); // bypassing DataOutputStream
if (widgetData != null && widgetData.length > 0) {
out.writeInt(BACKUP_WIDGET_METADATA_TOKEN);
out.writeInt(widgetData.length);
out.write(widgetData);
}
bout.flush();
out.close();
// As with the manifest file, guarantee idempotence of the archive metadata
// for the widget block by using a fixed mtime on the transient file.
destination.setLastModified(0);
}
private void tearDown() {
if (mPkg != null) {
backupManagerService.tearDownAgentAndKill(mPkg.applicationInfo);
}
}
}