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