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