Home | History | Annotate | Download | only in fullbackup
      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