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.RestoreSet;
     22 import android.content.ComponentName;
     23 import android.content.Context;
     24 import android.content.Intent;
     25 import android.content.pm.PackageInfo;
     26 import android.content.pm.PackageManager;
     27 import android.content.pm.PackageManager.NameNotFoundException;
     28 import android.os.Environment;
     29 import android.os.ParcelFileDescriptor;
     30 import android.os.RemoteException;
     31 import android.os.SELinux;
     32 import android.util.Log;
     33 
     34 import com.android.org.bouncycastle.util.encoders.Base64;
     35 
     36 import java.io.File;
     37 import java.io.FileFilter;
     38 import java.io.FileInputStream;
     39 import java.io.FileOutputStream;
     40 import java.io.IOException;
     41 import java.util.ArrayList;
     42 
     43 /**
     44  * Backup transport for stashing stuff into a known location on disk, and
     45  * later restoring from there.  For testing only.
     46  */
     47 
     48 public class LocalTransport extends IBackupTransport.Stub {
     49     private static final String TAG = "LocalTransport";
     50     private static final boolean DEBUG = true;
     51 
     52     private static final String TRANSPORT_DIR_NAME
     53             = "com.android.internal.backup.LocalTransport";
     54 
     55     private static final String TRANSPORT_DESTINATION_STRING
     56             = "Backing up to debug-only private cache";
     57 
     58     // The single hardcoded restore set always has the same (nonzero!) token
     59     private static final long RESTORE_TOKEN = 1;
     60 
     61     private Context mContext;
     62     private File mDataDir = new File(Environment.getDownloadCacheDirectory(), "backup");
     63     private PackageInfo[] mRestorePackages = null;
     64     private int mRestorePackage = -1;  // Index into mRestorePackages
     65 
     66 
     67     public LocalTransport(Context context) {
     68         mContext = context;
     69         mDataDir.mkdirs();
     70         if (!SELinux.restorecon(mDataDir)) {
     71             Log.e(TAG, "SELinux restorecon failed for " + mDataDir);
     72         }
     73     }
     74 
     75     public String name() {
     76         return new ComponentName(mContext, this.getClass()).flattenToShortString();
     77     }
     78 
     79     public Intent configurationIntent() {
     80         // The local transport is not user-configurable
     81         return null;
     82     }
     83 
     84     public String currentDestinationString() {
     85         return TRANSPORT_DESTINATION_STRING;
     86     }
     87 
     88     public String transportDirName() {
     89         return TRANSPORT_DIR_NAME;
     90     }
     91 
     92     public long requestBackupTime() {
     93         // any time is a good time for local backup
     94         return 0;
     95     }
     96 
     97     public int initializeDevice() {
     98         if (DEBUG) Log.v(TAG, "wiping all data");
     99         deleteContents(mDataDir);
    100         return BackupConstants.TRANSPORT_OK;
    101     }
    102 
    103     public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor data) {
    104         if (DEBUG) Log.v(TAG, "performBackup() pkg=" + packageInfo.packageName);
    105 
    106         File packageDir = new File(mDataDir, packageInfo.packageName);
    107         packageDir.mkdirs();
    108 
    109         // Each 'record' in the restore set is kept in its own file, named by
    110         // the record key.  Wind through the data file, extracting individual
    111         // record operations and building a set of all the updates to apply
    112         // in this update.
    113         BackupDataInput changeSet = new BackupDataInput(data.getFileDescriptor());
    114         try {
    115             int bufSize = 512;
    116             byte[] buf = new byte[bufSize];
    117             while (changeSet.readNextHeader()) {
    118                 String key = changeSet.getKey();
    119                 String base64Key = new String(Base64.encode(key.getBytes()));
    120                 File entityFile = new File(packageDir, base64Key);
    121 
    122                 int dataSize = changeSet.getDataSize();
    123 
    124                 if (DEBUG) Log.v(TAG, "Got change set key=" + key + " size=" + dataSize
    125                         + " key64=" + base64Key);
    126 
    127                 if (dataSize >= 0) {
    128                     if (entityFile.exists()) {
    129                         entityFile.delete();
    130                     }
    131                     FileOutputStream entity = new FileOutputStream(entityFile);
    132 
    133                     if (dataSize > bufSize) {
    134                         bufSize = dataSize;
    135                         buf = new byte[bufSize];
    136                     }
    137                     changeSet.readEntityData(buf, 0, dataSize);
    138                     if (DEBUG) Log.v(TAG, "  data size " + dataSize);
    139 
    140                     try {
    141                         entity.write(buf, 0, dataSize);
    142                     } catch (IOException e) {
    143                         Log.e(TAG, "Unable to update key file " + entityFile.getAbsolutePath());
    144                         return BackupConstants.TRANSPORT_ERROR;
    145                     } finally {
    146                         entity.close();
    147                     }
    148                 } else {
    149                     entityFile.delete();
    150                 }
    151             }
    152             return BackupConstants.TRANSPORT_OK;
    153         } catch (IOException e) {
    154             // oops, something went wrong.  abort the operation and return error.
    155             Log.v(TAG, "Exception reading backup input:", e);
    156             return BackupConstants.TRANSPORT_ERROR;
    157         }
    158     }
    159 
    160     // Deletes the contents but not the given directory
    161     private void deleteContents(File dirname) {
    162         File[] contents = dirname.listFiles();
    163         if (contents != null) {
    164             for (File f : contents) {
    165                 if (f.isDirectory()) {
    166                     // delete the directory's contents then fall through
    167                     // and delete the directory itself.
    168                     deleteContents(f);
    169                 }
    170                 f.delete();
    171             }
    172         }
    173     }
    174 
    175     public int clearBackupData(PackageInfo packageInfo) {
    176         if (DEBUG) Log.v(TAG, "clearBackupData() pkg=" + packageInfo.packageName);
    177 
    178         File packageDir = new File(mDataDir, packageInfo.packageName);
    179         final File[] fileset = packageDir.listFiles();
    180         if (fileset != null) {
    181             for (File f : fileset) {
    182                 f.delete();
    183             }
    184             packageDir.delete();
    185         }
    186         return BackupConstants.TRANSPORT_OK;
    187     }
    188 
    189     public int finishBackup() {
    190         if (DEBUG) Log.v(TAG, "finishBackup()");
    191         return BackupConstants.TRANSPORT_OK;
    192     }
    193 
    194     // Restore handling
    195     public RestoreSet[] getAvailableRestoreSets() throws android.os.RemoteException {
    196         // one hardcoded restore set
    197         RestoreSet set = new RestoreSet("Local disk image", "flash", RESTORE_TOKEN);
    198         RestoreSet[] array = { set };
    199         return array;
    200     }
    201 
    202     public long getCurrentRestoreSet() {
    203         // The hardcoded restore set always has the same token
    204         return RESTORE_TOKEN;
    205     }
    206 
    207     public int startRestore(long token, PackageInfo[] packages) {
    208         if (DEBUG) Log.v(TAG, "start restore " + token);
    209         mRestorePackages = packages;
    210         mRestorePackage = -1;
    211         return BackupConstants.TRANSPORT_OK;
    212     }
    213 
    214     public String nextRestorePackage() {
    215         if (mRestorePackages == null) throw new IllegalStateException("startRestore not called");
    216         while (++mRestorePackage < mRestorePackages.length) {
    217             String name = mRestorePackages[mRestorePackage].packageName;
    218             if (new File(mDataDir, name).isDirectory()) {
    219                 if (DEBUG) Log.v(TAG, "  nextRestorePackage() = " + name);
    220                 return name;
    221             }
    222         }
    223 
    224         if (DEBUG) Log.v(TAG, "  no more packages to restore");
    225         return "";
    226     }
    227 
    228     public int getRestoreData(ParcelFileDescriptor outFd) {
    229         if (mRestorePackages == null) throw new IllegalStateException("startRestore not called");
    230         if (mRestorePackage < 0) throw new IllegalStateException("nextRestorePackage not called");
    231         File packageDir = new File(mDataDir, mRestorePackages[mRestorePackage].packageName);
    232 
    233         // The restore set is the concatenation of the individual record blobs,
    234         // each of which is a file in the package's directory
    235         File[] blobs = packageDir.listFiles();
    236         if (blobs == null) {  // nextRestorePackage() ensures the dir exists, so this is an error
    237             Log.e(TAG, "Error listing directory: " + packageDir);
    238             return BackupConstants.TRANSPORT_ERROR;
    239         }
    240 
    241         // We expect at least some data if the directory exists in the first place
    242         if (DEBUG) Log.v(TAG, "  getRestoreData() found " + blobs.length + " key files");
    243         BackupDataOutput out = new BackupDataOutput(outFd.getFileDescriptor());
    244         try {
    245             for (File f : blobs) {
    246                 FileInputStream in = new FileInputStream(f);
    247                 try {
    248                     int size = (int) f.length();
    249                     byte[] buf = new byte[size];
    250                     in.read(buf);
    251                     String key = new String(Base64.decode(f.getName()));
    252                     if (DEBUG) Log.v(TAG, "    ... key=" + key + " size=" + size);
    253                     out.writeEntityHeader(key, size);
    254                     out.writeEntityData(buf, size);
    255                 } finally {
    256                     in.close();
    257                 }
    258             }
    259             return BackupConstants.TRANSPORT_OK;
    260         } catch (IOException e) {
    261             Log.e(TAG, "Unable to read backup records", e);
    262             return BackupConstants.TRANSPORT_ERROR;
    263         }
    264     }
    265 
    266     public void finishRestore() {
    267         if (DEBUG) Log.v(TAG, "finishRestore()");
    268     }
    269 }
    270