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