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