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