1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17 package com.android.server.backup.fullbackup; 18 19 import static com.android.server.backup.BackupManagerService.BACKUP_MANIFEST_FILENAME; 20 import static com.android.server.backup.BackupManagerService.BACKUP_METADATA_FILENAME; 21 import static com.android.server.backup.BackupManagerService.BACKUP_METADATA_VERSION; 22 import static com.android.server.backup.BackupManagerService.BACKUP_WIDGET_METADATA_TOKEN; 23 import static com.android.server.backup.BackupManagerService.DEBUG; 24 import static com.android.server.backup.BackupManagerService.MORE_DEBUG; 25 import static com.android.server.backup.BackupManagerService.OP_TYPE_BACKUP_WAIT; 26 import static com.android.server.backup.BackupManagerService.SHARED_BACKUP_AGENT_PACKAGE; 27 import static com.android.server.backup.BackupManagerService.TAG; 28 29 import android.app.ApplicationThreadConstants; 30 import android.app.IBackupAgent; 31 import android.app.backup.BackupTransport; 32 import android.app.backup.FullBackup; 33 import android.app.backup.FullBackupDataOutput; 34 import android.content.pm.ApplicationInfo; 35 import android.content.pm.PackageInfo; 36 import android.os.Environment.UserEnvironment; 37 import android.os.ParcelFileDescriptor; 38 import android.os.RemoteException; 39 import android.os.UserHandle; 40 import android.util.Log; 41 import android.util.Slog; 42 import android.util.StringBuilderPrinter; 43 44 import com.android.internal.util.Preconditions; 45 import com.android.server.AppWidgetBackupBridge; 46 import com.android.server.backup.BackupAgentTimeoutParameters; 47 import com.android.server.backup.BackupManagerService; 48 import com.android.server.backup.BackupRestoreTask; 49 import com.android.server.backup.utils.FullBackupUtils; 50 51 import java.io.BufferedOutputStream; 52 import java.io.DataOutputStream; 53 import java.io.File; 54 import java.io.FileOutputStream; 55 import java.io.IOException; 56 import java.io.OutputStream; 57 58 /** 59 * Core logic for performing one package's full backup, gathering the tarball from the 60 * application and emitting it to the designated OutputStream. 61 */ 62 public class FullBackupEngine { 63 64 private BackupManagerService backupManagerService; 65 OutputStream mOutput; 66 FullBackupPreflight mPreflightHook; 67 BackupRestoreTask mTimeoutMonitor; 68 IBackupAgent mAgent; 69 File mFilesDir; 70 File mManifestFile; 71 File mMetadataFile; 72 boolean mIncludeApks; 73 PackageInfo mPkg; 74 private final long mQuota; 75 private final int mOpToken; 76 private final int mTransportFlags; 77 private final BackupAgentTimeoutParameters mAgentTimeoutParameters; 78 79 class FullBackupRunner implements Runnable { 80 81 PackageInfo mPackage; 82 byte[] mWidgetData; 83 IBackupAgent mAgent; 84 ParcelFileDescriptor mPipe; 85 int mToken; 86 boolean mSendApk; 87 boolean mWriteManifest; 88 FullBackupRunner(PackageInfo pack, IBackupAgent agent, ParcelFileDescriptor pipe, int token, boolean sendApk, boolean writeManifest, byte[] widgetData)89 FullBackupRunner(PackageInfo pack, IBackupAgent agent, ParcelFileDescriptor pipe, 90 int token, boolean sendApk, boolean writeManifest, byte[] widgetData) 91 throws IOException { 92 mPackage = pack; 93 mWidgetData = widgetData; 94 mAgent = agent; 95 mPipe = ParcelFileDescriptor.dup(pipe.getFileDescriptor()); 96 mToken = token; 97 mSendApk = sendApk; 98 mWriteManifest = writeManifest; 99 } 100 101 @Override run()102 public void run() { 103 try { 104 FullBackupDataOutput output = new FullBackupDataOutput( 105 mPipe, -1, mTransportFlags); 106 107 if (mWriteManifest) { 108 final boolean writeWidgetData = mWidgetData != null; 109 if (MORE_DEBUG) { 110 Slog.d(TAG, "Writing manifest for " + mPackage.packageName); 111 } 112 FullBackupUtils 113 .writeAppManifest(mPackage, backupManagerService.getPackageManager(), 114 mManifestFile, mSendApk, 115 writeWidgetData); 116 FullBackup.backupToTar(mPackage.packageName, null, null, 117 mFilesDir.getAbsolutePath(), 118 mManifestFile.getAbsolutePath(), 119 output); 120 mManifestFile.delete(); 121 122 // We only need to write a metadata file if we have widget data to stash 123 if (writeWidgetData) { 124 writeMetadata(mPackage, mMetadataFile, mWidgetData); 125 FullBackup.backupToTar(mPackage.packageName, null, null, 126 mFilesDir.getAbsolutePath(), 127 mMetadataFile.getAbsolutePath(), 128 output); 129 mMetadataFile.delete(); 130 } 131 } 132 133 if (mSendApk) { 134 writeApkToBackup(mPackage, output); 135 } 136 137 final boolean isSharedStorage = 138 mPackage.packageName.equals(SHARED_BACKUP_AGENT_PACKAGE); 139 final long timeout = isSharedStorage ? 140 mAgentTimeoutParameters.getSharedBackupAgentTimeoutMillis() : 141 mAgentTimeoutParameters.getFullBackupAgentTimeoutMillis(); 142 143 if (DEBUG) { 144 Slog.d(TAG, "Calling doFullBackup() on " + mPackage.packageName); 145 } 146 backupManagerService 147 .prepareOperationTimeout(mToken, 148 timeout, 149 mTimeoutMonitor /* in parent class */, 150 OP_TYPE_BACKUP_WAIT); 151 mAgent.doFullBackup(mPipe, mQuota, mToken, 152 backupManagerService.getBackupManagerBinder(), mTransportFlags); 153 } catch (IOException e) { 154 Slog.e(TAG, "Error running full backup for " + mPackage.packageName); 155 } catch (RemoteException e) { 156 Slog.e(TAG, "Remote agent vanished during full backup of " + mPackage.packageName); 157 } finally { 158 try { 159 mPipe.close(); 160 } catch (IOException e) { 161 } 162 } 163 } 164 } 165 FullBackupEngine(BackupManagerService backupManagerService, OutputStream output, FullBackupPreflight preflightHook, PackageInfo pkg, boolean alsoApks, BackupRestoreTask timeoutMonitor, long quota, int opToken, int transportFlags)166 public FullBackupEngine(BackupManagerService backupManagerService, 167 OutputStream output, 168 FullBackupPreflight preflightHook, PackageInfo pkg, 169 boolean alsoApks, BackupRestoreTask timeoutMonitor, long quota, int opToken, 170 int transportFlags) { 171 this.backupManagerService = backupManagerService; 172 mOutput = output; 173 mPreflightHook = preflightHook; 174 mPkg = pkg; 175 mIncludeApks = alsoApks; 176 mTimeoutMonitor = timeoutMonitor; 177 mFilesDir = new File("/data/system"); 178 mManifestFile = new File(mFilesDir, BACKUP_MANIFEST_FILENAME); 179 mMetadataFile = new File(mFilesDir, BACKUP_METADATA_FILENAME); 180 mQuota = quota; 181 mOpToken = opToken; 182 mTransportFlags = transportFlags; 183 mAgentTimeoutParameters = Preconditions.checkNotNull( 184 backupManagerService.getAgentTimeoutParameters(), 185 "Timeout parameters cannot be null"); 186 } 187 preflightCheck()188 public int preflightCheck() throws RemoteException { 189 if (mPreflightHook == null) { 190 if (MORE_DEBUG) { 191 Slog.v(TAG, "No preflight check"); 192 } 193 return BackupTransport.TRANSPORT_OK; 194 } 195 if (initializeAgent()) { 196 int result = mPreflightHook.preflightFullBackup(mPkg, mAgent); 197 if (MORE_DEBUG) { 198 Slog.v(TAG, "preflight returned " + result); 199 } 200 return result; 201 } else { 202 Slog.w(TAG, "Unable to bind to full agent for " + mPkg.packageName); 203 return BackupTransport.AGENT_ERROR; 204 } 205 } 206 backupOnePackage()207 public int backupOnePackage() throws RemoteException { 208 int result = BackupTransport.AGENT_ERROR; 209 210 if (initializeAgent()) { 211 ParcelFileDescriptor[] pipes = null; 212 try { 213 pipes = ParcelFileDescriptor.createPipe(); 214 215 ApplicationInfo app = mPkg.applicationInfo; 216 final boolean isSharedStorage = 217 mPkg.packageName.equals(SHARED_BACKUP_AGENT_PACKAGE); 218 final boolean sendApk = mIncludeApks 219 && !isSharedStorage 220 && ((app.privateFlags & ApplicationInfo.PRIVATE_FLAG_FORWARD_LOCK) == 0) 221 && ((app.flags & ApplicationInfo.FLAG_SYSTEM) == 0 || 222 (app.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0); 223 224 // TODO: http://b/22388012 225 byte[] widgetBlob = AppWidgetBackupBridge.getWidgetState(mPkg.packageName, 226 UserHandle.USER_SYSTEM); 227 228 FullBackupRunner runner = new FullBackupRunner(mPkg, mAgent, pipes[1], 229 mOpToken, sendApk, !isSharedStorage, widgetBlob); 230 pipes[1].close(); // the runner has dup'd it 231 pipes[1] = null; 232 Thread t = new Thread(runner, "app-data-runner"); 233 t.start(); 234 235 // Now pull data from the app and stuff it into the output 236 FullBackupUtils.routeSocketDataToOutput(pipes[0], mOutput); 237 238 if (!backupManagerService.waitUntilOperationComplete(mOpToken)) { 239 Slog.e(TAG, "Full backup failed on package " + mPkg.packageName); 240 } else { 241 if (MORE_DEBUG) { 242 Slog.d(TAG, "Full package backup success: " + mPkg.packageName); 243 } 244 result = BackupTransport.TRANSPORT_OK; 245 } 246 } catch (IOException e) { 247 Slog.e(TAG, "Error backing up " + mPkg.packageName + ": " + e.getMessage()); 248 result = BackupTransport.AGENT_ERROR; 249 } finally { 250 try { 251 // flush after every package 252 mOutput.flush(); 253 if (pipes != null) { 254 if (pipes[0] != null) { 255 pipes[0].close(); 256 } 257 if (pipes[1] != null) { 258 pipes[1].close(); 259 } 260 } 261 } catch (IOException e) { 262 Slog.w(TAG, "Error bringing down backup stack"); 263 result = BackupTransport.TRANSPORT_ERROR; 264 } 265 } 266 } else { 267 Slog.w(TAG, "Unable to bind to full agent for " + mPkg.packageName); 268 } 269 tearDown(); 270 return result; 271 } 272 sendQuotaExceeded(final long backupDataBytes, final long quotaBytes)273 public void sendQuotaExceeded(final long backupDataBytes, final long quotaBytes) { 274 if (initializeAgent()) { 275 try { 276 mAgent.doQuotaExceeded(backupDataBytes, quotaBytes); 277 } catch (RemoteException e) { 278 Slog.e(TAG, "Remote exception while telling agent about quota exceeded"); 279 } 280 } 281 } 282 initializeAgent()283 private boolean initializeAgent() { 284 if (mAgent == null) { 285 if (MORE_DEBUG) { 286 Slog.d(TAG, "Binding to full backup agent : " + mPkg.packageName); 287 } 288 mAgent = backupManagerService.bindToAgentSynchronous(mPkg.applicationInfo, 289 ApplicationThreadConstants.BACKUP_MODE_FULL); 290 } 291 return mAgent != null; 292 } 293 writeApkToBackup(PackageInfo pkg, FullBackupDataOutput output)294 private void writeApkToBackup(PackageInfo pkg, FullBackupDataOutput output) { 295 // Forward-locked apps, system-bundled .apks, etc are filtered out before we get here 296 // TODO: handle backing up split APKs 297 final String appSourceDir = pkg.applicationInfo.getBaseCodePath(); 298 final String apkDir = new File(appSourceDir).getParent(); 299 FullBackup.backupToTar(pkg.packageName, FullBackup.APK_TREE_TOKEN, null, 300 apkDir, appSourceDir, output); 301 302 // TODO: migrate this to SharedStorageBackup, since AID_SYSTEM 303 // doesn't have access to external storage. 304 305 // Save associated .obb content if it exists and we did save the apk 306 // check for .obb and save those too 307 // TODO: http://b/22388012 308 final UserEnvironment userEnv = new UserEnvironment(UserHandle.USER_SYSTEM); 309 final File obbDir = userEnv.buildExternalStorageAppObbDirs(pkg.packageName)[0]; 310 if (obbDir != null) { 311 if (MORE_DEBUG) { 312 Log.i(TAG, "obb dir: " + obbDir.getAbsolutePath()); 313 } 314 File[] obbFiles = obbDir.listFiles(); 315 if (obbFiles != null) { 316 final String obbDirName = obbDir.getAbsolutePath(); 317 for (File obb : obbFiles) { 318 FullBackup.backupToTar(pkg.packageName, FullBackup.OBB_TREE_TOKEN, null, 319 obbDirName, obb.getAbsolutePath(), output); 320 } 321 } 322 } 323 } 324 325 // Widget metadata format. All header entries are strings ending in LF: 326 // 327 // Version 1 header: 328 // BACKUP_METADATA_VERSION, currently "1" 329 // package name 330 // 331 // File data (all integers are binary in network byte order) 332 // *N: 4 : integer token identifying which metadata blob 333 // 4 : integer size of this blob = N 334 // N : raw bytes of this metadata blob 335 // 336 // Currently understood blobs (always in network byte order): 337 // 338 // widgets : metadata token = 0x01FFED01 (BACKUP_WIDGET_METADATA_TOKEN) 339 // 340 // Unrecognized blobs are *ignored*, not errors. writeMetadata(PackageInfo pkg, File destination, byte[] widgetData)341 private void writeMetadata(PackageInfo pkg, File destination, byte[] widgetData) 342 throws IOException { 343 StringBuilder b = new StringBuilder(512); 344 StringBuilderPrinter printer = new StringBuilderPrinter(b); 345 printer.println(Integer.toString(BACKUP_METADATA_VERSION)); 346 printer.println(pkg.packageName); 347 348 FileOutputStream fout = new FileOutputStream(destination); 349 BufferedOutputStream bout = new BufferedOutputStream(fout); 350 DataOutputStream out = new DataOutputStream(bout); 351 bout.write(b.toString().getBytes()); // bypassing DataOutputStream 352 353 if (widgetData != null && widgetData.length > 0) { 354 out.writeInt(BACKUP_WIDGET_METADATA_TOKEN); 355 out.writeInt(widgetData.length); 356 out.write(widgetData); 357 } 358 bout.flush(); 359 out.close(); 360 361 // As with the manifest file, guarantee idempotence of the archive metadata 362 // for the widget block by using a fixed mtime on the transient file. 363 destination.setLastModified(0); 364 } 365 tearDown()366 private void tearDown() { 367 if (mPkg != null) { 368 backupManagerService.tearDownAgentAndKill(mPkg.applicationInfo); 369 } 370 } 371 } 372