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