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