1 package com.android.server.backup; 2 3 import static android.os.ParcelFileDescriptor.MODE_CREATE; 4 import static android.os.ParcelFileDescriptor.MODE_READ_ONLY; 5 import static android.os.ParcelFileDescriptor.MODE_READ_WRITE; 6 import static android.os.ParcelFileDescriptor.MODE_TRUNCATE; 7 8 import static com.android.server.backup.BackupManagerService.OP_TYPE_BACKUP_WAIT; 9 10 import android.app.ApplicationThreadConstants; 11 import android.app.IBackupAgent; 12 import android.app.backup.FullBackup; 13 import android.app.backup.FullBackupDataOutput; 14 import android.content.pm.ApplicationInfo; 15 import android.content.pm.PackageInfo; 16 import android.content.pm.PackageManager; 17 import android.os.ParcelFileDescriptor; 18 import android.os.RemoteException; 19 import android.os.SELinux; 20 import android.util.Slog; 21 22 import com.android.internal.util.Preconditions; 23 import com.android.server.backup.utils.FullBackupUtils; 24 25 import libcore.io.IoUtils; 26 27 import java.io.File; 28 import java.io.FileNotFoundException; 29 import java.io.FileOutputStream; 30 import java.io.IOException; 31 import java.io.OutputStream; 32 33 /** 34 * Used by BackupManagerService to perform adb backup for key-value packages. At the moment this 35 * class resembles what is done in the standard key-value code paths in BackupManagerService, and 36 * should be unified later. 37 * 38 * TODO: We should create unified backup/restore engines that can be used for both transport and 39 * adb backup/restore, and for fullbackup and key-value backup. 40 */ 41 public class KeyValueAdbBackupEngine { 42 private static final String TAG = "KeyValueAdbBackupEngine"; 43 private static final boolean DEBUG = false; 44 45 private static final String BACKUP_KEY_VALUE_DIRECTORY_NAME = "key_value_dir"; 46 private static final String BACKUP_KEY_VALUE_BLANK_STATE_FILENAME = "blank_state"; 47 private static final String BACKUP_KEY_VALUE_BACKUP_DATA_FILENAME_SUFFIX = ".data"; 48 private static final String BACKUP_KEY_VALUE_NEW_STATE_FILENAME_SUFFIX = ".new"; 49 50 private BackupManagerServiceInterface mBackupManagerService; 51 private final PackageManager mPackageManager; 52 private final OutputStream mOutput; 53 private final PackageInfo mCurrentPackage; 54 private final File mDataDir; 55 private final File mStateDir; 56 private final File mBlankStateName; 57 private final File mBackupDataName; 58 private final File mNewStateName; 59 private final File mManifestFile; 60 private ParcelFileDescriptor mSavedState; 61 private ParcelFileDescriptor mBackupData; 62 private ParcelFileDescriptor mNewState; 63 private final BackupAgentTimeoutParameters mAgentTimeoutParameters; 64 65 public KeyValueAdbBackupEngine(OutputStream output, PackageInfo packageInfo, 66 BackupManagerServiceInterface backupManagerService, PackageManager packageManager, 67 File baseStateDir, File dataDir) { 68 mOutput = output; 69 mCurrentPackage = packageInfo; 70 mBackupManagerService = backupManagerService; 71 mPackageManager = packageManager; 72 73 mDataDir = dataDir; 74 mStateDir = new File(baseStateDir, BACKUP_KEY_VALUE_DIRECTORY_NAME); 75 mStateDir.mkdirs(); 76 77 String pkg = mCurrentPackage.packageName; 78 79 mBlankStateName = new File(mStateDir, BACKUP_KEY_VALUE_BLANK_STATE_FILENAME); 80 mBackupDataName = new File(mDataDir, 81 pkg + BACKUP_KEY_VALUE_BACKUP_DATA_FILENAME_SUFFIX); 82 mNewStateName = new File(mStateDir, 83 pkg + BACKUP_KEY_VALUE_NEW_STATE_FILENAME_SUFFIX); 84 85 mManifestFile = new File(mDataDir, BackupManagerService.BACKUP_MANIFEST_FILENAME); 86 mAgentTimeoutParameters = Preconditions.checkNotNull( 87 backupManagerService.getAgentTimeoutParameters(), 88 "Timeout parameters cannot be null"); 89 } 90 91 public void backupOnePackage() throws IOException { 92 ApplicationInfo targetApp = mCurrentPackage.applicationInfo; 93 94 try { 95 prepareBackupFiles(mCurrentPackage.packageName); 96 97 IBackupAgent agent = bindToAgent(targetApp); 98 99 if (agent == null) { 100 // We failed binding to the agent, so ignore this package 101 Slog.e(TAG, "Failed binding to BackupAgent for package " 102 + mCurrentPackage.packageName); 103 return; 104 } 105 106 // We are bound to agent, initiate backup. 107 if (!invokeAgentForAdbBackup(mCurrentPackage.packageName, agent)) { 108 // Backup failed, skip package. 109 Slog.e(TAG, "Backup Failed for package " + mCurrentPackage.packageName); 110 return; 111 } 112 113 // Backup finished successfully. Copy the backup data to the output stream. 114 writeBackupData(); 115 } catch (FileNotFoundException e) { 116 Slog.e(TAG, "Failed creating files for package " + mCurrentPackage.packageName 117 + " will ignore package. " + e); 118 } finally { 119 // We are either done, failed or have timed out, so do cleanup and kill the agent. 120 cleanup(); 121 } 122 } 123 124 private void prepareBackupFiles(String packageName) throws FileNotFoundException { 125 126 // We pass a blank state to make sure we are getting the complete backup, not just an 127 // increment 128 mSavedState = ParcelFileDescriptor.open(mBlankStateName, 129 MODE_READ_ONLY | MODE_CREATE); // Make an empty file if necessary 130 131 mBackupData = ParcelFileDescriptor.open(mBackupDataName, 132 MODE_READ_WRITE | MODE_CREATE | MODE_TRUNCATE); 133 134 if (!SELinux.restorecon(mBackupDataName)) { 135 Slog.e(TAG, "SELinux restorecon failed on " + mBackupDataName); 136 } 137 138 mNewState = ParcelFileDescriptor.open(mNewStateName, 139 MODE_READ_WRITE | MODE_CREATE | MODE_TRUNCATE); 140 } 141 142 private IBackupAgent bindToAgent(ApplicationInfo targetApp) { 143 try { 144 return mBackupManagerService.bindToAgentSynchronous(targetApp, 145 ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL); 146 } catch (SecurityException e) { 147 Slog.e(TAG, "error in binding to agent for package " + targetApp.packageName 148 + ". " + e); 149 return null; 150 } 151 } 152 153 // Return true on backup success, false otherwise 154 private boolean invokeAgentForAdbBackup(String packageName, IBackupAgent agent) { 155 int token = mBackupManagerService.generateRandomIntegerToken(); 156 long kvBackupAgentTimeoutMillis = mAgentTimeoutParameters.getKvBackupAgentTimeoutMillis(); 157 try { 158 mBackupManagerService.prepareOperationTimeout(token, kvBackupAgentTimeoutMillis, null, 159 OP_TYPE_BACKUP_WAIT); 160 161 // Start backup and wait for BackupManagerService to get callback for success or timeout 162 agent.doBackup( 163 mSavedState, mBackupData, mNewState, Long.MAX_VALUE, token, 164 mBackupManagerService.getBackupManagerBinder(), /*transportFlags=*/ 0); 165 if (!mBackupManagerService.waitUntilOperationComplete(token)) { 166 Slog.e(TAG, "Key-value backup failed on package " + packageName); 167 return false; 168 } 169 if (DEBUG) { 170 Slog.i(TAG, "Key-value backup success for package " + packageName); 171 } 172 return true; 173 } catch (RemoteException e) { 174 Slog.e(TAG, "Error invoking agent for backup on " + packageName + ". " + e); 175 return false; 176 } 177 } 178 179 class KeyValueAdbBackupDataCopier implements Runnable { 180 private final PackageInfo mPackage; 181 private final ParcelFileDescriptor mPipe; 182 private final int mToken; 183 184 KeyValueAdbBackupDataCopier(PackageInfo pack, ParcelFileDescriptor pipe, 185 int token) 186 throws IOException { 187 mPackage = pack; 188 mPipe = ParcelFileDescriptor.dup(pipe.getFileDescriptor()); 189 mToken = token; 190 } 191 192 @Override 193 public void run() { 194 try { 195 FullBackupDataOutput output = new FullBackupDataOutput(mPipe); 196 197 if (DEBUG) { 198 Slog.d(TAG, "Writing manifest for " + mPackage.packageName); 199 } 200 FullBackupUtils.writeAppManifest( 201 mPackage, mPackageManager, mManifestFile, false, false); 202 FullBackup.backupToTar(mPackage.packageName, FullBackup.KEY_VALUE_DATA_TOKEN, null, 203 mDataDir.getAbsolutePath(), 204 mManifestFile.getAbsolutePath(), 205 output); 206 mManifestFile.delete(); 207 208 if (DEBUG) { 209 Slog.d(TAG, "Writing key-value package payload" + mPackage.packageName); 210 } 211 FullBackup.backupToTar(mPackage.packageName, FullBackup.KEY_VALUE_DATA_TOKEN, null, 212 mDataDir.getAbsolutePath(), 213 mBackupDataName.getAbsolutePath(), 214 output); 215 216 // Write EOD marker 217 try { 218 FileOutputStream out = new FileOutputStream(mPipe.getFileDescriptor()); 219 byte[] buf = new byte[4]; 220 out.write(buf); 221 } catch (IOException e) { 222 Slog.e(TAG, "Unable to finalize backup stream!"); 223 } 224 225 try { 226 mBackupManagerService.getBackupManagerBinder().opComplete(mToken, 0); 227 } catch (RemoteException e) { 228 // we'll time out anyway, so we're safe 229 } 230 231 } catch (IOException e) { 232 Slog.e(TAG, "Error running full backup for " + mPackage.packageName + ". " + e); 233 } finally { 234 IoUtils.closeQuietly(mPipe); 235 } 236 } 237 } 238 239 private void writeBackupData() throws IOException { 240 int token = mBackupManagerService.generateRandomIntegerToken(); 241 long kvBackupAgentTimeoutMillis = mAgentTimeoutParameters.getKvBackupAgentTimeoutMillis(); 242 243 ParcelFileDescriptor[] pipes = null; 244 try { 245 pipes = ParcelFileDescriptor.createPipe(); 246 247 mBackupManagerService.prepareOperationTimeout(token, kvBackupAgentTimeoutMillis, null, 248 OP_TYPE_BACKUP_WAIT); 249 250 // We will have to create a runnable that will read the manifest and backup data we 251 // created, such that we can pipe the data into mOutput. The reason we do this is that 252 // internally FullBackup.backupToTar is used, which will create the necessary file 253 // header, but will also chunk the data. The method routeSocketDataToOutput in 254 // BackupManagerService will dechunk the data, and append it to the TAR outputstream. 255 KeyValueAdbBackupDataCopier runner = new KeyValueAdbBackupDataCopier(mCurrentPackage, pipes[1], 256 token); 257 pipes[1].close(); // the runner has dup'd it 258 pipes[1] = null; 259 Thread t = new Thread(runner, "key-value-app-data-runner"); 260 t.start(); 261 262 // Now pull data from the app and stuff it into the output 263 FullBackupUtils.routeSocketDataToOutput(pipes[0], mOutput); 264 265 if (!mBackupManagerService.waitUntilOperationComplete(token)) { 266 Slog.e(TAG, "Full backup failed on package " + mCurrentPackage.packageName); 267 } else { 268 if (DEBUG) { 269 Slog.d(TAG, "Full package backup success: " + mCurrentPackage.packageName); 270 } 271 } 272 } catch (IOException e) { 273 Slog.e(TAG, "Error backing up " + mCurrentPackage.packageName + ": " + e); 274 } finally { 275 // flush after every package 276 mOutput.flush(); 277 if (pipes != null) { 278 IoUtils.closeQuietly(pipes[0]); 279 IoUtils.closeQuietly(pipes[1]); 280 } 281 } 282 } 283 284 private void cleanup() { 285 mBackupManagerService.tearDownAgentAndKill(mCurrentPackage.applicationInfo); 286 mBlankStateName.delete(); 287 mNewStateName.delete(); 288 mBackupDataName.delete(); 289 } 290 } 291