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 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 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 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 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 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 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 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 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. 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 366 private void tearDown() { 367 if (mPkg != null) { 368 backupManagerService.tearDownAgentAndKill(mPkg.applicationInfo); 369 } 370 } 371 } 372