Justin Klaassen | 10d07c8 | 2017-09-15 17:58:39 -0400 | [diff] [blame] | 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; |
Justin Klaassen | 4d01eea | 2018-04-03 23:21:57 -0400 | [diff] [blame^] | 7 | |
| 8 | import static com.android.server.backup.BackupManagerService.OP_TYPE_BACKUP_WAIT; |
Justin Klaassen | 10d07c8 | 2017-09-15 17:58:39 -0400 | [diff] [blame] | 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 | |
Justin Klaassen | 4d01eea | 2018-04-03 23:21:57 -0400 | [diff] [blame^] | 22 | import com.android.internal.util.Preconditions; |
Justin Klaassen | 93b7ee4 | 2017-10-10 15:20:13 -0400 | [diff] [blame] | 23 | import com.android.server.backup.utils.FullBackupUtils; |
| 24 | |
Justin Klaassen | 10d07c8 | 2017-09-15 17:58:39 -0400 | [diff] [blame] | 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; |
Justin Klaassen | 4d01eea | 2018-04-03 23:21:57 -0400 | [diff] [blame^] | 63 | private final BackupAgentTimeoutParameters mAgentTimeoutParameters; |
Justin Klaassen | 10d07c8 | 2017-09-15 17:58:39 -0400 | [diff] [blame] | 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 | |
Justin Klaassen | 4d01eea | 2018-04-03 23:21:57 -0400 | [diff] [blame^] | 85 | mManifestFile = new File(mDataDir, BackupManagerService.BACKUP_MANIFEST_FILENAME); |
| 86 | mAgentTimeoutParameters = Preconditions.checkNotNull( |
| 87 | backupManagerService.getAgentTimeoutParameters(), |
| 88 | "Timeout parameters cannot be null"); |
Justin Klaassen | 10d07c8 | 2017-09-15 17:58:39 -0400 | [diff] [blame] | 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(); |
Justin Klaassen | 4d01eea | 2018-04-03 23:21:57 -0400 | [diff] [blame^] | 156 | long kvBackupAgentTimeoutMillis = mAgentTimeoutParameters.getKvBackupAgentTimeoutMillis(); |
Justin Klaassen | 10d07c8 | 2017-09-15 17:58:39 -0400 | [diff] [blame] | 157 | try { |
Justin Klaassen | 4d01eea | 2018-04-03 23:21:57 -0400 | [diff] [blame^] | 158 | mBackupManagerService.prepareOperationTimeout(token, kvBackupAgentTimeoutMillis, null, |
Justin Klaassen | 10d07c8 | 2017-09-15 17:58:39 -0400 | [diff] [blame] | 159 | OP_TYPE_BACKUP_WAIT); |
| 160 | |
| 161 | // Start backup and wait for BackupManagerService to get callback for success or timeout |
Justin Klaassen | 4d01eea | 2018-04-03 23:21:57 -0400 | [diff] [blame^] | 162 | agent.doBackup( |
| 163 | mSavedState, mBackupData, mNewState, Long.MAX_VALUE, token, |
| 164 | mBackupManagerService.getBackupManagerBinder(), /*transportFlags=*/ 0); |
Justin Klaassen | 10d07c8 | 2017-09-15 17:58:39 -0400 | [diff] [blame] | 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 | } |
Justin Klaassen | 93b7ee4 | 2017-10-10 15:20:13 -0400 | [diff] [blame] | 200 | FullBackupUtils.writeAppManifest( |
Justin Klaassen | 10d07c8 | 2017-09-15 17:58:39 -0400 | [diff] [blame] | 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 { |
Justin Klaassen | 10d07c8 | 2017-09-15 17:58:39 -0400 | [diff] [blame] | 240 | int token = mBackupManagerService.generateRandomIntegerToken(); |
Justin Klaassen | 4d01eea | 2018-04-03 23:21:57 -0400 | [diff] [blame^] | 241 | long kvBackupAgentTimeoutMillis = mAgentTimeoutParameters.getKvBackupAgentTimeoutMillis(); |
Justin Klaassen | 10d07c8 | 2017-09-15 17:58:39 -0400 | [diff] [blame] | 242 | |
| 243 | ParcelFileDescriptor[] pipes = null; |
| 244 | try { |
| 245 | pipes = ParcelFileDescriptor.createPipe(); |
| 246 | |
Justin Klaassen | 4d01eea | 2018-04-03 23:21:57 -0400 | [diff] [blame^] | 247 | mBackupManagerService.prepareOperationTimeout(token, kvBackupAgentTimeoutMillis, null, |
Justin Klaassen | 10d07c8 | 2017-09-15 17:58:39 -0400 | [diff] [blame] | 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 |
Justin Klaassen | 93b7ee4 | 2017-10-10 15:20:13 -0400 | [diff] [blame] | 263 | FullBackupUtils.routeSocketDataToOutput(pipes[0], mOutput); |
Justin Klaassen | 10d07c8 | 2017-09-15 17:58:39 -0400 | [diff] [blame] | 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 | } |