/* * 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); } } }