Home | History | Annotate | Download | only in backup
      1 /*
      2  * Copyright (C) 2009 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.internal.backup;
     18 
     19 import android.app.backup.BackupDataInput;
     20 import android.app.backup.BackupDataOutput;
     21 import android.app.backup.BackupTransport;
     22 import android.app.backup.RestoreDescription;
     23 import android.app.backup.RestoreSet;
     24 import android.content.ComponentName;
     25 import android.content.Context;
     26 import android.content.Intent;
     27 import android.content.pm.PackageInfo;
     28 import android.os.Environment;
     29 import android.os.ParcelFileDescriptor;
     30 import android.system.ErrnoException;
     31 import android.system.Os;
     32 import android.system.StructStat;
     33 import android.util.Log;
     34 
     35 import com.android.org.bouncycastle.util.encoders.Base64;
     36 
     37 import libcore.io.IoUtils;
     38 
     39 import java.io.BufferedOutputStream;
     40 import java.io.File;
     41 import java.io.FileInputStream;
     42 import java.io.FileNotFoundException;
     43 import java.io.FileOutputStream;
     44 import java.io.IOException;
     45 import java.util.ArrayList;
     46 import java.util.Collections;
     47 import static android.system.OsConstants.SEEK_CUR;
     48 
     49 /**
     50  * Backup transport for stashing stuff into a known location on disk, and
     51  * later restoring from there.  For testing only.
     52  */
     53 
     54 public class LocalTransport extends BackupTransport {
     55     private static final String TAG = "LocalTransport";
     56     private static final boolean DEBUG = false;
     57 
     58     private static final String TRANSPORT_DIR_NAME
     59             = "com.android.internal.backup.LocalTransport";
     60 
     61     private static final String TRANSPORT_DESTINATION_STRING
     62             = "Backing up to debug-only private cache";
     63 
     64     private static final String TRANSPORT_DATA_MANAGEMENT_LABEL
     65             = "";
     66 
     67     private static final String INCREMENTAL_DIR = "_delta";
     68     private static final String FULL_DATA_DIR = "_full";
     69 
     70     // The currently-active restore set always has the same (nonzero!) token
     71     private static final long CURRENT_SET_TOKEN = 1;
     72 
     73     // Full backup size quota is set to reasonable value.
     74     private static final long FULL_BACKUP_SIZE_QUOTA = 25 * 1024 * 1024;
     75 
     76     private static final long KEY_VALUE_BACKUP_SIZE_QUOTA = 5 * 1024 * 1024;
     77 
     78     private Context mContext;
     79     private File mDataDir = new File(Environment.getDownloadCacheDirectory(), "backup");
     80     private File mCurrentSetDir = new File(mDataDir, Long.toString(CURRENT_SET_TOKEN));
     81     private File mCurrentSetIncrementalDir = new File(mCurrentSetDir, INCREMENTAL_DIR);
     82     private File mCurrentSetFullDir = new File(mCurrentSetDir, FULL_DATA_DIR);
     83 
     84     private PackageInfo[] mRestorePackages = null;
     85     private int mRestorePackage = -1;  // Index into mRestorePackages
     86     private int mRestoreType;
     87     private File mRestoreSetDir;
     88     private File mRestoreSetIncrementalDir;
     89     private File mRestoreSetFullDir;
     90 
     91     // Additional bookkeeping for full backup
     92     private String mFullTargetPackage;
     93     private ParcelFileDescriptor mSocket;
     94     private FileInputStream mSocketInputStream;
     95     private BufferedOutputStream mFullBackupOutputStream;
     96     private byte[] mFullBackupBuffer;
     97     private long mFullBackupSize;
     98 
     99     private FileInputStream mCurFullRestoreStream;
    100     private FileOutputStream mFullRestoreSocketStream;
    101     private byte[] mFullRestoreBuffer;
    102 
    103     private void makeDataDirs() {
    104         mCurrentSetDir.mkdirs();
    105         mCurrentSetFullDir.mkdir();
    106         mCurrentSetIncrementalDir.mkdir();
    107     }
    108 
    109     public LocalTransport(Context context) {
    110         mContext = context;
    111         makeDataDirs();
    112     }
    113 
    114     @Override
    115     public String name() {
    116         return new ComponentName(mContext, this.getClass()).flattenToShortString();
    117     }
    118 
    119     @Override
    120     public Intent configurationIntent() {
    121         // The local transport is not user-configurable
    122         return null;
    123     }
    124 
    125     @Override
    126     public String currentDestinationString() {
    127         return TRANSPORT_DESTINATION_STRING;
    128     }
    129 
    130     public Intent dataManagementIntent() {
    131         // The local transport does not present a data-management UI
    132         // TODO: consider adding simple UI to wipe the archives entirely,
    133         // for cleaning up the cache partition.
    134         return null;
    135     }
    136 
    137     public String dataManagementLabel() {
    138         return TRANSPORT_DATA_MANAGEMENT_LABEL;
    139     }
    140 
    141     @Override
    142     public String transportDirName() {
    143         return TRANSPORT_DIR_NAME;
    144     }
    145 
    146     @Override
    147     public long requestBackupTime() {
    148         // any time is a good time for local backup
    149         return 0;
    150     }
    151 
    152     @Override
    153     public int initializeDevice() {
    154         if (DEBUG) Log.v(TAG, "wiping all data");
    155         deleteContents(mCurrentSetDir);
    156         makeDataDirs();
    157         return TRANSPORT_OK;
    158     }
    159 
    160     @Override
    161     public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor data) {
    162         if (DEBUG) {
    163             try {
    164             StructStat ss = Os.fstat(data.getFileDescriptor());
    165             Log.v(TAG, "performBackup() pkg=" + packageInfo.packageName
    166                     + " size=" + ss.st_size);
    167             } catch (ErrnoException e) {
    168                 Log.w(TAG, "Unable to stat input file in performBackup() on "
    169                         + packageInfo.packageName);
    170             }
    171         }
    172 
    173         File packageDir = new File(mCurrentSetIncrementalDir, packageInfo.packageName);
    174         packageDir.mkdirs();
    175 
    176         // Each 'record' in the restore set is kept in its own file, named by
    177         // the record key.  Wind through the data file, extracting individual
    178         // record operations and building a set of all the updates to apply
    179         // in this update.
    180         BackupDataInput changeSet = new BackupDataInput(data.getFileDescriptor());
    181         try {
    182             int bufSize = 512;
    183             byte[] buf = new byte[bufSize];
    184             while (changeSet.readNextHeader()) {
    185                 String key = changeSet.getKey();
    186                 String base64Key = new String(Base64.encode(key.getBytes()));
    187                 File entityFile = new File(packageDir, base64Key);
    188 
    189                 int dataSize = changeSet.getDataSize();
    190 
    191                 if (DEBUG) Log.v(TAG, "Got change set key=" + key + " size=" + dataSize
    192                         + " key64=" + base64Key);
    193 
    194                 if (dataSize >= 0) {
    195                     if (entityFile.exists()) {
    196                         entityFile.delete();
    197                     }
    198                     FileOutputStream entity = new FileOutputStream(entityFile);
    199 
    200                     if (dataSize > bufSize) {
    201                         bufSize = dataSize;
    202                         buf = new byte[bufSize];
    203                     }
    204                     changeSet.readEntityData(buf, 0, dataSize);
    205                     if (DEBUG) {
    206                         try {
    207                             long cur = Os.lseek(data.getFileDescriptor(), 0, SEEK_CUR);
    208                             Log.v(TAG, "  read entity data; new pos=" + cur);
    209                         }
    210                         catch (ErrnoException e) {
    211                             Log.w(TAG, "Unable to stat input file in performBackup() on "
    212                                     + packageInfo.packageName);
    213                         }
    214                     }
    215 
    216                     try {
    217                         entity.write(buf, 0, dataSize);
    218                     } catch (IOException e) {
    219                         Log.e(TAG, "Unable to update key file " + entityFile.getAbsolutePath());
    220                         return TRANSPORT_ERROR;
    221                     } finally {
    222                         entity.close();
    223                     }
    224                 } else {
    225                     entityFile.delete();
    226                 }
    227             }
    228             return TRANSPORT_OK;
    229         } catch (IOException e) {
    230             // oops, something went wrong.  abort the operation and return error.
    231             Log.v(TAG, "Exception reading backup input:", e);
    232             return TRANSPORT_ERROR;
    233         }
    234     }
    235 
    236     // Deletes the contents but not the given directory
    237     private void deleteContents(File dirname) {
    238         File[] contents = dirname.listFiles();
    239         if (contents != null) {
    240             for (File f : contents) {
    241                 if (f.isDirectory()) {
    242                     // delete the directory's contents then fall through
    243                     // and delete the directory itself.
    244                     deleteContents(f);
    245                 }
    246                 f.delete();
    247             }
    248         }
    249     }
    250 
    251     @Override
    252     public int clearBackupData(PackageInfo packageInfo) {
    253         if (DEBUG) Log.v(TAG, "clearBackupData() pkg=" + packageInfo.packageName);
    254 
    255         File packageDir = new File(mCurrentSetIncrementalDir, packageInfo.packageName);
    256         final File[] fileset = packageDir.listFiles();
    257         if (fileset != null) {
    258             for (File f : fileset) {
    259                 f.delete();
    260             }
    261             packageDir.delete();
    262         }
    263 
    264         packageDir = new File(mCurrentSetFullDir, packageInfo.packageName);
    265         final File[] tarballs = packageDir.listFiles();
    266         if (tarballs != null) {
    267             for (File f : tarballs) {
    268                 f.delete();
    269             }
    270             packageDir.delete();
    271         }
    272 
    273         return TRANSPORT_OK;
    274     }
    275 
    276     @Override
    277     public int finishBackup() {
    278         if (DEBUG) Log.v(TAG, "finishBackup() of " + mFullTargetPackage);
    279         return tearDownFullBackup();
    280     }
    281 
    282     // ------------------------------------------------------------------------------------
    283     // Full backup handling
    284 
    285     private int tearDownFullBackup() {
    286         if (mSocket != null) {
    287             try {
    288                 if (mFullBackupOutputStream != null) {
    289                     mFullBackupOutputStream.flush();
    290                     mFullBackupOutputStream.close();
    291                 }
    292                 mSocketInputStream = null;
    293                 mFullTargetPackage = null;
    294                 mSocket.close();
    295             } catch (IOException e) {
    296                 if (DEBUG) {
    297                     Log.w(TAG, "Exception caught in tearDownFullBackup()", e);
    298                 }
    299                 return TRANSPORT_ERROR;
    300             } finally {
    301                 mSocket = null;
    302                 mFullBackupOutputStream = null;
    303             }
    304         }
    305         return TRANSPORT_OK;
    306     }
    307 
    308     private File tarballFile(String pkgName) {
    309         return new File(mCurrentSetFullDir, pkgName);
    310     }
    311 
    312     @Override
    313     public long requestFullBackupTime() {
    314         return 0;
    315     }
    316 
    317     @Override
    318     public int checkFullBackupSize(long size) {
    319         int result = TRANSPORT_OK;
    320         // Decline zero-size "backups"
    321         if (size <= 0) {
    322             result = TRANSPORT_PACKAGE_REJECTED;
    323         } else if (size > FULL_BACKUP_SIZE_QUOTA) {
    324             result = TRANSPORT_QUOTA_EXCEEDED;
    325         }
    326         if (result != TRANSPORT_OK) {
    327             if (DEBUG) {
    328                 Log.v(TAG, "Declining backup of size " + size);
    329             }
    330         }
    331         return result;
    332     }
    333 
    334     @Override
    335     public int performFullBackup(PackageInfo targetPackage, ParcelFileDescriptor socket) {
    336         if (mSocket != null) {
    337             Log.e(TAG, "Attempt to initiate full backup while one is in progress");
    338             return TRANSPORT_ERROR;
    339         }
    340 
    341         if (DEBUG) {
    342             Log.i(TAG, "performFullBackup : " + targetPackage);
    343         }
    344 
    345         // We know a priori that we run in the system process, so we need to make
    346         // sure to dup() our own copy of the socket fd.  Transports which run in
    347         // their own processes must not do this.
    348         try {
    349             mFullBackupSize = 0;
    350             mSocket = ParcelFileDescriptor.dup(socket.getFileDescriptor());
    351             mSocketInputStream = new FileInputStream(mSocket.getFileDescriptor());
    352         } catch (IOException e) {
    353             Log.e(TAG, "Unable to process socket for full backup");
    354             return TRANSPORT_ERROR;
    355         }
    356 
    357         mFullTargetPackage = targetPackage.packageName;
    358         mFullBackupBuffer = new byte[4096];
    359 
    360         return TRANSPORT_OK;
    361     }
    362 
    363     @Override
    364     public int sendBackupData(final int numBytes) {
    365         if (mSocket == null) {
    366             Log.w(TAG, "Attempted sendBackupData before performFullBackup");
    367             return TRANSPORT_ERROR;
    368         }
    369 
    370         mFullBackupSize += numBytes;
    371         if (mFullBackupSize > FULL_BACKUP_SIZE_QUOTA) {
    372             return TRANSPORT_QUOTA_EXCEEDED;
    373         }
    374 
    375         if (numBytes > mFullBackupBuffer.length) {
    376             mFullBackupBuffer = new byte[numBytes];
    377         }
    378 
    379         if (mFullBackupOutputStream == null) {
    380             FileOutputStream tarstream;
    381             try {
    382                 File tarball = tarballFile(mFullTargetPackage);
    383                 tarstream = new FileOutputStream(tarball);
    384             } catch (FileNotFoundException e) {
    385                 return TRANSPORT_ERROR;
    386             }
    387             mFullBackupOutputStream = new BufferedOutputStream(tarstream);
    388         }
    389 
    390         int bytesLeft = numBytes;
    391         while (bytesLeft > 0) {
    392             try {
    393             int nRead = mSocketInputStream.read(mFullBackupBuffer, 0, bytesLeft);
    394             if (nRead < 0) {
    395                 // Something went wrong if we expect data but saw EOD
    396                 Log.w(TAG, "Unexpected EOD; failing backup");
    397                 return TRANSPORT_ERROR;
    398             }
    399             mFullBackupOutputStream.write(mFullBackupBuffer, 0, nRead);
    400             bytesLeft -= nRead;
    401             } catch (IOException e) {
    402                 Log.e(TAG, "Error handling backup data for " + mFullTargetPackage);
    403                 return TRANSPORT_ERROR;
    404             }
    405         }
    406         if (DEBUG) {
    407             Log.v(TAG, "   stored " + numBytes + " of data");
    408         }
    409         return TRANSPORT_OK;
    410     }
    411 
    412     // For now we can't roll back, so just tear everything down.
    413     @Override
    414     public void cancelFullBackup() {
    415         if (DEBUG) {
    416             Log.i(TAG, "Canceling full backup of " + mFullTargetPackage);
    417         }
    418         File archive = tarballFile(mFullTargetPackage);
    419         tearDownFullBackup();
    420         if (archive.exists()) {
    421             archive.delete();
    422         }
    423     }
    424 
    425     // ------------------------------------------------------------------------------------
    426     // Restore handling
    427     static final long[] POSSIBLE_SETS = { 2, 3, 4, 5, 6, 7, 8, 9 };
    428 
    429     @Override
    430     public RestoreSet[] getAvailableRestoreSets() {
    431         long[] existing = new long[POSSIBLE_SETS.length + 1];
    432         int num = 0;
    433 
    434         // see which possible non-current sets exist...
    435         for (long token : POSSIBLE_SETS) {
    436             if ((new File(mDataDir, Long.toString(token))).exists()) {
    437                 existing[num++] = token;
    438             }
    439         }
    440         // ...and always the currently-active set last
    441         existing[num++] = CURRENT_SET_TOKEN;
    442 
    443         RestoreSet[] available = new RestoreSet[num];
    444         for (int i = 0; i < available.length; i++) {
    445             available[i] = new RestoreSet("Local disk image", "flash", existing[i]);
    446         }
    447         return available;
    448     }
    449 
    450     @Override
    451     public long getCurrentRestoreSet() {
    452         // The current restore set always has the same token
    453         return CURRENT_SET_TOKEN;
    454     }
    455 
    456     @Override
    457     public int startRestore(long token, PackageInfo[] packages) {
    458         if (DEBUG) Log.v(TAG, "start restore " + token + " : " + packages.length
    459                 + " matching packages");
    460         mRestorePackages = packages;
    461         mRestorePackage = -1;
    462         mRestoreSetDir = new File(mDataDir, Long.toString(token));
    463         mRestoreSetIncrementalDir = new File(mRestoreSetDir, INCREMENTAL_DIR);
    464         mRestoreSetFullDir = new File(mRestoreSetDir, FULL_DATA_DIR);
    465         return TRANSPORT_OK;
    466     }
    467 
    468     @Override
    469     public RestoreDescription nextRestorePackage() {
    470         if (DEBUG) {
    471             Log.v(TAG, "nextRestorePackage() : mRestorePackage=" + mRestorePackage
    472                     + " length=" + mRestorePackages.length);
    473         }
    474         if (mRestorePackages == null) throw new IllegalStateException("startRestore not called");
    475 
    476         boolean found = false;
    477         while (++mRestorePackage < mRestorePackages.length) {
    478             String name = mRestorePackages[mRestorePackage].packageName;
    479 
    480             // If we have key/value data for this package, deliver that
    481             // skip packages where we have a data dir but no actual contents
    482             String[] contents = (new File(mRestoreSetIncrementalDir, name)).list();
    483             if (contents != null && contents.length > 0) {
    484                 if (DEBUG) {
    485                     Log.v(TAG, "  nextRestorePackage(TYPE_KEY_VALUE) @ "
    486                         + mRestorePackage + " = " + name);
    487                 }
    488                 mRestoreType = RestoreDescription.TYPE_KEY_VALUE;
    489                 found = true;
    490             }
    491 
    492             if (!found) {
    493                 // No key/value data; check for [non-empty] full data
    494                 File maybeFullData = new File(mRestoreSetFullDir, name);
    495                 if (maybeFullData.length() > 0) {
    496                     if (DEBUG) {
    497                         Log.v(TAG, "  nextRestorePackage(TYPE_FULL_STREAM) @ "
    498                                 + mRestorePackage + " = " + name);
    499                     }
    500                     mRestoreType = RestoreDescription.TYPE_FULL_STREAM;
    501                     mCurFullRestoreStream = null;   // ensure starting from the ground state
    502                     found = true;
    503                 }
    504             }
    505 
    506             if (found) {
    507                 return new RestoreDescription(name, mRestoreType);
    508             }
    509 
    510             if (DEBUG) {
    511                 Log.v(TAG, "  ... package @ " + mRestorePackage + " = " + name
    512                         + " has no data; skipping");
    513             }
    514         }
    515 
    516         if (DEBUG) Log.v(TAG, "  no more packages to restore");
    517         return RestoreDescription.NO_MORE_PACKAGES;
    518     }
    519 
    520     @Override
    521     public int getRestoreData(ParcelFileDescriptor outFd) {
    522         if (mRestorePackages == null) throw new IllegalStateException("startRestore not called");
    523         if (mRestorePackage < 0) throw new IllegalStateException("nextRestorePackage not called");
    524         if (mRestoreType != RestoreDescription.TYPE_KEY_VALUE) {
    525             throw new IllegalStateException("getRestoreData(fd) for non-key/value dataset");
    526         }
    527         File packageDir = new File(mRestoreSetIncrementalDir,
    528                 mRestorePackages[mRestorePackage].packageName);
    529 
    530         // The restore set is the concatenation of the individual record blobs,
    531         // each of which is a file in the package's directory.  We return the
    532         // data in lexical order sorted by key, so that apps which use synthetic
    533         // keys like BLOB_1, BLOB_2, etc will see the date in the most obvious
    534         // order.
    535         ArrayList<DecodedFilename> blobs = contentsByKey(packageDir);
    536         if (blobs == null) {  // nextRestorePackage() ensures the dir exists, so this is an error
    537             Log.e(TAG, "No keys for package: " + packageDir);
    538             return TRANSPORT_ERROR;
    539         }
    540 
    541         // We expect at least some data if the directory exists in the first place
    542         if (DEBUG) Log.v(TAG, "  getRestoreData() found " + blobs.size() + " key files");
    543         BackupDataOutput out = new BackupDataOutput(outFd.getFileDescriptor());
    544         try {
    545             for (DecodedFilename keyEntry : blobs) {
    546                 File f = keyEntry.file;
    547                 FileInputStream in = new FileInputStream(f);
    548                 try {
    549                     int size = (int) f.length();
    550                     byte[] buf = new byte[size];
    551                     in.read(buf);
    552                     if (DEBUG) Log.v(TAG, "    ... key=" + keyEntry.key + " size=" + size);
    553                     out.writeEntityHeader(keyEntry.key, size);
    554                     out.writeEntityData(buf, size);
    555                 } finally {
    556                     in.close();
    557                 }
    558             }
    559             return TRANSPORT_OK;
    560         } catch (IOException e) {
    561             Log.e(TAG, "Unable to read backup records", e);
    562             return TRANSPORT_ERROR;
    563         }
    564     }
    565 
    566     static class DecodedFilename implements Comparable<DecodedFilename> {
    567         public File file;
    568         public String key;
    569 
    570         public DecodedFilename(File f) {
    571             file = f;
    572             key = new String(Base64.decode(f.getName()));
    573         }
    574 
    575         @Override
    576         public int compareTo(DecodedFilename other) {
    577             // sorts into ascending lexical order by decoded key
    578             return key.compareTo(other.key);
    579         }
    580     }
    581 
    582     // Return a list of the files in the given directory, sorted lexically by
    583     // the Base64-decoded file name, not by the on-disk filename
    584     private ArrayList<DecodedFilename> contentsByKey(File dir) {
    585         File[] allFiles = dir.listFiles();
    586         if (allFiles == null || allFiles.length == 0) {
    587             return null;
    588         }
    589 
    590         // Decode the filenames into keys then sort lexically by key
    591         ArrayList<DecodedFilename> contents = new ArrayList<DecodedFilename>();
    592         for (File f : allFiles) {
    593             contents.add(new DecodedFilename(f));
    594         }
    595         Collections.sort(contents);
    596         return contents;
    597     }
    598 
    599     @Override
    600     public void finishRestore() {
    601         if (DEBUG) Log.v(TAG, "finishRestore()");
    602         if (mRestoreType == RestoreDescription.TYPE_FULL_STREAM) {
    603             resetFullRestoreState();
    604         }
    605         mRestoreType = 0;
    606     }
    607 
    608     // ------------------------------------------------------------------------------------
    609     // Full restore handling
    610 
    611     private void resetFullRestoreState() {
    612         IoUtils.closeQuietly(mCurFullRestoreStream);
    613         mCurFullRestoreStream = null;
    614         mFullRestoreSocketStream = null;
    615         mFullRestoreBuffer = null;
    616     }
    617 
    618     /**
    619      * Ask the transport to provide data for the "current" package being restored.  The
    620      * transport then writes some data to the socket supplied to this call, and returns
    621      * the number of bytes written.  The system will then read that many bytes and
    622      * stream them to the application's agent for restore, then will call this method again
    623      * to receive the next chunk of the archive.  This sequence will be repeated until the
    624      * transport returns zero indicating that all of the package's data has been delivered
    625      * (or returns a negative value indicating some sort of hard error condition at the
    626      * transport level).
    627      *
    628      * <p>After this method returns zero, the system will then call
    629      * {@link #getNextFullRestorePackage()} to begin the restore process for the next
    630      * application, and the sequence begins again.
    631      *
    632      * @param socket The file descriptor that the transport will use for delivering the
    633      *    streamed archive.
    634      * @return 0 when no more data for the current package is available.  A positive value
    635      *    indicates the presence of that much data to be delivered to the app.  A negative
    636      *    return value is treated as equivalent to {@link BackupTransport#TRANSPORT_ERROR},
    637      *    indicating a fatal error condition that precludes further restore operations
    638      *    on the current dataset.
    639      */
    640     @Override
    641     public int getNextFullRestoreDataChunk(ParcelFileDescriptor socket) {
    642         if (mRestoreType != RestoreDescription.TYPE_FULL_STREAM) {
    643             throw new IllegalStateException("Asked for full restore data for non-stream package");
    644         }
    645 
    646         // first chunk?
    647         if (mCurFullRestoreStream == null) {
    648             final String name = mRestorePackages[mRestorePackage].packageName;
    649             if (DEBUG) Log.i(TAG, "Starting full restore of " + name);
    650             File dataset = new File(mRestoreSetFullDir, name);
    651             try {
    652                 mCurFullRestoreStream = new FileInputStream(dataset);
    653             } catch (IOException e) {
    654                 // If we can't open the target package's tarball, we return the single-package
    655                 // error code and let the caller go on to the next package.
    656                 Log.e(TAG, "Unable to read archive for " + name);
    657                 return TRANSPORT_PACKAGE_REJECTED;
    658             }
    659             mFullRestoreSocketStream = new FileOutputStream(socket.getFileDescriptor());
    660             mFullRestoreBuffer = new byte[2*1024];
    661         }
    662 
    663         int nRead;
    664         try {
    665             nRead = mCurFullRestoreStream.read(mFullRestoreBuffer);
    666             if (nRead < 0) {
    667                 // EOF: tell the caller we're done
    668                 nRead = NO_MORE_DATA;
    669             } else if (nRead == 0) {
    670                 // This shouldn't happen when reading a FileInputStream; we should always
    671                 // get either a positive nonzero byte count or -1.  Log the situation and
    672                 // treat it as EOF.
    673                 Log.w(TAG, "read() of archive file returned 0; treating as EOF");
    674                 nRead = NO_MORE_DATA;
    675             } else {
    676                 if (DEBUG) {
    677                     Log.i(TAG, "   delivering restore chunk: " + nRead);
    678                 }
    679                 mFullRestoreSocketStream.write(mFullRestoreBuffer, 0, nRead);
    680             }
    681         } catch (IOException e) {
    682             return TRANSPORT_ERROR;  // Hard error accessing the file; shouldn't happen
    683         } finally {
    684             // Most transports will need to explicitly close 'socket' here, but this transport
    685             // is in the same process as the caller so it can leave it up to the backup manager
    686             // to manage both socket fds.
    687         }
    688 
    689         return nRead;
    690     }
    691 
    692     /**
    693      * If the OS encounters an error while processing {@link RestoreDescription#TYPE_FULL_STREAM}
    694      * data for restore, it will invoke this method to tell the transport that it should
    695      * abandon the data download for the current package.  The OS will then either call
    696      * {@link #nextRestorePackage()} again to move on to restoring the next package in the
    697      * set being iterated over, or will call {@link #finishRestore()} to shut down the restore
    698      * operation.
    699      *
    700      * @return {@link #TRANSPORT_OK} if the transport was successful in shutting down the
    701      *    current stream cleanly, or {@link #TRANSPORT_ERROR} to indicate a serious
    702      *    transport-level failure.  If the transport reports an error here, the entire restore
    703      *    operation will immediately be finished with no further attempts to restore app data.
    704      */
    705     @Override
    706     public int abortFullRestore() {
    707         if (mRestoreType != RestoreDescription.TYPE_FULL_STREAM) {
    708             throw new IllegalStateException("abortFullRestore() but not currently restoring");
    709         }
    710         resetFullRestoreState();
    711         mRestoreType = 0;
    712         return TRANSPORT_OK;
    713     }
    714 
    715     @Override
    716     public long getBackupQuota(String packageName, boolean isFullBackup) {
    717         return isFullBackup ? FULL_BACKUP_SIZE_QUOTA : KEY_VALUE_BACKUP_SIZE_QUOTA;
    718     }
    719 }
    720